From 90613f5039bbefc6da91e1fbe19312d6dc670c7a Mon Sep 17 00:00:00 2001 From: mireado Date: Wed, 6 Jan 2016 00:01:17 +0900 Subject: [PATCH] update to 3.5640.1 update to ITHVNR 3.5640.1 and translation --- CMakeLists.txt | 97 +- gui/CustomFilter.h | 1 + gui/ITH.h | 6 +- gui/ITHVNR.rc | 73 +- gui/ProcessWindow.cpp | 25 +- gui/ProcessWindow.h | 5 +- gui/ProfileManager.cpp | 210 +- gui/ProfileManager.h | 19 +- gui/TextBuffer.cpp | 4 +- gui/TextBuffer.h | 1 + gui/command.cpp | 171 +- gui/language.cpp | 154 +- gui/main.cpp | 43 +- gui/utility.cpp | 60 +- gui/utility.h | 9 +- gui/window.cpp | 470 +- gui/window.h | 1 + vnr/CMakeLists.txt | 38 +- vnr/config.pri | 12 +- vnr/copy_vnr.cmd | 12 +- vnr/cpputil/cppcstring.h | 46 +- vnr/cpputil/cpplocale.h | 20 +- vnr/cpputil/cppstring.h | 30 +- vnr/disasm/disasm.cc | 24 +- vnr/disasm/disasm.h | 10 +- vnr/hashutil/hashstr.h | 55 + vnr/hashutil/hashutil.h | 15 + vnr/hashutil/hashutil.pri | 11 + vnr/ithsys/CMakeLists.txt | 28 + vnr/ithsys/ithsys.cc | 1549 ++ vnr/ithsys/ithsys.h | 132 + vnr/ithsys/ithsys.pri | 13 + vnr/ithsys/ithsys.pro | 52 + vnr/memdbg/memsearch.cc | 528 +- vnr/memdbg/memsearch.h | 88 +- vnr/mono/mono.pri | 12 + vnr/mono/monoobject.h | 48 + vnr/mono/monotype.h | 17 + vnr/ntinspect/ntinspect.cc | 115 +- vnr/ntinspect/ntinspect.h | 67 +- vnr/profile/CMakeLists.txt | 23 + vnr/profile/Profile.cpp | 293 + vnr/profile/Profile.h | 171 + vnr/profile/misc.cpp | 275 + vnr/profile/misc.h | 22 + vnr/profile/pugiconfig.hpp | 71 + vnr/profile/pugixml.cpp | 11554 +++++++++++ vnr/profile/pugixml.hpp | 1366 ++ vnr/sakurakit/sakurakit.pri | 62 + vnr/sakurakit/skautorun.h | 42 + vnr/sakurakit/skdebug.h | 46 + vnr/sakurakit/skglobal.h | 94 + vnr/sakurakit/skhash.h | 59 + vnr/texthook/growl.h | 85 + vnr/texthook/host/CMakeLists.txt | 69 + vnr/texthook/host/avl_p.h | 588 + vnr/texthook/host/config.h | 16 + vnr/texthook/host/hookman.cc | 1039 + vnr/texthook/host/hookman.h | 153 + vnr/texthook/host/host.cc | 646 + vnr/texthook/host/host.h | 33 + vnr/texthook/host/host.pri | 23 + vnr/texthook/host/host_p.h | 44 + vnr/texthook/host/pipe.cc | 327 + vnr/texthook/host/settings.h | 17 + vnr/texthook/host/textthread.cc | 786 + vnr/texthook/host/textthread.h | 135 + vnr/texthook/host/textthread_p.h | 155 + vnr/texthook/ihf.txt | 20 + vnr/texthook/ihf_p.cc | 583 + vnr/texthook/ihf_p.h | 117 + vnr/texthook/ith_p.cc | 275 + vnr/texthook/ith_p.h | 20 + vnr/texthook/texthook.cc | 414 + vnr/texthook/texthook.h | 101 + vnr/texthook/texthook.pri | 20 + vnr/texthook/texthook.pro | 61 + vnr/texthook/texthook.rc | 38 + vnr/texthook/texthook_config.h | 20 + vnr/texthook/texthook_p.h | 39 + vnr/texthook/texthook_static.pri | 73 + vnr/texthook/textthread_p.cc | 376 + vnr/texthook/textthread_p.h | 81 + vnr/texthook/winapi_p.cc | 21 + vnr/texthook/winapi_p.h | 18 + vnr/vnrhook/CMakeLists.txt | 112 + vnr/vnrhook/TRASH/dllconfig.pri | 35 + vnr/vnrhook/TRASH/hookxp.pro | 61 + vnr/vnrhook/TRASH/memory.h | 46 + vnr/vnrhook/TRASH/string.h | 36 + vnr/vnrhook/TRASH/xp.txt | 11 + vnr/vnrhook/include/const.h | 249 + vnr/vnrhook/include/defs.h | 52 + vnr/vnrhook/include/types.h | 91 + vnr/vnrhook/src/engine/engine.cc | 21158 +++++++++++++++++++++ vnr/vnrhook/src/engine/engine.h | 170 + vnr/vnrhook/src/engine/hookdefs.h | 12 + vnr/vnrhook/src/engine/match.cc | 936 + vnr/vnrhook/src/engine/match.h | 23 + vnr/vnrhook/src/engine/mono/funcinfo.h | 55 + vnr/vnrhook/src/engine/mono/types.h | 41 + vnr/vnrhook/src/engine/pchooks.cc | 278 + vnr/vnrhook/src/engine/pchooks.h | 16 + vnr/vnrhook/src/engine/ppsspp/funcinfo.h | 105 + vnr/vnrhook/src/except.h | 25 + vnr/vnrhook/src/hijack/texthook.cc | 910 + vnr/vnrhook/src/hijack/texthook.h | 98 + vnr/vnrhook/src/main.cc | 418 + vnr/vnrhook/src/main.h | 25 + vnr/vnrhook/src/pipe.cc | 353 + vnr/vnrhook/src/tree/avl.h | 589 + vnr/vnrhook/src/util/growl.h | 85 + vnr/vnrhook/src/util/util.cc | 326 + vnr/vnrhook/src/util/util.h | 78 + vnr/vnrhook/vnrhook.pri | 12 + vnr/vnrhook/vnrhook.pro | 80 + vnr/windbg/hijack.cc | 89 + vnr/windbg/hijack.h | 25 + vnr/windbg/inject.cc | 167 + vnr/windbg/inject.h | 56 + vnr/windbg/unload.cc | 22 + vnr/windbg/unload.h | 19 + vnr/windbg/util.cc | 61 + vnr/windbg/util.h | 34 + vnr/windbg/windbg.h | 16 + vnr/windbg/windbg.pri | 26 + vnr/windbg/windbg_p.h | 24 + vnr/winkey/winkey.h | 32 + vnr/winkey/winkey.pri | 15 + vnr/winmutex/winmutex.h | 2 +- vnr/winseh/Makefile | 2 +- vnr/winseh/safeseh.asm | 2 +- vnr/winseh/winseh.cc | 16 +- vnr/winseh/winseh.h | 44 +- vnr/wintimer/wintimer.cc | 49 + vnr/wintimer/wintimer.h | 93 + vnr/wintimer/wintimer.pri | 20 + vnr/wintimer/wintimer.pro | 22 + vnr/wintimer/wintimerbase.cc | 73 + vnr/wintimer/wintimerbase.h | 68 + 140 files changed, 51080 insertions(+), 1125 deletions(-) create mode 100644 vnr/hashutil/hashstr.h create mode 100644 vnr/hashutil/hashutil.h create mode 100644 vnr/hashutil/hashutil.pri create mode 100644 vnr/ithsys/CMakeLists.txt create mode 100644 vnr/ithsys/ithsys.cc create mode 100644 vnr/ithsys/ithsys.h create mode 100644 vnr/ithsys/ithsys.pri create mode 100644 vnr/ithsys/ithsys.pro create mode 100644 vnr/mono/mono.pri create mode 100644 vnr/mono/monoobject.h create mode 100644 vnr/mono/monotype.h create mode 100644 vnr/profile/CMakeLists.txt create mode 100644 vnr/profile/Profile.cpp create mode 100644 vnr/profile/Profile.h create mode 100644 vnr/profile/misc.cpp create mode 100644 vnr/profile/misc.h create mode 100644 vnr/profile/pugiconfig.hpp create mode 100644 vnr/profile/pugixml.cpp create mode 100644 vnr/profile/pugixml.hpp create mode 100644 vnr/sakurakit/sakurakit.pri create mode 100644 vnr/sakurakit/skautorun.h create mode 100644 vnr/sakurakit/skdebug.h create mode 100644 vnr/sakurakit/skglobal.h create mode 100644 vnr/sakurakit/skhash.h create mode 100644 vnr/texthook/growl.h create mode 100644 vnr/texthook/host/CMakeLists.txt create mode 100644 vnr/texthook/host/avl_p.h create mode 100644 vnr/texthook/host/config.h create mode 100644 vnr/texthook/host/hookman.cc create mode 100644 vnr/texthook/host/hookman.h create mode 100644 vnr/texthook/host/host.cc create mode 100644 vnr/texthook/host/host.h create mode 100644 vnr/texthook/host/host.pri create mode 100644 vnr/texthook/host/host_p.h create mode 100644 vnr/texthook/host/pipe.cc create mode 100644 vnr/texthook/host/settings.h create mode 100644 vnr/texthook/host/textthread.cc create mode 100644 vnr/texthook/host/textthread.h create mode 100644 vnr/texthook/host/textthread_p.h create mode 100644 vnr/texthook/ihf.txt create mode 100644 vnr/texthook/ihf_p.cc create mode 100644 vnr/texthook/ihf_p.h create mode 100644 vnr/texthook/ith_p.cc create mode 100644 vnr/texthook/ith_p.h create mode 100644 vnr/texthook/texthook.cc create mode 100644 vnr/texthook/texthook.h create mode 100644 vnr/texthook/texthook.pri create mode 100644 vnr/texthook/texthook.pro create mode 100644 vnr/texthook/texthook.rc create mode 100644 vnr/texthook/texthook_config.h create mode 100644 vnr/texthook/texthook_p.h create mode 100644 vnr/texthook/texthook_static.pri create mode 100644 vnr/texthook/textthread_p.cc create mode 100644 vnr/texthook/textthread_p.h create mode 100644 vnr/texthook/winapi_p.cc create mode 100644 vnr/texthook/winapi_p.h create mode 100644 vnr/vnrhook/CMakeLists.txt create mode 100644 vnr/vnrhook/TRASH/dllconfig.pri create mode 100644 vnr/vnrhook/TRASH/hookxp.pro create mode 100644 vnr/vnrhook/TRASH/memory.h create mode 100644 vnr/vnrhook/TRASH/string.h create mode 100644 vnr/vnrhook/TRASH/xp.txt create mode 100644 vnr/vnrhook/include/const.h create mode 100644 vnr/vnrhook/include/defs.h create mode 100644 vnr/vnrhook/include/types.h create mode 100644 vnr/vnrhook/src/engine/engine.cc create mode 100644 vnr/vnrhook/src/engine/engine.h create mode 100644 vnr/vnrhook/src/engine/hookdefs.h create mode 100644 vnr/vnrhook/src/engine/match.cc create mode 100644 vnr/vnrhook/src/engine/match.h create mode 100644 vnr/vnrhook/src/engine/mono/funcinfo.h create mode 100644 vnr/vnrhook/src/engine/mono/types.h create mode 100644 vnr/vnrhook/src/engine/pchooks.cc create mode 100644 vnr/vnrhook/src/engine/pchooks.h create mode 100644 vnr/vnrhook/src/engine/ppsspp/funcinfo.h create mode 100644 vnr/vnrhook/src/except.h create mode 100644 vnr/vnrhook/src/hijack/texthook.cc create mode 100644 vnr/vnrhook/src/hijack/texthook.h create mode 100644 vnr/vnrhook/src/main.cc create mode 100644 vnr/vnrhook/src/main.h create mode 100644 vnr/vnrhook/src/pipe.cc create mode 100644 vnr/vnrhook/src/tree/avl.h create mode 100644 vnr/vnrhook/src/util/growl.h create mode 100644 vnr/vnrhook/src/util/util.cc create mode 100644 vnr/vnrhook/src/util/util.h create mode 100644 vnr/vnrhook/vnrhook.pri create mode 100644 vnr/vnrhook/vnrhook.pro create mode 100644 vnr/windbg/hijack.cc create mode 100644 vnr/windbg/hijack.h create mode 100644 vnr/windbg/inject.cc create mode 100644 vnr/windbg/inject.h create mode 100644 vnr/windbg/unload.cc create mode 100644 vnr/windbg/unload.h create mode 100644 vnr/windbg/util.cc create mode 100644 vnr/windbg/util.h create mode 100644 vnr/windbg/windbg.h create mode 100644 vnr/windbg/windbg.pri create mode 100644 vnr/windbg/windbg_p.h create mode 100644 vnr/winkey/winkey.h create mode 100644 vnr/winkey/winkey.pri create mode 100644 vnr/wintimer/wintimer.cc create mode 100644 vnr/wintimer/wintimer.h create mode 100644 vnr/wintimer/wintimer.pri create mode 100644 vnr/wintimer/wintimer.pro create mode 100644 vnr/wintimer/wintimerbase.cc create mode 100644 vnr/wintimer/wintimerbase.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 29d7a45..715d51e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,55 +1,3 @@ -# common.pri -# DEFINES += _CRT_NON_CONFORMING_SWPRINTFS - -# config.pri -# win32 { -# DEFINES += _SECURE_SCL=0 _SCL_SECURE_NO_WARNINGS -# DEFINES += _CRT_SECURE_NO_WARNINGS -# QMAKE_CXXFLAGS += -wd4819 -# } - -# config.pri -# win32 { -# CONFIG(nocrt) { -# message(CONFIG nocrt) -# QMAKE_CFLAGS -= /MD /MDd -# QMAKE_CFLAGS_DEBUG -= /MD /MDd -# QMAKE_CFLAGS_RELEASE -= /MD /MDd -# QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO -= /MD /MDd -# QMAKE_CXXFLAGS -= /MD /MDd -# QMAKE_CXXFLAGS_DEBUG -= /MD /MDd -# QMAKE_CXXFLAGS_RELEASE -= /MD /MDd -# QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO -= /MD /MDd -# } -# CONFIG(eha) { -# message(CONFIG eha) -# QMAKE_CXXFLAGS_STL_ON -= /EHsc -# QMAKE_CXXFLAGS_EXCEPTIONS_ON -= /EHsc -# QMAKE_CXXFLAGS_STL_ON += /EHa -# QMAKE_CXXFLAGS_EXCEPTIONS_ON += /EHa -# } -# CONFIG(noeh) { -# message(CONFIG noeh) -# QMAKE_CXXFLAGS += /GR- -# QMAKE_CXXFLAGS_RTTI_ON -= /GR -# QMAKE_CXXFLAGS_STL_ON -= /EHsc -# QMAKE_CXXFLAGS_EXCEPTIONS_ON -= /EHsc -# CONFIG(dll) { -# QMAKE_LFLAGS += /ENTRY:"DllMain" -# } -# } -# CONFIG(nosafeseh) { -# message(CONFIG nosafeseh) -# QMAKE_LFLAGS += -safeseh:no -# } -# } - -# dllconfig.pri -# win32 { -# CONFIG(eh): DEFINES += ITH_HAS_SEH -# CONFIG(noeh): DEFINES -= ITH_HAS_SEH -# } - cmake_minimum_required(VERSION 2.8) set(CMAKE_CONFIGURATION_TYPES Debug Release) @@ -66,10 +14,11 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/Release") set(CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION ON) + set(CPACK_GENERATOR "ZIP") set(CPACK_PACKAGE_VERSION_MAJOR 3) -set(CPACK_PACKAGE_VERSION_MINOR 4152) -set(CPACK_PACKAGE_VERSION_PATCH 0) +set(CPACK_PACKAGE_VERSION_MINOR 5640) +set(CPACK_PACKAGE_VERSION_PATCH 1) set(CPACK_SOURCE_GENERATOR "ZIP") set(CPACK_SOURCE_IGNORE_FILES "/CVS/;/\\\\.svn/;/\\\\.bzr/;/\\\\.hg/;/\\\\.git/;\\\\.swp$;\\\\.#;/#" ".*\\\\.user$" "\\\\.gitignore$" "\\\\.gitmodules$" "\\\\.git$") include(CPack) @@ -84,24 +33,20 @@ add_compile_options( ) add_definitions( - -D_SECURE_SCL=0 # config.pri - -D_SCL_SECURE_NO_WARNINGS # config.pri - -D_CRT_SECURE_NO_WARNINGS # config.pri - -DUNICODE # config.pri - -D_UNICODE - -D_CRT_NON_CONFORMING_SWPRINTFS # common.pri - -DITH_HAS_CRT + /D_SECURE_SCL=0 # config.pri + /D_SCL_SECURE_NO_WARNINGS # config.pri + /D_CRT_SECURE_NO_WARNINGS # config.pri + /DUNICODE # config.pri + /D_UNICODE + /D_CRT_NON_CONFORMING_SWPRINTFS # common.pri + /DITH_HAS_CRT ) -include_directories(${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/vnr ${CMAKE_BINARY_DIR}/gui) - -set(common_src - vnr/ith/common/const.h - vnr/ith/common/defs.h - vnr/ith/common/except.h - vnr/ith/common/growl.h - vnr/ith/common/memory.h - vnr/ith/common/types.h +include_directories( + . + vnr + vnr/texthook + ${CMAKE_BINARY_DIR}/gui ) set(resource_src @@ -119,13 +64,8 @@ set(ithvnr_src gui/main.cpp gui/ProcessWindow.cpp gui/ProcessWindow.h - gui/Profile.cpp - gui/Profile.h gui/ProfileManager.cpp gui/ProfileManager.h - gui/pugiconfig.hpp - gui/pugixml.cpp - gui/pugixml.hpp gui/resource.h gui/utility.cpp gui/utility.h @@ -135,17 +75,15 @@ set(ithvnr_src gui/window.h gui/TextBuffer.cpp gui/TextBuffer.h - ${common_src} ${resource_src} ) -source_group("common" FILES ${common_src}) - source_group("Resource Files" FILES ${resource_src}) add_executable(${PROJECT_NAME} ${ithvnr_src}) add_subdirectory(vnr) +# add_subdirectory(profile) set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS /MANIFESTDEPENDENCY:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"" @@ -158,8 +96,9 @@ target_compile_definitions(${PROJECT_NAME} ) target_link_libraries(${PROJECT_NAME} + profile vnrhost - vnrsys + ithsys ${WDK_HOME}/lib/wxp/i386/ntdll.lib comctl32.lib psapi.lib diff --git a/gui/CustomFilter.h b/gui/CustomFilter.h index 4bdcbc4..fb298a0 100644 --- a/gui/CustomFilter.h +++ b/gui/CustomFilter.h @@ -16,6 +16,7 @@ */ #pragma once + #include "ITH.h" typedef void (*CustomFilterCallBack) (WORD, PVOID); diff --git a/gui/ITH.h b/gui/ITH.h index 0873862..0701399 100644 --- a/gui/ITH.h +++ b/gui/ITH.h @@ -15,7 +15,8 @@ * along with this program. If not, see . */ #pragma once -#include + +#include #include #include #include @@ -29,9 +30,8 @@ #include #include #include -#include #include #include #include -#include "pugixml.hpp" +#include "profile/pugixml.hpp" #pragma warning(disable: 4146) diff --git a/gui/ITHVNR.rc b/gui/ITHVNR.rc index 0fe97f4..3f66f64 100644 --- a/gui/ITHVNR.rc +++ b/gui/ITHVNR.rc @@ -1,5 +1,5 @@ -// Generated by ResEdit 1.6.3 -// Copyright (C) 2006-2014 +// Generated by ResEdit 1.6.5 +// Copyright (C) 2006-2015 // http://www.resedit.net #include @@ -7,9 +7,6 @@ #include #include "resource.h" -#define WC_LISTVIEW L"SysListView32" - - @@ -22,14 +19,14 @@ STYLE DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYS CAPTION "Process Explorer" FONT 8, "MS Shell Dlg", 400, 0, 1 { - DEFPUSHBUTTON "확인", IDOK, 281, 189, 53, 14, 0, WS_EX_LEFT - PUSHBUTTON "프로필 제거", IDC_BUTTON6, 226, 189, 53, 14, 0, WS_EX_LEFT - CONTROL "", IDC_LIST1, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_SHOWSELALWAYS | LVS_SINGLESEL | LVS_REPORT, 7, 20, 327, 164, WS_EX_LEFT - LTEXT "프로세스", IDC_STATIC, 7, 7, 65, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT - PUSHBUTTON "부착", IDC_BUTTON2, 61, 189, 53, 14, 0, WS_EX_LEFT - PUSHBUTTON "탈착", IDC_BUTTON3, 116, 189, 53, 14, 0, WS_EX_LEFT - PUSHBUTTON "프로필 추가", IDC_BUTTON5, 171, 189, 53, 14, 0, WS_EX_LEFT - PUSHBUTTON "새로고침", IDC_BUTTON1, 7, 189, 53, 14, 0, WS_EX_LEFT + DEFPUSHBUTTON "Ȯ", IDOK, 281, 189, 53, 14, 0, WS_EX_LEFT + PUSHBUTTON " ", IDC_BUTTON6, 226, 189, 53, 14, 0, WS_EX_LEFT + CONTROL "", IDC_LIST1, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_SHOWSELALWAYS | LVS_SINGLESEL | LVS_REPORT, 7, 20, 327, 164, WS_EX_LEFT + LTEXT "μ", IDC_STATIC, 7, 7, 65, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT + PUSHBUTTON "", IDC_BUTTON2, 61, 189, 53, 14, 0, WS_EX_LEFT + PUSHBUTTON "Ż", IDC_BUTTON3, 116, 189, 53, 14, 0, WS_EX_LEFT + PUSHBUTTON " ߰", IDC_BUTTON5, 171, 189, 53, 14, 0, WS_EX_LEFT + PUSHBUTTON "ΰħ", IDC_BUTTON1, 7, 189, 53, 14, 0, WS_EX_LEFT } @@ -40,22 +37,22 @@ STYLE DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYS CAPTION "Option" FONT 8, "MS Shell Dlg", 400, 0, 1 { - DEFPUSHBUTTON "확인", IDOK, 8, 164, 50, 14, 0, WS_EX_LEFT - PUSHBUTTON "취소", IDCANCEL, 65, 164, 50, 14, 0, WS_EX_LEFT - EDITTEXT IDC_EDIT1, 60, 7, 55, 14, ES_AUTOHSCROLL, WS_EX_LEFT - LTEXT "문단나누기", IDC_STATIC, 7, 7, 47, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT - EDITTEXT IDC_EDIT2, 60, 25, 55, 14, ES_AUTOHSCROLL, WS_EX_LEFT - LTEXT "프로세스 대기", IDC_STATIC, 7, 26, 47, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT - EDITTEXT IDC_EDIT3, 60, 45, 55, 14, ES_AUTOHSCROLL, WS_EX_LEFT - LTEXT "인젝션 대기", IDC_STATIC, 7, 45, 47, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT - EDITTEXT IDC_EDIT4, 60, 65, 55, 14, ES_AUTOHSCROLL, WS_EX_LEFT - LTEXT "삽입 대기", IDC_STATIC, 7, 65, 47, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT - AUTOCHECKBOX "자동 부착", IDC_CHECK1, 7, 87, 54, 10, 0, WS_EX_LEFT - AUTOCHECKBOX "자동 삽입", IDC_CHECK2, 62, 87, 50, 10, 0, WS_EX_LEFT - AUTOCHECKBOX "클립보드로 자동 복사", IDC_CHECK3, 7, 103, 88, 10, 0, WS_EX_LEFT - AUTOCHECKBOX "자동 반복문 제거", IDC_CHECK4, 7, 119, 95, 10, 0, WS_EX_LEFT - AUTOCHECKBOX "문자필터 초기화", IDC_CHECK6, 7, 149, 81, 8, 0, WS_EX_LEFT - AUTOCHECKBOX "글로벌 필터 활성화", IDC_CHECK5, 7, 134, 75, 10, 0, WS_EX_LEFT + DEFPUSHBUTTON "Ȯ", IDOK, 8, 164, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "", IDCANCEL, 65, 164, 50, 14, 0, WS_EX_LEFT + EDITTEXT IDC_EDIT1, 60, 7, 55, 14, ES_AUTOHSCROLL, WS_EX_LEFT + LTEXT "ܳ", IDC_STATIC, 7, 7, 47, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT + EDITTEXT IDC_EDIT2, 60, 25, 55, 14, ES_AUTOHSCROLL, WS_EX_LEFT + LTEXT "μ ", IDC_STATIC, 7, 26, 47, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT + EDITTEXT IDC_EDIT3, 60, 45, 55, 14, ES_AUTOHSCROLL, WS_EX_LEFT + LTEXT " ", IDC_STATIC, 7, 45, 47, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT + EDITTEXT IDC_EDIT4, 60, 65, 55, 14, ES_AUTOHSCROLL, WS_EX_LEFT + LTEXT " ", IDC_STATIC, 7, 65, 47, 13, SS_LEFT | SS_CENTERIMAGE, WS_EX_LEFT + AUTOCHECKBOX "ڵ ", IDC_CHECK1, 7, 87, 54, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "ڵ ", IDC_CHECK2, 62, 87, 50, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "Ŭ ڵ ", IDC_CHECK3, 7, 103, 88, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "ڵ ݺ ", IDC_CHECK4, 7, 119, 95, 10, 0, WS_EX_LEFT + AUTOCHECKBOX " ʱȭ", IDC_CHECK6, 7, 149, 81, 8, 0, WS_EX_LEFT + AUTOCHECKBOX "۷ι Ȱȭ", IDC_CHECK5, 7, 134, 75, 10, 0, WS_EX_LEFT } @@ -65,3 +62,21 @@ FONT 8, "MS Shell Dlg", 400, 0, 1 // LANGUAGE LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN IDI_ICON1 ICON "icon1.ico" + + + +// +// Version Information resources +// +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +1 VERSIONINFO + FILEVERSION 0,0,0,0 + PRODUCTVERSION 0,0,0,0 + FILEOS VOS_UNKNOWN + FILETYPE VFT_UNKNOWN + FILESUBTYPE VFT2_UNKNOWN + FILEFLAGSMASK 0 + FILEFLAGS 0 +{ + +} diff --git a/gui/ProcessWindow.cpp b/gui/ProcessWindow.cpp index c4b21b5..b7906c9 100644 --- a/gui/ProcessWindow.cpp +++ b/gui/ProcessWindow.cpp @@ -1,9 +1,9 @@ #include "ProcessWindow.h" #include "resource.h" -#include "ith/host/srv.h" -#include "ith/host/hookman.h" +#include "host/host.h" +#include "host/hookman.h" #include "ProfileManager.h" -#include "Profile.h" +#include "profile/Profile.h" extern HookManager* man; // main.cpp extern ProfileManager* pfman; // ProfileManager.cpp @@ -22,6 +22,8 @@ ProcessWindow::ProcessWindow(HWND hDialog) : hDlg(hDialog) ListView_SetExtendedListViewStyleEx(hlProcess, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT); InitProcessDlg(); RefreshProcess(); + EnableWindow(hbDetach, FALSE); + EnableWindow(hbAttach, FALSE); } void ProcessWindow::InitProcessDlg() @@ -73,30 +75,33 @@ void ProcessWindow::RefreshProcess() void ProcessWindow::AttachProcess() { DWORD pid = GetSelectedPID(); - if (IHF_InjectByPID(pid) != -1) + if (Host_InjectByPID(pid)) + { + Host_HijackProcess(pid); RefreshThreadWithPID(pid, true); + } } void ProcessWindow::DetachProcess() { DWORD pid = GetSelectedPID(); - if (IHF_ActiveDetachProcess(pid) == 0) + if (Host_ActiveDetachProcess(pid) == 0) RefreshThreadWithPID(pid, false); } -void ProcessWindow::AddCurrentToProfile() +void ProcessWindow::CreateProfileForSelectedProcess() { DWORD pid = GetSelectedPID(); auto path = GetProcessPath(pid); if (!path.empty()) { - Profile* pf = pfman->AddProfile(path, pid); - pfman->FindProfileAndUpdateHookAddresses(pid, path); + Profile* pf = pfman->CreateProfile(pid); + pfman->SaveProfiles(); RefreshThread(ListView_GetSelectionMark(hlProcess)); } } -void ProcessWindow::RemoveCurrentFromProfile() +void ProcessWindow::DeleteProfileForSelectedProcess() { DWORD pid = GetSelectedPID(); auto path = GetProcessPath(pid); @@ -132,7 +137,7 @@ void ProcessWindow::RefreshThreadWithPID(DWORD pid, bool isAttached) DWORD ProcessWindow::GetSelectedPID() { - LVITEM item={}; + LVITEM item = {}; item.mask = LVIF_PARAM; item.iItem = ListView_GetSelectionMark(hlProcess); ListView_GetItem(hlProcess, &item); diff --git a/gui/ProcessWindow.h b/gui/ProcessWindow.h index 8b6a546..ffa584e 100644 --- a/gui/ProcessWindow.h +++ b/gui/ProcessWindow.h @@ -1,4 +1,5 @@ #pragma once + #include "ITH.h" class ProcessWindow @@ -9,8 +10,8 @@ public: void RefreshProcess(); void AttachProcess(); void DetachProcess(); - void AddCurrentToProfile(); - void RemoveCurrentFromProfile(); + void CreateProfileForSelectedProcess(); + void DeleteProfileForSelectedProcess(); void RefreshThread(int index); private: void RefreshThreadWithPID(DWORD pid, bool isAttached); diff --git a/gui/ProfileManager.cpp b/gui/ProfileManager.cpp index fa65901..f60d517 100644 --- a/gui/ProfileManager.cpp +++ b/gui/ProfileManager.cpp @@ -1,9 +1,11 @@ #include "ProfileManager.h" -#include "Profile.h" -#include "ith/host/srv.h" -#include "ith/host/hookman.h" -#include "ith/common/types.h" -#include "ith/common/const.h" +#include "profile/Profile.h" +#include "host/host.h" +#include "host/hookman.h" +#include "vnrhook/include/types.h" +#include "vnrhook/include/const.h" +#include "utility.h" +#include "profile/misc.h" extern HookManager* man; // main.cpp extern LONG auto_inject, auto_insert, inject_delay; // main.cpp @@ -12,21 +14,16 @@ bool MonitorFlag; ProfileManager* pfman; DWORD WINAPI MonitorThread(LPVOID lpThreadParameter); -void AddHooksToProfile(Profile& pf, const ProcessRecord& pr); -void AddThreadsToProfile(Profile& pf, const ProcessRecord& pr, DWORD pid); -DWORD AddThreadToProfile(Profile& pf, const ProcessRecord& pr, TextThread& thread); -void MakeHookRelative(const ProcessRecord& pr, HookParam& hp); -std::wstring GetHookNameByAddress(const ProcessRecord& pr, DWORD hook_address); -void GetHookNameToAddressMap(const ProcessRecord& pr, std::map& hookNameToAddress); -ProfileManager::ProfileManager(): +ProfileManager::ProfileManager() : hMonitorThread(IthCreateThread(MonitorThread, 0)) { - LoadProfile(); + LoadProfiles(); } + ProfileManager::~ProfileManager() { - SaveProfile(); + SaveProfiles(); WaitForSingleObject(hMonitorThread.get(), 0); } @@ -42,7 +39,7 @@ Profile* ProfileManager::GetProfile(DWORD pid) return NULL; } -bool ProfileManager::AddProfile(pugi::xml_node game) +bool ProfileManager::CreateProfile(pugi::xml_node game) { auto file = game.child(L"File"); auto profile = game.child(L"Profile"); @@ -56,13 +53,17 @@ bool ProfileManager::AddProfile(pugi::xml_node game) auto pf = new Profile(title); if (!pf->XmlReadProfile(profile)) return false; - AddProfile(path.value(), profile_ptr(pf)); - return true; + CSLock lock(cs); + auto& oldProfile = profile_tree[path.value()]; + if (!oldProfile) + oldProfile.swap(profile_ptr(pf)); + return true; } -Profile* ProfileManager::AddProfile(const std::wstring& path, DWORD pid) +Profile* ProfileManager::CreateProfile(DWORD pid) { CSLock lock(cs); + auto path = GetProcessPath(pid); auto& pf = profile_tree[path]; if (!pf) { @@ -72,15 +73,6 @@ Profile* ProfileManager::AddProfile(const std::wstring& path, DWORD pid) return pf.get(); } -Profile* ProfileManager::AddProfile(const std::wstring& path, profile_ptr new_profile) -{ - CSLock lock(cs); - auto& pf = profile_tree[path]; - if (!pf) - pf.swap(new_profile); - return pf.get(); -} - void ProfileManager::WriteProfileXml(const std::wstring& path, Profile& pf, pugi::xml_node root) { auto game = root.append_child(L"Game"); @@ -96,7 +88,7 @@ void ProfileManager::WriteProfileXml(const std::wstring& path, Profile& pf, pugi } } -void ProfileManager::LoadProfile() +void ProfileManager::LoadProfiles() { pugi::xml_document doc; UniqueHandle hFile(IthCreateFile(L"ITH_Profile.xml", GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING)); @@ -112,10 +104,10 @@ void ProfileManager::LoadProfile() if (!root) return; for (auto game = root.begin(); game != root.end(); ++game) - AddProfile(*game); + CreateProfile(*game); } -void ProfileManager::SaveProfile() +void ProfileManager::SaveProfiles() { pugi::xml_document doc; auto root = doc.append_child(L"ITH_Profile"); @@ -138,44 +130,14 @@ void ProfileManager::DeleteProfile(const std::wstring& path) profile_tree.erase(profile_tree.find(path)); } -void ProfileManager::FindProfileAndUpdateHookAddresses(DWORD pid, const std::wstring& path) +Profile* ProfileManager::GetProfile(const std::wstring& path) { if (path.empty()) - return; + return nullptr; auto it = profile_tree.find(path); if (it == profile_tree.end()) - return; - auto& pf = it->second; - const ProcessRecord* pr = man->GetProcessRecord(pid); - if (pr == NULL) - return; - // hook name -> hook address - std::map hookNameToAddress; - GetHookNameToAddressMap(*pr, hookNameToAddress); - for (auto thread_profile = pf->Threads().begin(); thread_profile != pf->Threads().end(); - ++thread_profile) - { - auto it = hookNameToAddress.find((*thread_profile)->HookName()); - if (it != hookNameToAddress.end()) - (*thread_profile)->HookAddress() = it->second; - } -} - -void GetHookNameToAddressMap(const ProcessRecord& pr, - std::map& hookNameToAddress) -{ - WaitForSingleObject(pr.hookman_mutex, 0); - auto hooks = (const Hook*)pr.hookman_map; - for (DWORD i = 0; i < MAX_HOOK; ++i) - { - if (hooks[i].Address() == 0) - continue; - auto& hook = hooks[i]; - std::unique_ptr name(new WCHAR[hook.NameLength() * 2]); - if (ReadProcessMemory(pr.process_handle, hook.Name(), name.get(), hook.NameLength() * 2, NULL)) - hookNameToAddress[name.get()] = hook.Address(); - } - ReleaseMutex(pr.hookman_mutex); + return nullptr; + return it->second.get(); } bool ProfileManager::HasProfile(const std::wstring& path) @@ -183,7 +145,7 @@ bool ProfileManager::HasProfile(const std::wstring& path) return profile_tree.find(path) != profile_tree.end(); } -DWORD ProfileManager::ProfileCount() +DWORD ProfileManager::CountProfiles() { return profile_tree.size(); } @@ -194,7 +156,7 @@ DWORD WINAPI InjectThread(LPVOID lpThreadParameter) Sleep(inject_delay); if (man == NULL) return 0; - DWORD status = IHF_InjectByPID(pid); + DWORD status = Host_InjectByPID(pid); if (!auto_insert) return status; if (status == -1) @@ -206,7 +168,10 @@ DWORD WINAPI InjectThread(LPVOID lpThreadParameter) SendParam sp; sp.type = 0; for (auto hp = pf->Hooks().begin(); hp != pf->Hooks().end(); ++hp) - IHF_InsertHook(pid, const_cast(&(*hp)->HP()), (*hp)->Name().c_str()); + { + std::string name = toMultiByteString((*hp)->Name()); + Host_InsertHook(pid, const_cast(&(*hp)->HP()), name.c_str()); + } } return status; } @@ -215,7 +180,7 @@ DWORD WINAPI MonitorThread(LPVOID lpThreadParameter) { while (MonitorFlag) { - DWORD aProcesses[1024], cbNeeded, cProcesses; + DWORD aProcesses[1024], cbNeeded, cProcesses; if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)) break; cProcesses = cbNeeded / sizeof(DWORD); @@ -237,116 +202,17 @@ DWORD WINAPI MonitorThread(LPVOID lpThreadParameter) DWORD SaveProcessProfile(DWORD pid) { - const ProcessRecord* pr = man->GetProcessRecord(pid); - if (pr == NULL) - return 0; std::wstring path = GetProcessPath(pid); if (path.empty()) return 0; + pugi::xml_document doc; + pugi::xml_node profile_node = doc.append_child(L"Profile"); + man->GetProfile(pid, profile_node); Profile* pf = pfman->GetProfile(pid); if (pf != NULL) pf->Clear(); else - pf = pfman->AddProfile(path, pid); - AddHooksToProfile(*pf, *pr); - AddThreadsToProfile(*pf, *pr, pid); + pf = pfman->CreateProfile(pid); + pf->XmlReadProfile(profile_node); return 0; } - -void AddHooksToProfile(Profile& pf, const ProcessRecord& pr) -{ - WaitForSingleObject(pr.hookman_mutex, 0); - auto hooks = (const Hook*)pr.hookman_map; - for (DWORD i = 0; i < MAX_HOOK; ++i) - { - if (hooks[i].Address() == 0) - continue; - auto& hook = hooks[i]; - DWORD type = hook.Type(); - if ((type & HOOK_ADDITIONAL) && (type & HOOK_ENGINE) == 0) - { - std::unique_ptr name(new WCHAR[hook.NameLength() * 2]); - if (ReadProcessMemory(pr.process_handle, hook.Name(), name.get(), hook.NameLength() * 2, NULL)) - { - if (hook.hp.module) - { - HookParam hp = hook.hp; - MakeHookRelative(pr, hp); - pf.AddHook(hp, name.get()); - } - else - pf.AddHook(hook.hp, name.get()); - } - } - } - ReleaseMutex(pr.hookman_mutex); -} - -void MakeHookRelative(const ProcessRecord& pr, HookParam& hp) -{ - MEMORY_BASIC_INFORMATION info; - VirtualQueryEx(pr.process_handle, (LPCVOID)hp.addr, &info, sizeof(info)); - hp.addr -= (DWORD)info.AllocationBase; - hp.function = 0; -} - -void AddThreadsToProfile(Profile& pf, const ProcessRecord& pr, DWORD pid) -{ - man->LockHookman(); - ThreadTable* table = man->Table(); - for (int i = 0; i < table->Used(); ++i) - { - TextThread* tt = table->FindThread(i); - if (tt == NULL || tt->GetThreadParameter()->pid != pid) - continue; - //if (tt->Status() & CURRENT_SELECT || tt->Link() || tt->GetComment()) - if (tt->Status() & CURRENT_SELECT || tt->Link()) - AddThreadToProfile(pf, pr, *tt); - } - man->UnlockHookman(); -} - -DWORD AddThreadToProfile(Profile& pf, const ProcessRecord& pr, TextThread& thread) -{ - const ThreadParameter* tp = thread.GetThreadParameter(); - std::wstring hook_name = GetHookNameByAddress(pr, tp->hook); - if (hook_name.empty()) - return -1; - auto thread_profile = new ThreadProfile(hook_name, tp->retn, tp->spl, 0, 0, - THREAD_MASK_RETN | THREAD_MASK_SPLIT, L""); - DWORD threads_size = pf.Threads().size(); - int thread_profile_index = pf.AddThread(thread_ptr(thread_profile)); - if (thread_profile_index == threads_size) // new thread - { - WORD iw = thread_profile_index & 0xFFFF; - if (thread.Status() & CURRENT_SELECT) - pf.SelectedIndex() = iw; - if (thread.Link()) - { - WORD to_index = AddThreadToProfile(pf, pr, *(thread.Link())) & 0xFFFF; - if (iw >= 0) - pf.AddLink(link_ptr(new LinkProfile(iw, to_index))); - } - } - return thread_profile_index; // in case more than one thread links to the same thread. -} - -std::wstring GetHookNameByAddress(const ProcessRecord& pr, DWORD hook_address) -{ - std::wstring hook_name; - WaitForSingleObject(pr.hookman_mutex, 0); - auto hooks = (const Hook*)pr.hookman_map; - for (int i = 0; i < MAX_HOOK; ++i) - { - auto& hook = hooks[i]; - if (hook.Address() == hook_address) - { - std::unique_ptr name(new WCHAR[hook.NameLength() * 2]); - if (ReadProcessMemory(pr.process_handle, hooks[i].Name(), name.get(), hook.NameLength() * 2, NULL)) - hook_name = name.get(); - break; - } - } - ReleaseMutex(pr.hookman_mutex); - return hook_name; -} diff --git a/gui/ProfileManager.h b/gui/ProfileManager.h index 73d5ccb..64ed6d0 100644 --- a/gui/ProfileManager.h +++ b/gui/ProfileManager.h @@ -1,4 +1,5 @@ #pragma once + #include "ITH.h" #include "utility.h" // UniqueHandle, CriticalSection @@ -9,14 +10,14 @@ class ProfileManager public: ProfileManager(); ~ProfileManager(); - Profile* AddProfile(const std::wstring& path, DWORD pid); - void DeleteProfile(const std::wstring& path); - void LoadProfile(); - void SaveProfile(); - void FindProfileAndUpdateHookAddresses(DWORD pid, const std::wstring& path); + Profile* CreateProfile(DWORD pid); + Profile* GetProfile(DWORD pid); + Profile* GetProfile(const std::wstring& path); + void LoadProfiles(); + void SaveProfiles(); + void DeleteProfile(const std::wstring& path); + void UpdateHookAddresses(DWORD pid); bool HasProfile(const std::wstring& path); - Profile* GetProfile(DWORD pid); - DWORD ProfileCount(); private: typedef std::unique_ptr profile_ptr; typedef std::map profile_map; @@ -24,8 +25,8 @@ private: ProfileManager(const ProfileManager&); ProfileManager operator=(const ProfileManager&); - bool AddProfile(pugi::xml_node game); - Profile* AddProfile(const std::wstring& path, profile_ptr new_profile); + DWORD CountProfiles(); + bool CreateProfile(pugi::xml_node game); void WriteProfileXml(const std::wstring& path, Profile& pf, pugi::xml_node doc); // locate profile with executable path profile_map profile_tree; diff --git a/gui/TextBuffer.cpp b/gui/TextBuffer.cpp index 2620743..ad3025c 100644 --- a/gui/TextBuffer.cpp +++ b/gui/TextBuffer.cpp @@ -3,8 +3,8 @@ DWORD WINAPI FlushThread(LPVOID lParam); // window.cpp TextBuffer::TextBuffer(HWND edit) : hThread(IthCreateThread(FlushThread, (DWORD)this)), - hEdit(edit), - running(true) +hEdit(edit), +running(true) { } diff --git a/gui/TextBuffer.h b/gui/TextBuffer.h index b5ec063..20bd63f 100644 --- a/gui/TextBuffer.h +++ b/gui/TextBuffer.h @@ -1,4 +1,5 @@ #pragma once + #include "ITH.h" #include "utility.h" // UniqueHandle, CriticalSection diff --git a/gui/command.cpp b/gui/command.cpp index ad1b31c..79d5fb4 100644 --- a/gui/command.cpp +++ b/gui/command.cpp @@ -16,157 +16,16 @@ */ #include "ITH.h" -#include "ith/host/srv.h" -#include "ith/common/const.h" -#include "ith/common/types.h" +#include "host/host.h" +#include "vnrhook/include/const.h" +#include "vnrhook/include/types.h" #include "language.h" #include "utility.h" +#include "profile/misc.h" extern HookManager* man; extern HWND hwndProcessComboBox; -bool Parse(const std::wstring& cmd, HookParam& hp) -{ - using std::wregex; - using std::regex_search; - // /H[X]{A|B|W|S|Q}[N][data_offset[*drdo]][:sub_offset[*drso]]@addr[:[module[:{name|#ordinal}]]] - wregex rx(L"^X?([ABWSQ])(N)?", wregex::icase); - std::match_results m; - auto start = cmd.begin(); - auto end = cmd.end(); - bool result = regex_search(start, end, m, rx); - if (!result) - return result; - start = m[0].second; - if (m[2].matched) - hp.type |= NO_CONTEXT; - - switch (m[1].first[0]) - { - case L's': - case L'S': - hp.type |= USING_STRING; - break; - case L'e': - case L'E': - hp.type |= STRING_LAST_CHAR; - case L'a': - case L'A': - hp.type |= BIG_ENDIAN; - hp.length_offset = 1; - break; - case L'b': - case L'B': - hp.length_offset = 1; - break; - case L'h': - case L'H': - hp.type |= PRINT_DWORD; - case L'q': - case L'Q': - hp.type |= USING_STRING | USING_UNICODE; - break; - case L'l': - case L'L': - hp.type |= STRING_LAST_CHAR; - case L'w': - case L'W': - hp.type |= USING_UNICODE; - hp.length_offset = 1; - break; - default: - break; - } - - // [data_offset[*drdo]] - std::wstring data_offset(L"(-?[[:xdigit:]]+)"), drdo(L"(\\*-?[[:xdigit:]]+)?"); - rx = wregex(L"^"+ data_offset + drdo, wregex::icase); - result = regex_search(start, end, m, rx); - if (result) - { - start = m[0].second; - hp.off = std::stoul(m[1].str(), NULL, 16); - if (m[2].matched) - { - hp.type |= DATA_INDIRECT; - hp.ind = std::stoul(m[2].str().substr(1), NULL, 16); - } - } - - // [:sub_offset[*drso]] - std::wstring sub_offset(L"(-?[[:xdigit:]]+)"), drso(L"(\\*-?[[:xdigit:]]+)?"); - rx = wregex(L"^:" + sub_offset + drso, wregex::icase); - result = regex_search(start, end, m, rx); - if (result) - { - start = m[0].second; - hp.type |= USING_SPLIT; - hp.split = std::stoul(m[1].str(), NULL, 16); - if (m[2].matched) - { - hp.type |= SPLIT_INDIRECT; - hp.split_ind = std::stoul(m[2].str().substr(1), NULL, 16); - } - } - // @addr - rx = wregex(L"^@[[:xdigit:]]+", wregex::icase); - result = regex_search(start, end, m, rx); - if (!result) - return false; - start = m[0].second; - hp.addr = std::stoul(m[0].str().substr(1), NULL, 16); - if (hp.off & 0x80000000) - hp.off -= 4; - if (hp.split & 0x80000000) - hp.split -= 4; - - // [:[module[:{name|#ordinal}]]] - // ":" -> - // "" -> MODULE_OFFSET && module == NULL && function == addr - // ":GDI.dll" -> MODULE_OFFSET && module != NULL - // ":GDI.dll:strlen" -> MODULE_OFFSET | FUNCTION_OFFSET && module != NULL && function != NULL - // ":GDI.dll:#123" -> MODULE_OFFSET | FUNCTION_OFFSET && module != NULL && function != NULL - std::wstring module(L"([[:graph:]]+)"), name(L"[[:graph:]]+"), ordinal(L"\\d+"); - rx = wregex(L"^:(" + module + L"(:" + name + L"|#" + ordinal + L")?)?$", wregex::icase); - result = regex_search(start, end, m, rx); - if (result) // :[module[:{name|#ordinal}]] - { - if (m[1].matched) // module - { - hp.type |= MODULE_OFFSET; - std::wstring module = m[2]; - std::transform(module.begin(), module.end(), module.begin(), ::towlower); - hp.module = Hash(module); - if (m[3].matched) // :name|#ordinal - { - hp.type |= FUNCTION_OFFSET; - hp.function = Hash(m[3].str().substr(1)); - } - } - } - else - { - rx = wregex(L"^!([[:xdigit:]]+)(!([[:xdigit:]]+))?$", wregex::icase); - result = regex_search(start, end, m, rx); - if (result) - { - hp.type |= MODULE_OFFSET; - hp.module = std::stoul(m[1].str(), NULL, 16); - if (m[2].matched) - { - hp.type |= FUNCTION_OFFSET; - hp.function = std::stoul(m[2].str().substr(1), NULL, 16); - } - } - else - { - hp.type |= MODULE_OFFSET; - hp.function = hp.addr; - } - } - return true; -} - DWORD ProcessCommand(const std::wstring& cmd, DWORD pid) { using std::wregex; @@ -175,40 +34,40 @@ DWORD ProcessCommand(const std::wstring& cmd, DWORD pid) if (regex_match(cmd, m, wregex(L"/pn(.+)", wregex::icase))) { - pid = IHF_GetPIDByName(m[1].str().c_str()); + pid = Host_GetPIDByName(m[1].str().c_str()); if (pid == 0) return 0; - IHF_InjectByPID(pid); + Host_InjectByPID(pid); } else if (regex_match(cmd, m, wregex(L"/p(\\d+)", wregex::icase))) { pid = std::stoul(m[1].str()); - IHF_InjectByPID(pid); + Host_InjectByPID(pid); } - else if (regex_match(cmd, m, wregex (L"/h(.+)", wregex::icase))) + else if (regex_match(cmd, m, wregex(L"/h(.+)", wregex::icase))) { HookParam hp = {}; if (Parse(m[1].str(), hp)) - IHF_InsertHook(pid, &hp); + Host_InsertHook(pid, &hp); } - else if (regex_match(cmd, m, wregex(L"(?::|)(?:ㅇ|연|l|)([[:xdigit:]]+)(?:-| )([[:xdigit:]]+)", wregex::icase))) + else if (regex_match(cmd, m, wregex(L"(?::|)(?:||l|)([[:xdigit:]]+)(?:-| )([[:xdigit:]]+)", wregex::icase))) { DWORD from = std::stoul(m[1].str(), NULL, 16); DWORD to = std::stoul(m[2].str(), NULL, 16); - IHF_AddLink(from, to); + Host_AddLink(from, to); } - else if (regex_match(cmd, m, wregex(L"(?::|)(?:ㅎ|해|해제|u)([[:xdigit:]]+)", wregex::icase))) + else if (regex_match(cmd, m, wregex(L"(?::|)(?:|||u)([[:xdigit:]]+)", wregex::icase))) { DWORD from = std::stoul(m[1].str(), NULL, 16); - IHF_UnLink(from); + Host_UnLink(from); } - else if (regex_match(cmd, m, wregex(L"(?::|)(?:ㄷ|도|도움|도움말|h|help)", wregex::icase))) + else if (regex_match(cmd, m, wregex(L"(?::|)(?:||||h|help)", wregex::icase))) { ConsoleOutput(Usage); } else { - ConsoleOutput(L"알 수 없는 명령어. 도움말을 보시려면, :h 나 :help를 입력하세요."); + ConsoleOutput(L" ɾ. ÷, :h :help Էϼ."); } return 0; } diff --git a/gui/language.cpp b/gui/language.cpp index fa2a292..ee33716 100644 --- a/gui/language.cpp +++ b/gui/language.cpp @@ -14,44 +14,44 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -const wchar_t* Warning=L"경고!"; +const wchar_t* Warning = L"!"; //command.cpp -const wchar_t* ErrorSyntax=L"명령어 오류"; -const wchar_t* Usage = L"명령어:\r\n\ +const wchar_t* ErrorSyntax = L"ɾ "; +const wchar_t* Usage = L"ɾ:\r\n\ \r\n\ -도움말 //도움말을 출력합니다\r\n\ -출발 도착 // '출발'스레드에서 '도착'스레드로 연결합니다\r\n\ -ㅎ출발 // '출발'스레드에 연결된 링크를 해제합니다\r\n\ + // մϴ\r\n\ + // ''忡 '' մϴ\r\n\ + // ''忡 ũ մϴ\r\n\ \r\n\ -'출발'과 '도착'에는 16진법(헥사코드) 스레드번호를 입력합니다. 스레드 번호는 맨 앞에 있는 첫 번째 숫자열입니다.\r\n\ +'' '' 16(ڵ) ȣ Էմϴ. ȣ տ ִ ù ° ڿԴϴ.\r\n\ \r\n\ -로더 옵션:\r\n\ -/P[{process_id|Nprocess_name}] //프로세스에 부착\r\n\ +δ ɼ:\r\n\ +/P[{process_id|Nprocess_name}] //μ \r\n\ \r\n\ -H코드 후킹 옵션:\r\n\ +Hڵ ŷ ɼ:\r\n\ /H[X]{A|B|W|S|Q}[N][data_offset[*drdo]][:sub_offset[*drso]]@addr[:module[:{name|#ordinal}]]\r\n\ \r\n\ -(서수를 제외한) /H코드의 모든 숫자는 아무것도 처리되지 않은 16진법(헥사코드)입니다"; +( ) /Hڵ ڴ ƹ͵ ó 16(ڵ)Դϴ"; const wchar_t* ExtendedUsage = L"/H[X]{A|B|W|S|Q}[N][data_offset[*drdo]][:sub_offset[*drso]]@addr[:[module[:{name|#ordinal}]]]\r\n\ \r\n\ -추가 사용자정의 후킹설정\r\n\ +߰ ŷ\r\n\ \r\n\ -후킹 종류 :\r\n\ -A - DBCS 문자\r\n\ -B - DBCS 문자(big-endian)\r\n\ -W - UCS2 문자\r\n\ -S - MBCS 문자열\r\n\ -Q - UTF-16 문자열\r\n\ +ŷ :\r\n\ +A - DBCS \r\n\ +B - DBCS (big-endian)\r\n\ +W - UCS2 \r\n\ +S - MBCS ڿ\r\n\ +Q - UTF-16 ڿ\r\n\ \r\n\ -매개변수:\r\n\ -X - 하드웨어 구획점 사용\r\n\ -N - 문법을 사용하지 않음\r\n\ +Ű:\r\n\ +X - ϵ ȹ \r\n\ +N - \r\n\ data_offset - stack offset to char / string pointer\r\n\ drdo - add a level of indirection to data_offset\r\n\ sub_offset - stack offset to subcontext\r\n\ drso - add a level of indirection to sub_offset\r\n\ -addr - 후킹할 주소\r\n\ +addr - ŷ ּ\r\n\ module - name of the module to use as base for 'addr'\r\n\ name - name of the 'module' export to use as base for 'addr'\r\n\ ordinal - number of the 'module' export ordinal to use as base for 'addr'\r\n\ @@ -64,70 +64,70 @@ Negative values of 'data_offset' and 'sub_offset' refer to registers: \r\n\ All numbers except ordinal are hexadecimal without any prefixes"; //inject.cpp -const wchar_t* ErrorRemoteThread=L"원격 스레드를 생성할 수 없음."; -const wchar_t* ErrorOpenProcess=L"프로세스를 열 수 없음."; -const wchar_t* ErrorNoProcess=L"프로세스를 찾을 수 없음"; -const wchar_t* SelfAttach=L"ITH.exe에 부착하지 말아 주세요"; -const wchar_t* AlreadyAttach=L"프로세스가 이미 부착됨."; -const wchar_t* FormatInject=L"프로세스 %d에 인젝션. 모듈 기반 %.8X"; +const wchar_t* ErrorRemoteThread = L" 带 ."; +const wchar_t* ErrorOpenProcess = L"μ ."; +const wchar_t* ErrorNoProcess = L"μ ã "; +const wchar_t* SelfAttach = L"ITH.exe ּ"; +const wchar_t* AlreadyAttach = L"μ ̹ ."; +const wchar_t* FormatInject = L"μ %d . %.8X"; //main.cpp -const wchar_t* NotAdmin=L"SeDebugPrevilege을 활성화 할 수 없습니다. ITH가 제대로 작동하지 못합니다.\r\n\ -관리자 계정으로 실행하시거나 UAC를 끄시고 ITH를 실행해 주세요."; +const wchar_t* NotAdmin = L"SeDebugPrevilege Ȱȭ ϴ. ITH ۵ մϴ.\r\n\ + Ͻðų UAC ð ITH ּ."; //pipe.cpp -const wchar_t* ErrorCreatePipe=L"텍스트 파이프를 생성할 수 없거나, 요청이 너무 많습니다."; -const wchar_t* FormatDetach=L"프로세스 %d가 탈착됨."; -const wchar_t* ErrorCmdQueueFull=L"명령어 대기열이 가득참."; -const wchar_t* ErrorNoAttach=L"프로세스가 부착되지 않음."; +const wchar_t* ErrorCreatePipe = L"ؽƮ ų, û ʹ ϴ."; +const wchar_t* FormatDetach = L"μ %d Ż."; +const wchar_t* ErrorCmdQueueFull = L"ɾ ⿭ ."; +const wchar_t* ErrorNoAttach = L"μ ."; //profile.cpp -const wchar_t* ErrorMonitor=L"프로세스를 감시할 수 없음."; +const wchar_t* ErrorMonitor = L"μ ."; //utility.cpp -const wchar_t* InitMessage=L"Copyright (C) 2010-2012 kaosu \r\n\ +const wchar_t* InitMessage = L"Copyright (C) 2010-2012 kaosu \r\n\ Copyright (C) 2015 zorkzero \r\n\ -소스코드 \r\n\ -일반토론 \r\n\ -한글화 @mireado"; -const wchar_t* BackgroundMsg=L"도움말을 보시려면, \"help\", \"도움말\"이나 \"도움\"을 입력하세요."; -const wchar_t* ErrorLinkExist=L"연결이 존재함."; -const wchar_t* ErrorCylicLink=L"연결실패. 순환연결은 허용되지 않습니다."; -const wchar_t* FormatLink=L"출발스레드%.4x에서 도착스레드%.4x로 연결."; -const wchar_t* ErrorLink=L"연결실패. 출발/도착 스레드를 찾을 수 없음."; -const wchar_t* ErrorDeleteCombo=L"글상자에서 지우기 실패."; +ҽڵ \r\n\ +Ϲ \r\n\ +ѱȭ @mireado"; +const wchar_t* BackgroundMsg = L" ÷, \"help\", \"\"̳ \"\" Էϼ."; +const wchar_t* ErrorLinkExist = L" ."; +const wchar_t* ErrorCylicLink = L". ȯ ʽϴ."; +const wchar_t* FormatLink = L"߽%.4x %.4x ."; +const wchar_t* ErrorLink = L". / 带 ã ."; +const wchar_t* ErrorDeleteCombo = L"ۻڿ ."; //window.cpp -const wchar_t* ClassName=L"ITH"; -const wchar_t* ClassNameAdmin=L"ITH (관리자)"; -const wchar_t* ErrorNotSplit=L"먼저 문단 나누기를 활성화해주세요!"; -const wchar_t* ErrorNotModule=L"먼저 모듈을 활성화해주세요!"; +const wchar_t* ClassName = L"ITH"; +const wchar_t* ClassNameAdmin = L"ITH ()"; +const wchar_t* ErrorNotSplit = L" ⸦ Ȱȭּ!"; +const wchar_t* ErrorNotModule = L" Ȱȭּ!"; //Main window buttons -const wchar_t* ButtonTitleProcess=L"프로세스"; -const wchar_t* ButtonTitleThread=L"스레드"; -const wchar_t* ButtonTitleHook=L"후킹"; -const wchar_t* ButtonTitleProfile=L"프로필"; -const wchar_t* ButtonTitleOption=L"옵션"; -const wchar_t* ButtonTitleClear=L"지우기"; -const wchar_t* ButtonTitleSave=L"저장"; -const wchar_t* ButtonTitleTop=L"항상위"; +const wchar_t* ButtonTitleProcess = L"μ"; +const wchar_t* ButtonTitleThread = L""; +const wchar_t* ButtonTitleHook = L"ŷ"; +const wchar_t* ButtonTitleProfile = L""; +const wchar_t* ButtonTitleOption = L"ɼ"; +const wchar_t* ButtonTitleClear = L""; +const wchar_t* ButtonTitleSave = L""; +const wchar_t* ButtonTitleTop = L"׻"; //Hook window -const wchar_t* SpecialHook=L"H코드 후킹, AGTH 코드는 지원하지 않습니다."; +const wchar_t* SpecialHook = L"Hڵ ŷ, AGTH ڵ ʽϴ."; //Process window -const wchar_t* TabTitlePID=L"PID"; -const wchar_t* TabTitleMemory=L"메모리"; -const wchar_t* TabTitleName=L"이름"; -const wchar_t* TabTitleTID=L"TID"; -const wchar_t* TabTitleStart=L"시작"; -const wchar_t* TabTitleModule=L"모듈"; -const wchar_t* TabTitleState=L"상태"; -const wchar_t* SuccessAttach=L"프로세스에 ITH 부착성공."; -const wchar_t* FailAttach=L"프로세스에 ITH 부착실패."; -const wchar_t* SuccessDetach=L"프로세스에서 ITH 탈착성공."; -const wchar_t* FailDetach=L"ITH 탈착실패."; +const wchar_t* TabTitlePID = L"PID"; +const wchar_t* TabTitleMemory = L"޸"; +const wchar_t* TabTitleName = L"̸"; +const wchar_t* TabTitleTID = L"TID"; +const wchar_t* TabTitleStart = L""; +const wchar_t* TabTitleModule = L""; +const wchar_t* TabTitleState = L""; +const wchar_t* SuccessAttach = L"μ ITH ."; +const wchar_t* FailAttach = L"μ ITH ."; +const wchar_t* SuccessDetach = L"μ ITH Ż."; +const wchar_t* FailDetach = L"ITH Ż."; //Profile window -const wchar_t* ProfileExist=L"프로필이 이미 존재함."; -const wchar_t* SuccessAddProfile=L"프로필 추가됨."; -const wchar_t* FailAddProfile=L"프로필 추가실패"; -const wchar_t* TabTitleNumber=L"No."; -const wchar_t* NoFile=L"파일을 찾을 수 없음."; -const wchar_t* PathDismatch=L"프로세스 이름이 일치하지 않습니다, 계속하시겠습니까?"; -const wchar_t* SuccessImportProfile=L"프로필 가져오기 성공"; -//const wchar_t* SuccessAddProfile=L"Profile added."; +const wchar_t* ProfileExist = L" ̹ ."; +const wchar_t* SuccessAddProfile = L" ߰."; +const wchar_t* FailAddProfile = L" ߰"; +const wchar_t* TabTitleNumber = L"No."; +const wchar_t* NoFile = L" ã ."; +const wchar_t* PathDismatch = L"μ ̸ ġ ʽϴ, Ͻðڽϱ?"; +const wchar_t* SuccessImportProfile = L" "; +//const wchar_t* SuccessAddProfile=L"Profile added."; \ No newline at end of file diff --git a/gui/main.cpp b/gui/main.cpp index 5e4ff57..d48fd46 100644 --- a/gui/main.cpp +++ b/gui/main.cpp @@ -16,11 +16,11 @@ */ #include "ITH.h" -#include "ith/host/srv.h" -#include "ith/host/hookman.h" -#include "ith/host/SettingManager.h" +#include "host/host.h" +#include "host/hookman.h" +#include "host/settings.h" #include "CustomFilter.h" -#include "profile.h" +#include "profile/Profile.h" #include "ProfileManager.h" HINSTANCE hIns; @@ -39,10 +39,10 @@ extern "C" { CustomFilter* uni_filter; CustomFilter* mb_filter; HookManager* man; -SettingManager* setman; +Settings* setman; LONG split_time, cyclic_remove, global_filter; LONG process_time, inject_delay, insert_delay, - auto_inject, auto_insert, clipboard_flag; +auto_inject, auto_insert, clipboard_flag; std::map setting; @@ -69,11 +69,13 @@ void RecordUniChar(WORD uni, PVOID f) void SaveSettings() { - GetWindowRect(hMainWnd, &window); - setting[L"window_left"] = window.left; - setting[L"window_right"] = window.right; - setting[L"window_top"] = window.top; - setting[L"window_bottom"] = window.bottom; + WINDOWPLACEMENT wndpl; + wndpl.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(hMainWnd, &wndpl); + setting[L"window_left"] = wndpl.rcNormalPosition.left; + setting[L"window_right"] = wndpl.rcNormalPosition.right; + setting[L"window_top"] = wndpl.rcNormalPosition.top; + setting[L"window_bottom"] = wndpl.rcNormalPosition.bottom; setting[L"split_time"] = split_time; setting[L"process_time"] = process_time; setting[L"inject_delay"] = inject_delay; @@ -238,17 +240,18 @@ LONG WINAPI UnhandledExcept(_EXCEPTION_POINTERS *ExceptionInfo) return 0; } -int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { + InitCommonControls(); if (!IthInitSystemService()) TerminateProcess(GetCurrentProcess(), 0); CreateMutex(NULL, TRUE, L"ITH_MAIN_RUNNING"); - if (IHF_Init()) + if (Host_Open()) { SetUnhandledExceptionFilter(UnhandledExcept); - IHF_GetHookManager(&man); - IHF_GetSettingManager(&setman); - setman->SetValue(SETTING_SPLIT_TIME, 200); + Host_GetHookManager(&man); + Host_GetSettings(&setman); + setman->splittingInterval = 200; MonitorFlag = true; pfman = new ProfileManager(); mb_filter = new CustomFilter(); @@ -256,11 +259,11 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLin DefaultSettings(); LoadSettings(); InitializeSettings(); - setman->SetValue(SETTING_SPLIT_TIME, split_time); - setman->SetValue(SETTING_CLIPFLAG, clipboard_flag); + setman->splittingInterval = split_time; + setman->clipboardFlag = clipboard_flag > 0; hIns = hInstance; MyRegisterClass(hIns); - InitInstance(hIns, IHF_IsAdmin(), &window); + InitInstance(hIns, FALSE, &window); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { @@ -277,7 +280,7 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLin { FindITH(); } - IHF_Cleanup(); + Host_Close(); IthCloseSystemService(); TerminateProcess(GetCurrentProcess(), 0); } diff --git a/gui/utility.cpp b/gui/utility.cpp index 4f2b4d1..2c47320 100644 --- a/gui/utility.cpp +++ b/gui/utility.cpp @@ -16,10 +16,11 @@ */ #include "utility.h" -#include "ith/host/srv.h" -#include "ith/host/hookman.h" -#include "ith/common/types.h" -#include "ith/common/const.h" +#include "host/host.h" +#include "host/hookman.h" +#include "vnrhook/include/types.h" +#include "vnrhook/include/const.h" +#include "profile/misc.h" extern HookManager* man; // main.cpp @@ -65,11 +66,11 @@ std::wstring GetWindowsPath(const std::wstring& path) // path is in device form // \Device\HarddiskVolume2\Windows\System32\taskhost.exe auto pathOffset = path.find(L'\\', 1) + 1; - pathOffset = path.find(L'\\', pathOffset); + pathOffset = path.find(L'\\', pathOffset); std::wstring devicePath = path.substr(0, pathOffset); // \Device\HarddiskVolume2 std::wstring dosDrive = GetDriveLetter(devicePath); // C: if (dosDrive.empty()) - return L""; + return path; std::wstring dosPath = dosDrive; // C: dosPath += path.substr(pathOffset); // C:\Windows\System32\taskhost.exe return dosPath; @@ -117,16 +118,16 @@ std::wstring GetCode(const HookParam& hp, DWORD pid) code += c; if (hp.type & NO_CONTEXT) code += L'N'; - if (hp.off >> 31) - code += L"-" + ToHexString(-(hp.off + 4)); + if (hp.offset >> 31) + code += L"-" + ToHexString(-(hp.offset + 4)); else - code += ToHexString(hp.off); + code += ToHexString(hp.offset); if (hp.type & DATA_INDIRECT) { - if (hp.ind >> 31) - code += L"*-" + ToHexString(-hp.ind); + if (hp.index >> 31) + code += L"*-" + ToHexString(-hp.index); else - code += L"*" + ToHexString(hp.ind); + code += L"*" + ToHexString(hp.index); } if (hp.type & USING_SPLIT) { @@ -137,21 +138,21 @@ std::wstring GetCode(const HookParam& hp, DWORD pid) } if (hp.type & SPLIT_INDIRECT) { - if (hp.split_ind >> 31) - code += L"*-" + ToHexString(-hp.split_ind); + if (hp.split_index >> 31) + code += L"*-" + ToHexString(-hp.split_index); else - code += L"*" + ToHexString(hp.split_ind); + code += L"*" + ToHexString(hp.split_index); } if (pid) { - PVOID allocationBase = GetAllocationBase(pid, (LPCVOID)hp.addr); + PVOID allocationBase = GetAllocationBase(pid, (LPCVOID)hp.address); if (allocationBase) { std::wstring path = GetModuleFileNameAsString(pid, allocationBase); if (!path.empty()) { auto fileName = path.substr(path.rfind(L'\\') + 1); - DWORD relativeHookAddress = hp.addr - (DWORD)allocationBase; + DWORD relativeHookAddress = hp.address - (DWORD)allocationBase; code += L"@" + ToHexString(relativeHookAddress) + L":" + fileName; return code; } @@ -159,20 +160,20 @@ std::wstring GetCode(const HookParam& hp, DWORD pid) } if (hp.module) { - code += L"@" + ToHexString(hp.addr) + L"!" + ToHexString(hp.module); + code += L"@" + ToHexString(hp.address) + L"!" + ToHexString(hp.module); if (hp.function) code += L"!" + ToHexString(hp.function); } else { - // hack, the original address is stored in the function field - // if (module == NULL && function != NULL) - // in TextHook::UnsafeInsertHookCode() MODULE_OFFSET and FUNCTION_OFFSET are removed from - // HookParam.type + // Hack. The original address is stored in the function field + // if (module == NULL && function != NULL). + // MODULE_OFFSET and FUNCTION_OFFSET are removed from HookParam.type in + // TextHook::UnsafeInsertHookCode() and can not be used here. if (hp.function) code += L"@" + ToHexString(hp.function); else - code += L"@" + ToHexString(hp.addr) + L":"; + code += L"@" + ToHexString(hp.address) + L":"; } return code; } @@ -282,13 +283,13 @@ HANDLE IthCreateFile(LPCWSTR name, DWORD option, DWORD share, DWORD disposition) return CreateFile(path.c_str(), option, share, NULL, disposition, FILE_ATTRIBUTE_NORMAL, NULL); } -//SJIS->Unicode. 'mb' must be null-terminated. 'wc_length' is the length of 'wc' in characters. +//SJIS->Unicode. mb must be null-terminated. wc_length is the length of wc in characters. int MB_WC(const char* mb, wchar_t* wc, int wc_length) { return MultiByteToWideChar(932, 0, mb, -1, wc, wc_length); } -// Count characters in wide string. 'mb_length' is the number of bytes from 'mb' to convert or +// Count characters in wide string. mb_length is the number of bytes from mb to convert or // -1 if the string is null terminated. int MB_WC_count(const char* mb, int mb_length) { @@ -300,12 +301,3 @@ int WC_MB(const wchar_t *wc, char* mb, int mb_length) { return WideCharToMultiByte(932, 0, wc, -1, mb, mb_length, NULL, NULL); } - -DWORD Hash(const std::wstring& module, int length) -{ - DWORD hash = 0; - auto end = length < 0 || static_cast(length) > module.length() ? module.end() : module.begin() + length; - for (auto it = module.begin(); it != end; ++it) - hash = _rotr(hash, 7) + *it; - return hash; -} diff --git a/gui/utility.h b/gui/utility.h index a899d0f..5c8a065 100644 --- a/gui/utility.h +++ b/gui/utility.h @@ -1,10 +1,10 @@ #pragma once + #include "ITH.h" struct HookParam; struct ProcessRecord; -DWORD Hash(const std::wstring& module, int length = -1); DWORD ProcessCommand(const std::wstring& cmd, DWORD pid); std::wstring GetProcessPath(DWORD pid); void ConsoleOutput(LPCWSTR); @@ -58,13 +58,6 @@ int MB_WC_count(const char* mb, int mb_length); int WC_MB(const wchar_t *wc, char* mb, int mb_length); bool Parse(const std::wstring& cmd, HookParam& hp); -template -std::wstring ToHexString(T i) { - std::wstringstream ss; - ss << std::uppercase << std::hex << i; - return ss.str(); -} - // http://jrdodds.blogs.com/blog/2004/08/raii_in_c.html class CriticalSection { diff --git a/gui/window.cpp b/gui/window.cpp index 4e793d2..df24e62 100644 --- a/gui/window.cpp +++ b/gui/window.cpp @@ -18,15 +18,16 @@ #include "ProcessWindow.h" #include "resource.h" #include "language.h" -#include "ith/host/srv.h" -#include "ith/host/hookman.h" -#include "ith/common/const.h" +#include "host/host.h" +#include "host/hookman.h" +#include "vnrhook/include/const.h" #include "version.h" #include "ProfileManager.h" -#include "ith/host/SettingManager.h" +#include "host/settings.h" #include "CustomFilter.h" -#include "Profile.h" +#include "profile/Profile.h" #include "TextBuffer.h" +#include "profile/misc.h" #define CMD_SIZE 512 @@ -46,39 +47,38 @@ extern ProfileManager* pfman; // ProfileManager.cpp extern HookManager* man; // main.cpp extern CustomFilter* mb_filter; // main.cpp extern CustomFilter* uni_filter; // main.cpp -extern SettingManager* setman; // main.cpp +extern Settings* setman; // main.cpp #define COMMENT_BUFFER_LENGTH 512 static WCHAR comment_buffer[COMMENT_BUFFER_LENGTH]; LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); void SaveSettings(); // main.cpp extern LONG split_time, process_time, inject_delay, insert_delay, - auto_inject, auto_insert, clipboard_flag, cyclic_remove, global_filter; //main.cpp +auto_inject, auto_insert, clipboard_flag, cyclic_remove, global_filter; //main.cpp static int last_select, last_edit; -void AddLinksToHookManager(const Profile& pf, size_t thread_profile_index, const TextThread& thread); ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); - wcex.style = CS_HREDRAW | CS_VREDRAW; - wcex.lpfnWndProc = WndProc; - wcex.cbClsExtra = 0; - wcex.cbWndExtra = 0; - wcex.hInstance = hInstance; - wcex.hIcon = NULL; - wcex.hCursor = NULL; - wcex.hbrBackground = GetStockBrush(WHITE_BRUSH); - wcex.lpszMenuName = NULL; - wcex.lpszClassName = ClassName; - wcex.hIconSm = LoadIcon(hInstance, (LPWSTR)IDI_ICON1); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = NULL; + wcex.hCursor = NULL; + wcex.hbrBackground = GetStockBrush(WHITE_BRUSH); + wcex.lpszMenuName = NULL; + wcex.lpszClassName = ClassName; + wcex.hIconSm = LoadIcon(hInstance, (LPWSTR)IDI_ICON1); return RegisterClassEx(&wcex); } BOOL InitInstance(HINSTANCE hInstance, DWORD nAdmin, RECT* rc) { hIns = hInstance; - LPCWSTR name = (nAdmin) ? ClassNameAdmin : ClassName; + LPCWSTR name = (nAdmin) ? ClassNameAdmin : ClassName; hMainWnd = CreateWindow(ClassName, name, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, rc->left, rc->top, rc->right - rc->left, rc->bottom - rc->top, NULL, NULL, hInstance, 0); if (!hMainWnd) @@ -137,8 +137,8 @@ BOOL CALLBACK OptionDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) clipboard_flag = IsDlgButtonChecked(hDlg, IDC_CHECK3); cyclic_remove = IsDlgButtonChecked(hDlg, IDC_CHECK4); global_filter = IsDlgButtonChecked(hDlg, IDC_CHECK5); - setman->SetValue(SETTING_CLIPFLAG, clipboard_flag); - setman->SetValue(SETTING_SPLIT_TIME, split_time); + setman->clipboardFlag = clipboard_flag; + setman->splittingInterval = split_time; if (auto_inject == 0) auto_insert = 0; } case IDCANCEL: @@ -187,15 +187,15 @@ BOOL CALLBACK ProcessDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) pswnd->DetachProcess(); break; case IDC_BUTTON5: - pswnd->AddCurrentToProfile(); + pswnd->CreateProfileForSelectedProcess(); break; case IDC_BUTTON6: - pswnd->RemoveCurrentFromProfile(); + pswnd->DeleteProfileForSelectedProcess(); break; } } return TRUE; - + case WM_NOTIFY: { LPNMHDR dr = (LPNMHDR)lParam; @@ -223,22 +223,22 @@ LRESULT CALLBACK EditProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) switch (message) { case WM_CHAR: //Filter user input. - if (GetKeyState(VK_CONTROL) & 0x8000) - { - if (wParam == 1) - { - Edit_SetSel(hwndEdit, 0, -1); - SendMessage(hwndEdit, WM_COPY, 0, 0); - } - } - return 0; - case WM_LBUTTONUP: - if (hwndEdit) - SendMessage(hwndEdit, WM_COPY, 0, 0); - default: + if (GetKeyState(VK_CONTROL) & 0x8000) { - return proc(hWnd, message, wParam, lParam); + if (wParam == 1) + { + Edit_SetSel(hwndEdit, 0, -1); + SendMessage(hwndEdit, WM_COPY, 0, 0); + } } + return 0; + case WM_LBUTTONUP: + if (hwndEdit) + SendMessage(hwndEdit, WM_COPY, 0, 0); + default: + { + return proc(hWnd, message, wParam, lParam); + } } } @@ -280,29 +280,29 @@ LRESULT CALLBACK EditCmdProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPar void CreateButtons(HWND hWnd) { - hwndProcess = CreateWindow(L"Button", L"프로세스", WS_CHILD | WS_VISIBLE, + hwndProcess = CreateWindow(L"Button", L"μ", WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hWnd, 0, hIns, NULL); - hwndOption = CreateWindow(L"Button", L"옵션", WS_CHILD | WS_VISIBLE, + hwndOption = CreateWindow(L"Button", L"ɼ", WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hWnd, 0, hIns, NULL); - hwndClear = CreateWindow(L"Button", L"지우기", WS_CHILD | WS_VISIBLE, + hwndClear = CreateWindow(L"Button", L"", WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hWnd, 0, hIns, NULL); - hwndSave = CreateWindow(L"Button", L"저장", WS_CHILD | WS_VISIBLE, + hwndSave = CreateWindow(L"Button", L"", WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hWnd, 0, hIns, NULL); - hwndRemoveLink = CreateWindow(L"Button", L"링크해제", WS_CHILD | WS_VISIBLE, + hwndRemoveLink = CreateWindow(L"Button", L"ũ", WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hWnd, 0, hIns, NULL); - hwndRemoveHook = CreateWindow(L"Button", L"후킹해제", WS_CHILD | WS_VISIBLE, + hwndRemoveHook = CreateWindow(L"Button", L"ŷ", WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hWnd, 0, hIns, NULL); - hwndTop = CreateWindow(L"Button", L"항상위", WS_CHILD | WS_VISIBLE | BS_PUSHLIKE | BS_CHECKBOX, + hwndTop = CreateWindow(L"Button", L"׻", WS_CHILD | WS_VISIBLE | BS_PUSHLIKE | BS_CHECKBOX, 0, 0, 0, 0, hWnd, 0, hIns, NULL); hwndProcessComboBox = CreateWindow(L"ComboBox", NULL, WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP, 0, 0, 0, 0, hWnd, 0, hIns, NULL); hwndCmd = CreateWindowEx(WS_EX_CLIENTEDGE, L"Edit", NULL, - WS_CHILD | WS_VISIBLE | ES_NOHIDESEL| ES_LEFT | ES_AUTOHSCROLL, + WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT | ES_AUTOHSCROLL, 0, 0, 0, 0, hWnd, 0, hIns, NULL); hwndEdit = CreateWindowEx(WS_EX_CLIENTEDGE, L"Edit", NULL, - WS_CHILD | WS_VISIBLE | ES_NOHIDESEL| WS_VSCROLL | + WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | WS_VSCROLL | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, 0, hIns, NULL); } @@ -336,7 +336,7 @@ void ClickButton(HWND hWnd, HWND h) } else if (h == hwndTop) { - if (Button_GetCheck(h)==BST_CHECKED) + if (Button_GetCheck(h) == BST_CHECKED) { Button_SetCheck(h, BST_UNCHECKED); SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); @@ -363,7 +363,7 @@ void ClickButton(HWND hWnd, HWND h) DWORD pid = std::stoul(str); SaveProcessProfile(pid); } - pfman->SaveProfile(); + pfman->SaveProfiles(); } else if (h == hwndRemoveLink) { @@ -372,7 +372,7 @@ void ClickButton(HWND hWnd, HWND h) { DWORD from = std::stoul(str, NULL, 16); if (from != 0) - IHF_UnLink(from); + Host_UnLink(from); } } else if (h == hwndRemoveHook) @@ -388,12 +388,12 @@ void ClickButton(HWND hWnd, HWND h) entry = entry.substr(i + 1); DWORD addr = std::stoul(entry, NULL, 16); if (threadNumber != 0) - IHF_RemoveHook(pid, addr); + Host_RemoveHook(pid, addr); } } } -DWORD ThreadFilter(TextThread* thread, BYTE* out,DWORD len, DWORD new_line, PVOID data, bool space) +DWORD ThreadFilter(TextThread* thread, BYTE* out, DWORD len, DWORD new_line, PVOID data, bool space) { DWORD status = thread->Status(); if (global_filter && !new_line && thread->Number() != 0) @@ -444,7 +444,7 @@ DWORD ThreadFilter(TextThread* thread, BYTE* out,DWORD len, DWORD new_line, PVOI return len; } -DWORD ThreadOutput(TextThread* thread, BYTE* out,DWORD len, DWORD new_line, PVOID data, bool space) +DWORD ThreadOutput(TextThread* thread, BYTE* out, DWORD len, DWORD new_line, PVOID data, bool space) { if (len == 0) return len; @@ -498,11 +498,16 @@ bool GetHookParam(DWORD pid, DWORD hook_addr, HookParam& hp) return result; } -void AddToCombo(TextThread& thread, bool replace) +std::wstring GetEntryString(TextThread& thread) { - WCHAR entry[512]; + CHAR entry[512]; thread.GetEntryString(entry, 512); - std::wstring entryWithLink(entry); + return toUnicodeString(entry); +} + +std::wstring CreateEntryWithLink(TextThread& thread, std::wstring& entry) +{ + std::wstring entryWithLink = entry; if (thread.Link()) entryWithLink += L"->" + ToHexString(thread.LinkNumber()); if (thread.PID() == 0) @@ -510,7 +515,14 @@ void AddToCombo(TextThread& thread, bool replace) HookParam hp = {}; if (GetHookParam(thread.PID(), thread.Addr(), hp)) entryWithLink += L" (" + GetCode(hp, thread.PID()) + L")"; - int i = ComboBox_FindString(hwndCombo, 0, entry); + return entryWithLink; +} + +void AddToCombo(TextThread& thread, bool replace) +{ + std::wstring entry = GetEntryString(thread); + std::wstring entryWithLink = CreateEntryWithLink(thread, entry); + int i = ComboBox_FindString(hwndCombo, -1, entry.c_str()); if (replace) { int sel = ComboBox_GetCurSel(hwndCombo); @@ -531,11 +543,12 @@ void AddToCombo(TextThread& thread, bool replace) void RemoveFromCombo(TextThread* thread) { - WCHAR entry[512]; + CHAR entry[512]; thread->GetEntryString(entry, 512); + std::wstring unicodeEntry = toUnicodeString(entry); if (thread->PID() == 0) - std::wcscat(entry, L"ConsoleOutput"); - int i = ComboBox_FindString(hwndCombo, 0, entry); + unicodeEntry += L"ConsoleOutput"; + int i = ComboBox_FindString(hwndCombo, 0, unicodeEntry.c_str()); if (i != CB_ERR) { if (ComboBox_DeleteString(hwndCombo, i) == CB_ERR) @@ -595,6 +608,36 @@ DWORD AddRemoveLink(TextThread* thread) return 0; } +bool IsUnicodeHook(const ProcessRecord& pr, DWORD hook); +void AddLinksToHookManager(const Profile* pf, size_t thread_index, const TextThread* thread); + +DWORD ThreadCreate(TextThread* thread) +{ + thread->RegisterOutputCallBack(ThreadOutput, 0); + thread->RegisterFilterCallBack(ThreadFilter, 0); + AddToCombo(*thread, false); + const auto& tp = thread->GetThreadParameter(); + auto pr = man->GetProcessRecord(tp->pid); + if (pr == NULL) + return 0; + if (IsUnicodeHook(*pr, tp->hook)) + thread->Status() |= USING_UNICODE; + auto pf = pfman->GetProfile(tp->pid); + if (!pf) + return 0; + const std::wstring& hook_name = GetHookNameByAddress(*pr, thread->GetThreadParameter()->hook); + auto thread_profile = pf->FindThread(thread->GetThreadParameter(), hook_name); + if (thread_profile != pf->Threads().end()) + { + (*thread_profile)->HookManagerIndex() = thread->Number(); + auto thread_index = thread_profile - pf->Threads().begin(); + AddLinksToHookManager(pf, thread_index, thread); + if (pf->IsThreadSelected(thread_profile)) + ThreadReset(thread); + } + return 0; +} + bool IsUnicodeHook(const ProcessRecord& pr, DWORD hook) { bool res = false; @@ -612,50 +655,21 @@ bool IsUnicodeHook(const ProcessRecord& pr, DWORD hook) return res; } -DWORD ThreadCreate(TextThread* thread) +void AddLinksToHookManager(const Profile* pf, size_t thread_index, const TextThread* thread) { - thread->RegisterOutputCallBack(ThreadOutput, 0); - thread->RegisterFilterCallBack(ThreadFilter, 0); - AddToCombo(*thread, false); - const auto tp = thread->GetThreadParameter(); - auto pr = man->GetProcessRecord(tp->pid); - if (pr != NULL) + for (auto lp = pf->Links().begin(); lp != pf->Links().end(); ++lp) { - if (IsUnicodeHook(*pr, tp->hook)) - thread->Status() |= USING_UNICODE; - } - - auto pf = pfman->GetProfile(tp->pid); - if (pf) - { - auto thread_profile = pf->FindThreadProfile(*tp); - if (thread_profile != pf->Threads().end()) + if ((*lp)->FromIndex() == thread_index) { - (*thread_profile)->HookManagerIndex() = thread->Number(); - auto thread_profile_index = thread_profile - pf->Threads().begin(); - AddLinksToHookManager(*pf, thread_profile_index, *thread); - if (pf->SelectedIndex() == thread_profile_index) - ThreadReset(thread); - } - } - return 0; -} - -void AddLinksToHookManager(const Profile& pf, size_t thread_profile_index, const TextThread& thread) -{ - for (auto lp = pf.Links().begin(); lp != pf.Links().end(); ++lp) - { - if ((*lp)->FromIndex() == thread_profile_index) - { - WORD to_index = pf.Threads()[(*lp)->ToIndex()]->HookManagerIndex(); + WORD to_index = pf->Threads()[(*lp)->ToIndex()]->HookManagerIndex(); if (to_index != 0) - man->AddLink(thread.Number(), to_index); + man->AddLink(thread->Number(), to_index); } - if ((*lp)->ToIndex() == thread_profile_index) + if ((*lp)->ToIndex() == thread_index) { - WORD from_index = pf.Threads()[(*lp)->FromIndex()]->HookManagerIndex(); + WORD from_index = pf->Threads()[(*lp)->FromIndex()]->HookManagerIndex(); if (from_index != 0) - man->AddLink(from_index, thread.Number()); + man->AddLink(from_index, thread->Number()); } } } @@ -663,14 +677,6 @@ void AddLinksToHookManager(const Profile& pf, size_t thread_profile_index, const DWORD ThreadRemove(TextThread* thread) { RemoveFromCombo(thread); - const auto tp = thread->GetThreadParameter(); - auto pf = pfman->GetProfile(tp->pid); - if (pf) - { - auto thread_profile = pf->FindThreadProfile(*tp); - if (thread_profile != pf->Threads().end()) - (*thread_profile)->HookManagerIndex() = 0; // reset hookman index number - } return 0; } @@ -684,7 +690,6 @@ DWORD RegisterProcessList(DWORD pid) ComboBox_AddString(hwndProcessComboBox, str); if (ComboBox_GetCount(hwndProcessComboBox) == 1) ComboBox_SetCurSel(hwndProcessComboBox, 0); - pfman->FindProfileAndUpdateHookAddresses(pid, path); } return 0; } @@ -706,9 +711,6 @@ DWORD RemoveProcessList(DWORD pid) DWORD RefreshProfileOnNewHook(DWORD pid) { - auto path = GetProcessPath(pid); - if (!path.empty()) - pfman->FindProfileAndUpdateHookAddresses(pid, path); return 0; } @@ -716,136 +718,138 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { - case WM_CREATE: - CreateButtons(hWnd); - // Add text to the window. - Edit_LimitText(hwndEdit, -1); - SendMessage(hwndEdit, WM_INPUTLANGCHANGEREQUEST, 0, 0x411); - proc = (WNDPROC)SetWindowLong(hwndEdit, GWL_WNDPROC, (LONG)EditProc); - proccmd = (WNDPROC)SetWindowLong(hwndCmd, GWL_WNDPROC, (LONG)EditCmdProc); - hwndCombo = CreateWindow(L"ComboBox", NULL, - WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | - CBS_SORT | WS_VSCROLL | WS_TABSTOP, - 0, 0, 0, 0, hWnd, 0, hIns, NULL); + case WM_CREATE: + CreateButtons(hWnd); + // Add text to the window. + Edit_LimitText(hwndEdit, -1); + SendMessage(hwndEdit, WM_INPUTLANGCHANGEREQUEST, 0, 0x411); + proc = (WNDPROC)SetWindowLong(hwndEdit, GWL_WNDPROC, (LONG)EditProc); + proccmd = (WNDPROC)SetWindowLong(hwndCmd, GWL_WNDPROC, (LONG)EditCmdProc); + hwndCombo = CreateWindow(L"ComboBox", NULL, + WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | + CBS_SORT | WS_VSCROLL | WS_TABSTOP, + 0, 0, 0, 0, hWnd, 0, hIns, NULL); + { + HDC hDC = GetDC(hWnd); + int nHeight = -MulDiv(12, GetDeviceCaps(hDC, LOGPIXELSY), 72); + ReleaseDC(hWnd, hDC); + HFONT hf = CreateFont(nHeight, 0, 0, 0, FW_LIGHT, FALSE, FALSE, FALSE, SHIFTJIS_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_DONTCARE, + L"MS Gothic"); + hWhiteBrush = GetStockBrush(WHITE_BRUSH); + SendMessage(hwndCmd, WM_SETFONT, (WPARAM)hf, 0); + SendMessage(hwndEdit, WM_SETFONT, (WPARAM)hf, 0); + SendMessage(hwndCombo, WM_SETFONT, (WPARAM)hf, 0); + SendMessage(hwndProcessComboBox, WM_SETFONT, (WPARAM)hf, 0); + texts = new TextBuffer(hwndEdit); + man->RegisterThreadCreateCallback(ThreadCreate); + man->RegisterThreadRemoveCallback(ThreadRemove); + man->RegisterThreadResetCallback(ThreadReset); + TextThread* console = man->FindSingle(0); + console->RegisterOutputCallBack(ThreadOutput, NULL); + AddToCombo(*console, false); + man->RegisterProcessAttachCallback(RegisterProcessList); + man->RegisterProcessDetachCallback(RemoveProcessList); + man->RegisterProcessNewHookCallback(RefreshProfileOnNewHook); + man->RegisterAddRemoveLinkCallback(AddRemoveLink); + man->RegisterConsoleCallback(ConsoleOutput); + Host_Start(); { - HFONT hf = CreateFont(18, 0, 0, 0, FW_LIGHT, 0, 0, 0, SHIFTJIS_CHARSET, 0, 0, ANTIALIASED_QUALITY, 0, - L"MS Gothic"); - hWhiteBrush = GetStockBrush(WHITE_BRUSH); - SendMessage(hwndCmd, WM_SETFONT, (WPARAM)hf, 0); - SendMessage(hwndEdit, WM_SETFONT, (WPARAM)hf, 0); - SendMessage(hwndCombo, WM_SETFONT, (WPARAM)hf, 0); - SendMessage(hwndProcessComboBox, WM_SETFONT, (WPARAM)hf, 0); - texts = new TextBuffer(hwndEdit); - man->RegisterThreadCreateCallback(ThreadCreate); - man->RegisterThreadRemoveCallback(ThreadRemove); - man->RegisterThreadResetCallback(ThreadReset); - TextThread* console = man->FindSingle(0); - console->RegisterOutputCallBack(ThreadOutput, NULL); - AddToCombo(*console, false); - man->RegisterProcessAttachCallback(RegisterProcessList); - man->RegisterProcessDetachCallback(RemoveProcessList); - man->RegisterProcessNewHookCallback(RefreshProfileOnNewHook); - man->RegisterAddRemoveLinkCallback(AddRemoveLink); - man->RegisterConsoleCallback(ConsoleOutput); - IHF_Start(); - { - static const WCHAR program_name[] = L"Interactive Text Hooker"; - //static const WCHAR program_version[] = L"3.0"; - static WCHAR version_info[256]; - std::swprintf(version_info, L"%s %s (%s)", program_name, program_version, build_date); - man->AddConsoleOutput(version_info); - man->AddConsoleOutput(InitMessage); - } - - if (background == 0) - man->AddConsoleOutput(BackgroundMsg); - if (!IHF_IsAdmin()) - man->AddConsoleOutput(NotAdmin); + static const WCHAR program_name[] = L"Interactive Text Hooker"; + //static const WCHAR program_version[] = L"3.0"; + static WCHAR version_info[256]; + std::swprintf(version_info, L"%s %s (%s)", program_name, program_version, build_date); + man->AddConsoleOutput(version_info); + man->AddConsoleOutput(InitMessage); } - return 0; - case WM_COMMAND: + if (background == 0) + man->AddConsoleOutput(BackgroundMsg); + } + + return 0; + case WM_COMMAND: + { + DWORD wmId, wmEvent, dwId; + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmEvent) + { + case EN_VSCROLL: + { + SCROLLBARINFO info = { sizeof(info) }; + GetScrollBarInfo(hwndEdit, OBJID_VSCROLL, &info); + InvalidateRect(hwndEdit, 0, 1); + ValidateRect(hwndEdit, &info.rcScrollBar); + RedrawWindow(hwndEdit, 0, 0, RDW_ERASE); + } + break; + case CBN_SELENDOK: + { + if ((HWND)lParam == hwndProcessComboBox) + return 0; + dwId = ComboBox_GetCurSel(hwndCombo); + int len = ComboBox_GetLBTextLen(hwndCombo, dwId); + if (len > 0) { - DWORD wmId, wmEvent, dwId; - wmId = LOWORD(wParam); - wmEvent = HIWORD(wParam); - switch (wmEvent) - { - case EN_VSCROLL: - { - SCROLLBARINFO info={sizeof(info)}; - GetScrollBarInfo(hwndEdit, OBJID_VSCROLL, &info); - InvalidateRect(hwndEdit, 0, 1); - ValidateRect(hwndEdit, &info.rcScrollBar); - RedrawWindow(hwndEdit, 0, 0, RDW_ERASE); - } - break; - case CBN_SELENDOK: - { - if ((HWND)lParam == hwndProcessComboBox) - return 0; - dwId = ComboBox_GetCurSel(hwndCombo); - int len = ComboBox_GetLBTextLen(hwndCombo, dwId); - if (len > 0) - { - LPWSTR pwcEntry = new WCHAR[len + 1]; - len = ComboBox_GetLBText(hwndCombo, dwId, pwcEntry); - DWORD num = std::stoul(pwcEntry, NULL, 16); - man->SelectCurrent(num); - delete[] pwcEntry; - } - } - return 0; - case BN_CLICKED: - ClickButton(hWnd, (HWND)lParam); - break; - default: - break; - } + LPWSTR pwcEntry = new WCHAR[len + 1]; + len = ComboBox_GetLBText(hwndCombo, dwId, pwcEntry); + DWORD num = std::stoul(pwcEntry, NULL, 16); + man->SelectCurrent(num); + delete[] pwcEntry; } + } + return 0; + case BN_CLICKED: + ClickButton(hWnd, (HWND)lParam); break; - case WM_SETFOCUS: - SetFocus(hwndEdit); - return 0; - case WM_SIZE: - { - WORD width = LOWORD(lParam); - WORD height = HIWORD(lParam); - DWORD l = width / 7; - WORD h = HIWORD(GetDialogBaseUnits()); // height of the system font - h = h + (h / 2); - HDC hDC = GetDC(hWnd); - RECT rc; - GetClientRect(hWnd, &rc); - FillRect(hDC, &rc, hWhiteBrush); - ReleaseDC(hWnd, hDC); - MoveWindow(hwndProcess, 0, 0, l, h, TRUE); - MoveWindow(hwndOption, l * 1, 0, l, h, TRUE); - MoveWindow(hwndTop, l * 2, 0, l, h, TRUE); - MoveWindow(hwndClear, l * 3, 0, l, h, TRUE); - MoveWindow(hwndRemoveLink, l * 4, 0, l, h, TRUE); - MoveWindow(hwndRemoveHook, l * 5, 0, l, h, TRUE); - MoveWindow(hwndSave, l * 6, 0, width - 6 * l, h, TRUE); - l *= 2; - MoveWindow(hwndProcessComboBox, 0, h, l, 200, TRUE); - MoveWindow(hwndCmd, l, h, width - l, h, TRUE); - MoveWindow(hwndCombo, 0, h * 2, width, 200, TRUE); - h *= 3; - MoveWindow(hwndEdit, 0, h, width, height - h, TRUE); - } - return 0; - case WM_DESTROY: - man->RegisterThreadCreateCallback(0); - man->RegisterThreadRemoveCallback(0); - man->RegisterThreadResetCallback(0); - man->RegisterProcessAttachCallback(0); - man->RegisterProcessDetachCallback(0); - //delete texts; - SaveSettings(); - PostQuitMessage(0); - return 0; default: - return DefWindowProc(hWnd, message, wParam, lParam); + break; + } + } + break; + case WM_SETFOCUS: + SetFocus(hwndEdit); + return 0; + case WM_SIZE: + { + WORD width = LOWORD(lParam); + WORD height = HIWORD(lParam); + DWORD l = width / 7; + WORD h = HIWORD(GetDialogBaseUnits()); // height of the system font + h = h + (h / 2); + HDC hDC = GetDC(hWnd); + RECT rc; + GetClientRect(hWnd, &rc); + FillRect(hDC, &rc, hWhiteBrush); + ReleaseDC(hWnd, hDC); + MoveWindow(hwndProcess, 0, 0, l, h, TRUE); + MoveWindow(hwndOption, l * 1, 0, l, h, TRUE); + MoveWindow(hwndTop, l * 2, 0, l, h, TRUE); + MoveWindow(hwndClear, l * 3, 0, l, h, TRUE); + MoveWindow(hwndRemoveLink, l * 4, 0, l, h, TRUE); + MoveWindow(hwndRemoveHook, l * 5, 0, l, h, TRUE); + MoveWindow(hwndSave, l * 6, 0, width - 6 * l, h, TRUE); + l *= 2; + MoveWindow(hwndProcessComboBox, 0, h, l, 200, TRUE); + MoveWindow(hwndCmd, l, h, width - l, h, TRUE); + MoveWindow(hwndCombo, 0, h * 2, width, 200, TRUE); + h *= 3; + MoveWindow(hwndEdit, 0, h, width, height - h, TRUE); + } + return 0; + case WM_DESTROY: + man->RegisterThreadCreateCallback(0); + man->RegisterThreadRemoveCallback(0); + man->RegisterThreadResetCallback(0); + man->RegisterProcessAttachCallback(0); + man->RegisterProcessDetachCallback(0); + //delete texts; + SaveSettings(); + PostQuitMessage(0); + return 0; + default: + return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } diff --git a/gui/window.h b/gui/window.h index 2997f93..68fc565 100644 --- a/gui/window.h +++ b/gui/window.h @@ -16,4 +16,5 @@ */ #pragma once + #include "ITH.h" diff --git a/vnr/CMakeLists.txt b/vnr/CMakeLists.txt index 622ce9f..5360eec 100644 --- a/vnr/CMakeLists.txt +++ b/vnr/CMakeLists.txt @@ -1,33 +1,29 @@ +# config.pri +# DEFINES += _SECURE_SCL=0 _SCL_SECURE_NO_WARNINGS +# DEFINES += _CRT_SECURE_NO_WARNINGS + cmake_minimum_required(VERSION 2.8) set(CMAKE_CONFIGURATION_TYPES Debug Release) project(vnr) -set(WDK_HOME "C:\\WinDDK\\7600.16385.1" CACHE FILEPATH "path to the Windows DDK directory") +set(WDK_HOME "C:\\WinDDK\\7600.16385.1" CACHE FILEPATH "Windows Driver Kit path") add_definitions( - -DUNICODE - -D_UNICODE + /DUNICODE + /D_UNICODE + /D_SECURE_SCL=0 + /D_SCL_SECURE_NO_WARNINGS + /D_CRT_SECURE_NO_WARNINGS ) -include_directories(${PROJECT_SOURCE_DIR}) - -set(common_src - ${PROJECT_SOURCE_DIR}/ith/common/const.h - ${PROJECT_SOURCE_DIR}/ith/common/defs.h - ${PROJECT_SOURCE_DIR}/ith/common/except.h - ${PROJECT_SOURCE_DIR}/ith/common/growl.h - ${PROJECT_SOURCE_DIR}/ith/common/memory.h - ${PROJECT_SOURCE_DIR}/ith/common/types.h -) - -set(import_src - ${PROJECT_SOURCE_DIR}/ith/import/mono/funcinfo.h - ${PROJECT_SOURCE_DIR}/ith/import/mono/types.h - ${PROJECT_SOURCE_DIR}/ith/import/ppsspp/funcinfo.h +include_directories( + ${PROJECT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/texthook ) -add_subdirectory(ith/hook) -add_subdirectory(ith/host) -add_subdirectory(ith/sys) +add_subdirectory(vnrhook) +add_subdirectory(texthook/host) +add_subdirectory(ithsys) +add_subdirectory(profile) diff --git a/vnr/config.pri b/vnr/config.pri index 9c46c84..7e7c2a7 100644 --- a/vnr/config.pri +++ b/vnr/config.pri @@ -72,8 +72,8 @@ win32 { ## External Libraries win32 { - D3D_HOME = "$$PROGRAMFILES/Microsoft DirectX SDK" - DETOURS_HOME = "$$PROGRAMFILES/Microsoft Research/Detours Express 3.0" + D3D_HOME = "C:/Program Files/Microsoft DirectX SDK" + DETOURS_HOME = "C:/Program Files/Microsoft Research/Detours Express 3.0" #DEV_HOME = c:/dev DEV_HOME = z:/local/windows/developer BOOST_HOME = $$DEV_HOME/boost/build @@ -81,8 +81,8 @@ win32 { MSIME_HOME = $$DEV_HOME/msime #PYTHON_HOME = $$ROOTDIR/../Python #PYTHON_HOME = C:/Python - PYTHON_HOME = Z:/Local/Windows/Developer/Python - PYSIDE_HOME = $$PYTHON_HOME/Lib/site-packages/PySide + PYTHON_HOME = $$DEV_HOME/python + PYSIDE_HOME = $$PYTHON_HOME/lib/site-packages/PySide QT_HOME = c:/qt/4 QT_SRC = c:/qt SAPI_HOME = "$$PROGRAMFILES/Microsoft Speech SDK 5.1" @@ -161,7 +161,7 @@ win32 { QMAKE_CXXFLAGS_EXCEPTIONS_ON += -EHa } - CONFIG(noeh) { # No Exception handler + CONFIG(noeh) { # No exception handler message(CONFIG noeh) #CONFIG -= rtti #-exceptions -stl QMAKE_CXXFLAGS += /GR- @@ -175,7 +175,7 @@ win32 { } } - CONFIG(nosafeseh) { # No Exception handler + CONFIG(nosafeseh) { # No safe exception handler message(CONFIG nosafeseh) # Disable SafeSEH table diff --git a/vnr/copy_vnr.cmd b/vnr/copy_vnr.cmd index 8c43352..bacb760 100644 --- a/vnr/copy_vnr.cmd +++ b/vnr/copy_vnr.cmd @@ -1,20 +1,28 @@ @echo off setlocal if [%1] == [] ( - echo usage: copy_vnr + echo usage: copy_vnr path_to_Sakura goto :EOF ) xcopy %1\config.pri . /S /Y /I xcopy %1\cpp\libs\ccutil ccutil /S /Y /I xcopy %1\cpp\libs\cpputil cpputil /S /Y /I xcopy %1\cpp\libs\disasm disasm /S /Y /I /EXCLUDE:exclude.txt -xcopy %1\cpp\plugins\ith ith /S /Y /I +xcopy %1\cpp\libs\hashutil hashutil /S /Y /I +xcopy %1\cpp\plugins\ithsys ithsys /S /Y /I +xcopy %1\cpp\plugins\vnrhook vnrhook /S /Y /I +xcopy %1\cpp\plugins\texthook texthook /S /Y /I /EXCLUDE:exclude.txt xcopy %1\cpp\libs\memdbg memdbg /S /Y /I xcopy %1\cpp\libs\ntdll ntdll /S /Y /I xcopy %1\cpp\libs\ntinspect ntinspect /S /Y /I +xcopy %1\cpp\libs\winkey winkey /S /Y /I xcopy %1\cpp\libs\winmaker winmaker /S /Y /I xcopy %1\cpp\libs\winmutex winmutex /S /Y /I xcopy %1\cpp\libs\winversion winversion /S /Y /I xcopy %1\cpp\libs\winseh winseh /S /Y /I +xcopy %1\cpp\libs\wintimer wintimer /S /Y /I +xcopy %1\cpp\libs\windbg windbg /S /Y /I +xcopy %1\cpp\libs\sakurakit sakurakit /S /Y /I +xcopy %1\cpp\libs\mono mono /S /Y /I endlocal diff --git a/vnr/cpputil/cppcstring.h b/vnr/cpputil/cppcstring.h index d1cd3dc..02793a0 100644 --- a/vnr/cpputil/cppcstring.h +++ b/vnr/cpputil/cppcstring.h @@ -19,8 +19,8 @@ inline size_t cpp_basic_strlen(const charT *s) return p - s; } -inline size_t cpp_strlen(const char *s) { return cpp_basic_strlen(s); } -inline size_t cpp_wstrlen(const wchar_t *s) { return cpp_basic_strlen(s); } +inline size_t cpp_strlen(const char *s) { return cpp_basic_strlen(s); } +inline size_t cpp_wstrlen(const wchar_t *s) { return cpp_basic_strlen(s); } template inline size_t cpp_basic_strnlen(const charT *s, size_t n) @@ -30,8 +30,8 @@ inline size_t cpp_basic_strnlen(const charT *s, size_t n) return p - s; } -inline size_t cpp_strnlen(const char *s, size_t n) { return cpp_basic_strnlen(s, n); } -inline size_t cpp_wstrnlen(const wchar_t *s, size_t n) { return cpp_basic_strnlen(s, n); } +inline size_t cpp_strnlen(const char *s, size_t n) { return cpp_basic_strnlen(s, n); } +inline size_t cpp_wstrnlen(const wchar_t *s, size_t n) { return cpp_basic_strnlen(s, n); } // strnchr @@ -45,19 +45,15 @@ inline size_t cpp_wstrnlen(const wchar_t *s, size_t n) { return cpp_basic_strnle return nullptr; \ } template -inline charT *cpp_basic_strnchr(charT *s, int c, size_t n) -cpp_basic_strnchr_(s, c, n) - +inline charT *cpp_basic_strnchr(charT *s, charT c, size_t n) cpp_basic_strnchr_(s, c, n) template -inline const charT *cpp_basic_strnchr(const charT *s, int c, size_t n) -cpp_basic_strnchr_(s, c, n) +inline const charT *cpp_basic_strnchr(const charT *s, charT c, size_t n) cpp_basic_strnchr_(s, c, n) // The same as memchr -inline char *cpp_strnchr(char *s, int c, size_t n) { return cpp_basic_strnchr(s, c, n); } -inline const char *cpp_strnchr(const char *s, int c, size_t n) { return cpp_basic_strnchr(s, c, n); } - -inline wchar_t *cpp_wcsnchr(wchar_t *s, int c, size_t n) { return cpp_basic_strnchr(s, c, n); } -inline const wchar_t *cpp_wcsnchr(const wchar_t *s, int c, size_t n) { return cpp_basic_strnchr(s, c, n); } +inline char *cpp_strnchr(char *s, char c, size_t n) { return cpp_basic_strnchr(s, c, n); } +inline const char *cpp_strnchr(const char *s, char c, size_t n) { return cpp_basic_strnchr(s, c, n); } +inline wchar_t *cpp_wcsnchr(wchar_t *s, wchar_t c, size_t n) { return cpp_basic_strnchr(s, c, n); } +inline const wchar_t *cpp_wcsnchr(const wchar_t *s, wchar_t c, size_t n) { return cpp_basic_strnchr(s, c, n); } // strnstr @@ -72,25 +68,19 @@ inline const wchar_t *cpp_wcsnchr(const wchar_t *s, int c, size_t n) { return cp } template -inline charT *cpp_basic_strnstr(charT *s, const charT *r, size_t n) -cpp_basic_strnstr_(s, n, r, ::strlen(r), ::strncmp) - +inline charT *cpp_basic_strnstr(charT *s, const charT *r, size_t n) cpp_basic_strnstr_(s, n, r, ::strlen(r), ::strncmp) template -inline const charT *cpp_basic_strnstr(const charT *s, const charT *r, size_t n) -cpp_basic_strnstr_(s, n, r, ::strlen(r), ::strncmp) +inline const charT *cpp_basic_strnstr(const charT *s, const charT *r, size_t n) cpp_basic_strnstr_(s, n, r, ::strlen(r), ::strncmp) template <> -inline wchar_t *cpp_basic_strnstr(wchar_t *s, const wchar_t *r, size_t n) -cpp_basic_strnstr_(s, n, r, ::wcslen(r), ::wcsncmp) - +inline wchar_t *cpp_basic_strnstr(wchar_t *s, const wchar_t *r, size_t n) cpp_basic_strnstr_(s, n, r, ::wcslen(r), ::wcsncmp) template <> -inline const wchar_t *cpp_basic_strnstr(const wchar_t *s, const wchar_t *r, size_t n) -cpp_basic_strnstr_(s, n, r, ::wcslen(r), ::wcsncmp) +inline const wchar_t *cpp_basic_strnstr(const wchar_t *s, const wchar_t *r, size_t n) cpp_basic_strnstr_(s, n, r, ::wcslen(r), ::wcsncmp) -inline char *cpp_strnstr(char *s, const char *r, size_t n) { return cpp_basic_strnstr(s, r, n); } -inline const char *cpp_strnstr(const char *s, const char *r, size_t n) { return cpp_basic_strnstr(s, r, n); } -inline wchar_t *cpp_wcsnstr(wchar_t *s, const wchar_t *r, size_t n) { return cpp_basic_strnstr(s, r, n); } -inline const wchar_t *cpp_wcsnstr(const wchar_t *s, const wchar_t *r, size_t n) { return cpp_basic_strnstr(s, r, n); } +inline char *cpp_strnstr(char *s, const char *r, size_t n) { return cpp_basic_strnstr(s, r, n); } +inline const char *cpp_strnstr(const char *s, const char *r, size_t n) { return cpp_basic_strnstr(s, r, n); } +inline wchar_t *cpp_wcsnstr(wchar_t *s, const wchar_t *r, size_t n) { return cpp_basic_strnstr(s, r, n); } +inline const wchar_t *cpp_wcsnstr(const wchar_t *s, const wchar_t *r, size_t n) { return cpp_basic_strnstr(s, r, n); } // strnpbrk diff --git a/vnr/cpputil/cpplocale.h b/vnr/cpputil/cpplocale.h index 71041f4..bb36c75 100644 --- a/vnr/cpputil/cpplocale.h +++ b/vnr/cpputil/cpplocale.h @@ -4,9 +4,23 @@ // cpplocale.h // 9/26/2014 jichi -#include #include +#ifdef WITHOUT_CXX_CODECVT +// http://www.boost.org/doc/libs/1_48_0/libs/serialization/doc/codecvt.html +# define BOOST_UTF8_BEGIN_NAMESPACE +# define BOOST_UTF8_END_NAMESPACE +# define BOOST_UTF8_DECL +# include +# include // WARNING: This implementation should only be included ONCE +# define CPPLOCALE_NEW_FACET_UTF8(charT) (new utf8_codecvt_facet) // charT is ignored and assumed to be wchar_t +//# include +//# define CPPLOCALE_NEW_FACET_UTF8(charT) (new utf8_codecvt_facet) +#else +# include +# define CPPLOCALE_NEW_FACET_UTF8(charT) (new std::codecvt_utf8) +#endif // WITHOUT_CXX_CODECVT + //#include // See: http://stackoverflow.com/questions/20195262/how-to-read-an-utf-8-encoded-file-containing-chinese-characters-and-output-them @@ -15,7 +29,7 @@ // - 0x10ffff is the default maximum value. // - std::consume_header will skip the leading encoding byte from the input. template -inline std::locale cpp_utf8_locale(std::locale init = std::locale()) -{ return std::locale(init, new std::codecvt_utf8()); } +inline std::locale cpp_utf8_locale(std::locale init = std::locale()) //::empty()) +{ return std::locale(init, CPPLOCALE_NEW_FACET_UTF8(charT)); } #endif // CPPLOCALE_H diff --git a/vnr/cpputil/cppstring.h b/vnr/cpputil/cppstring.h index 77c65fe..6c0e67b 100644 --- a/vnr/cpputil/cppstring.h +++ b/vnr/cpputil/cppstring.h @@ -4,33 +4,15 @@ // cppstring.h // 10/12/2014 jichi -#include -#include +/#include // Initializers -template -inline std::basic_string cpp_basic_string_of(const stringT &s) -{ return std::basic_string(s.cbegin(), s.cend()); } +template +inline std::basic_string cpp_basic_string_of(const std::string &s) +{ return std::basic_string(s.begin(), s.end()); } -template -inline std::string cpp_string_of(const stringT &s) -{ return std::string(s.cbegin(), s.cend()); } - -inline std::string cpp_string_of(const char *s) -{ return s; } - -inline std::string cpp_string_of(const wchar_t *s) -{ return std::string(s, s + ::wcslen(s)); } - -template -inline std::wstring cpp_wstring_of(const stringT &s) -{ return std::wstring(s.cbegin(), s.cend()); } - -inline std::wstring cpp_wstring_of(const wchar_t *s) -{ return s; } - -inline std::wstring cpp_wstring_of(const char *s) -{ return std::wstring(s, s + ::strlen(s)); } +inline std::wstring cpp_wstring_of(const std::string &s) +{ return std::wstring(s.begin(), s.end()); } #endif // CPPSTRING_H diff --git a/vnr/disasm/disasm.cc b/vnr/disasm/disasm.cc index 3e12604..d038c32 100644 --- a/vnr/disasm/disasm.cc +++ b/vnr/disasm/disasm.cc @@ -9,6 +9,7 @@ // 3024b815 0f1302 movlps qword ptr ds:[edx],xmm0 #include "disasm.h" +#include // disasm_flag values: enum : unsigned { @@ -29,21 +30,22 @@ DISASM_BEGIN_NAMESPACE // But the are currently unused and could make disasm thread-unsafe namespace { // unnamed -BYTE disasm_seg, // CS DS ES SS FS GS - disasm_rep, // REPZ/REPNZ - disasm_opcode, // opcode - disasm_opcode2, // used when opcode==0f - disasm_modrm, // modxxxrm - disasm_sib, // scale-index-base - disasm_mem[8], // mem addr value - disasm_data[8]; // data value +BYTE disasm_seg // CS DS ES SS FS GS + , disasm_rep // REPZ/REPNZ + , disasm_opcode // opcode + , disasm_opcode2 // used when opcode==0f + , disasm_modrm // modxxxrm + , disasm_sib // scale-index-base + , disasm_mem[8] // mem addr value + , disasm_data[8] // data value + ; } // unnamed namespace // return: length if success, 0 if error -int disasm(const BYTE *opcode0) +size_t disasm(const void *opcode0) { - const BYTE *opcode = opcode0; + const BYTE *opcode = (const BYTE *)opcode0; DWORD disasm_len = 0, // 0 if error disasm_flag = 0, // C_xxx @@ -253,7 +255,7 @@ retry: for (DWORD i = 0; i < disasm_datasize; i++) disasm_data[i] = *opcode++; - disasm_len = opcode - opcode0; + disasm_len = opcode - (const BYTE *)opcode0; return disasm_len; } // disasm diff --git a/vnr/disasm/disasm.h b/vnr/disasm/disasm.h index 69a9255..f7ba7ff 100644 --- a/vnr/disasm/disasm.h +++ b/vnr/disasm/disasm.h @@ -4,7 +4,7 @@ // Include typedef of BYTE //#include -#include +//#include //#ifdef QT_CORE_LIB //# include @@ -20,7 +20,13 @@ #endif DISASM_BEGIN_NAMESPACE -int disasm(const BYTE *opcode0); // return: op length if success, 0 if error +/** + * This function can do more, but currently only used to estimate the length of an instruction. + * Warning: The current implementation is stateful and hence not thread-safe. + * @param address of the instruction to look at + * @return length of the instruction at the address or 0 if failed + */ +size_t disasm(const void *address); DISASM_END_NAMESPACE // EOF diff --git a/vnr/hashutil/hashstr.h b/vnr/hashutil/hashstr.h new file mode 100644 index 0000000..fb13101 --- /dev/null +++ b/vnr/hashutil/hashstr.h @@ -0,0 +1,55 @@ +#ifndef HASHSTR_H +#define HASHSTR_H + +// hashstr.h +// 8/1/2011 +// See: http://www.cse.yorku.ca/~oz/hash.html + +#include "hashutil/hashutil.h" +#include + +HASHUTIL_BEGIN_NAMESPACE + +enum : uint64_t { djb2_hash0 = 5381 }; + +/// djb2: h = h*33 + c +template +inline uint64_t djb2(const charT *str, uint64_t hash = djb2_hash0) +{ + charT c; + while ((c = *str++)) + hash = ((hash << 5) + hash) + c; // hash * 33 + c + return hash; +} + +/// n: length +template +inline uint64_t djb2_n(const charT *str, size_t len, uint64_t hash = djb2_hash0) +{ + while (len--) + hash = ((hash << 5) + hash) + (*str++); // hash * 33 + c + return hash; +} + +/// sdbm: hash(i) = hash(i - 1) * 65599 + str[i]; +template +inline uint64_t sdbm(const charT *str, uint64_t hash = 0) +{ + charT c; + while ((c = *str++)) + hash = c + (hash << 6) + (hash << 16) - hash; + return hash; +} + +template +inline uint64_t loselose(const charT *str, uint64_t hash = 0) +{ + charT c; + while ((c = *str++)) + hash += c; + return hash; +} + +HASHUTIL_END_NAMESPACE + +#endif // HASHSTR_H diff --git a/vnr/hashutil/hashutil.h b/vnr/hashutil/hashutil.h new file mode 100644 index 0000000..74b8597 --- /dev/null +++ b/vnr/hashutil/hashutil.h @@ -0,0 +1,15 @@ +#ifndef HASHUTIL_H +#define HASHUTIL_H + +// hashutil.h +// 6/16/2015 jichi + +// Redefine HASHUTIL_BEGIN_NAMESPACE/HASHUTIL_END_NAMESPACE if need custom namespace +#ifndef HASHUTIL_BEGIN_NAMESPACE +# define HASHUTIL_BEGIN_NAMESPACE namespace hashutil { +#endif +#ifndef HASHUTIL_END_NAMESPACE +# define HASHUTIL_END_NAMESPACE } // namespace hashutil +#endif + +#endif // HASHUTIL_H diff --git a/vnr/hashutil/hashutil.pri b/vnr/hashutil/hashutil.pri new file mode 100644 index 0000000..7ffca52 --- /dev/null +++ b/vnr/hashutil/hashutil.pri @@ -0,0 +1,11 @@ +# hashutil.pri +# 6/28/2011 jichi + +DEFINES += WITH_LIB_HASHUTIL +DEPENDPATH += $$PWD + +HEADERS += \ + $$PWD/hashstr.h \ + $$PWD/hashutil.h + +# EOF diff --git a/vnr/ithsys/CMakeLists.txt b/vnr/ithsys/CMakeLists.txt new file mode 100644 index 0000000..83228e9 --- /dev/null +++ b/vnr/ithsys/CMakeLists.txt @@ -0,0 +1,28 @@ +# # ithsys.pro +# CONFIG += noqt staticlib +# include(../../../config.pri) + +# # jichi 7/12/2015: Always enable SEH +# DEFINES += ITH_HAS_SEH + +# DEFINES += _CRT_NON_CONFORMING_SWPRINTFS + +set(ithsys_src + ithsys.h + ithsys.cc +) + +add_library(ithsys STATIC ${ithsys_src}) + +target_compile_options(ithsys PRIVATE + $<$:> + $<$:> +) + +target_link_libraries(ithsys comctl32.lib) + +target_compile_definitions(ithsys + PRIVATE + ITH_HAS_SEH + _CRT_NON_CONFORMING_SWPRINTFS +) diff --git a/vnr/ithsys/ithsys.cc b/vnr/ithsys/ithsys.cc new file mode 100644 index 0000000..3837301 --- /dev/null +++ b/vnr/ithsys/ithsys.cc @@ -0,0 +1,1549 @@ +// ithsys.cc +// 8/21/2013 jichi +// Branch: ITH_SYS/SYS.cpp, rev 126 +// +// 8/24/2013 TODO: +// - Clean up the code +// - Move my old create remote thread for ITH2 here + +#include "ithsys/ithsys.h" +//#include "vnrhook/src/util/growl.h" + +//#define ITH_SYS_SECTION L"ITH_SysSection" +#define ITH_THREADMAN_SECTION L"VNR_SYS_THREAD" + +// jichi 9/28/2013: Weither use NtThread or RemoteThread +// RemoteThread works on both Windows 7 or Wine, while NtThread does not work on wine +#define ITH_ENABLE_THREADMAN (!IthIsWindows8OrGreater() && !IthIsWine()) +//#define ITH_ENABLE_THREADMAN true + +//#define ITH_ENABLE_WINAPI // jichi: prefer Win32 API to NTDLL API + +// Helpers + +// jichi 2/3/2015: About GetVersion +// Windows XP SP3: 5.1 +// Windows 7: 6.1, 0x1db10106 +// Windows 8: 6.2, 0x23f00206 +// Windows 10: 6.2, 0x23f00206 (build 9926): + +BOOL IthIsWindowsXp() +{ + static BOOL ret = -1; // cached + if (ret < 0) { + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms724439%28v=vs.85%29.aspx + DWORD v = ::GetVersion(); + BYTE major = LOBYTE(LOWORD(v)); + //DWORD minor = (DWORD)(HIBYTE(LOWORD(v))); + + // Windows XP = 5.1 + //ret = major < 6 ? 1 : 0; + ret = major < 6; + } + return ret; +} + +// https://msdn.microsoft.com/en-us/library/windows/desktop/dn424972%28v=vs.85%29.aspx +// The same as IsWindows8OrGreater, which I don't know if the function is available to lower Windows. +static BOOL IthIsWindows8OrGreater() // this function is not exported +{ + static BOOL ret = -1; // cached + if (ret < 0) { + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms724439%28v=vs.85%29.aspx + DWORD v = ::GetVersion(); + BYTE major = LOBYTE(LOWORD(v)), + minor = HIBYTE(LOWORD(v)); + //DWORD minor = (DWORD)(HIBYTE(LOWORD(v))); + + // Windows 8/10 = 6.2 + ret = major > 6 || (major == 6 && minor >= 2); + } + return ret; +} + +BOOL IthIsWine() +{ + static BOOL ret = -1; // cached + if (ret < 0) { + const wchar_t *path; + wchar_t buffer[MAX_PATH]; + if (UINT sz = ::GetSystemDirectoryW(buffer, MAX_PATH)) { + path = buffer; + ::wcscpy(buffer + sz, L"\\winecfg.exe"); + } else + path = L"C:\\Windows\\System32\\winecfg.exe"; + //ITH_MSG(path); + ret = ::GetFileAttributesW(path) != INVALID_FILE_ATTRIBUTES ? TRUE : FALSE; + } + return ret; +} + +// jichi 9/28/2013: prevent parallelization in wine +void IthCoolDown() +{ + // http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Thread/NtDelayExecution.html + //const LONGLONG timeout = -10000; // in 100ns, i.e. 1ms + //NtDelayExecution(FALSE, (PLARGE_INTEGER)&timeout); + //NtFlushInstructionCache(NtCurrentProcess(), (LPVOID)hp.addr, hp.recover_len); + // Flush the instruction cache line, and prevent wine from rending things in parallel + if (IthIsWine()) + IthSleep(1); // sleep for 1 ms + //__asm + //{ + // //mov eax,0x2710 // = 10000 + // mov ecx,time + // mul ecx + // neg eax + // adc edx,0 + // neg edx + // push edx + // push eax + // push esp + // push 0 + // call dword ptr [NtDelayExecution] + // add esp,8 + //} +} + +// jichi 9/23/2013: wine deficenciy on mapping sections +// Whe set to false, do not map sections. +//static bool ith_has_section = true; + +//#ifdef ITH_WINE +//# include "winddk/winddk.h" +//#endif // ITH_WINE + +//#define SEC_BASED 0x200000 // jichi 8/24/2013: emoved + +// jichi 10/6/2013 +// See: http://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code +// See: http://www.codeproject.com/Articles/16598/Get-Your-DLL-s-Path-Name +EXTERN_C IMAGE_DOS_HEADER __ImageBase; +#define CURRENT_MODULE_HANDLE ((HINSTANCE)&__ImageBase) +size_t IthGetCurrentModulePath(wchar_t *buf, size_t len) +{ return ::GetModuleFileNameW(CURRENT_MODULE_HANDLE, buf, len); } + +// - Global variables - + +#ifdef ITH_HAS_HEAP +HANDLE hHeap; // used in ith/common/memory.h +#endif // ITH_HAS_HEAP + +DWORD current_process_id; +DWORD debug; +BYTE launch_time[0x10]; +LPVOID page; + +// jichi 6/12/2015: https://en.wikipedia.org/wiki/Shift_JIS +// Leading table for SHIFT-JIS encoding +BYTE LeadByteTable[0x100] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1 +}; + +namespace { // unnamed + +WCHAR file_path[MAX_PATH] = L"\\??\\"; +LPWSTR current_dir; +DWORD page_locale; +HANDLE root_obj, + dir_obj, + codepage_section, + thread_man_section; + +BYTE file_info[0x1000]; + + +// - Helper functions - + +inline DWORD GetShareMemory() +{ + __asm + { + mov eax,fs:[0x30] + mov eax,[eax+0x4C] + } +} + +inline LARGE_INTEGER *GetTimeBias() +{ __asm mov eax,0x7ffe0020 } + + +//Get full path of current process. +//inline LPWSTR GetModulePath() +//{ +// __asm +// { +// mov eax,fs:[0x30] +// mov eax,[eax+0xC] +// mov eax,[eax+0xC] +// mov eax,[eax+0x28] +// } +//} + +// - Singleton classes - + +BYTE normal_routine[0x14] = { + 0x51,0x52,0x64,0x89,0x23,0x55,0xff,0xd0,0x50,0x6a,0xfe,0xff,0x15,0x14,0x00,0x00,0x00 +}; + +BYTE except_routine[0xe0] = { + 0xba,0x08,0x00,0x00,0x00,0x8b,0xc1,0x83,0xe0,0x0f,0x83,0xf8,0x0a,0x72,0x02,0x04, + 0x07,0x04,0x30,0x66,0xab,0xc1,0xc9,0x04,0x4a,0x75,0xea,0xc3,0x00,0x00,0x00,0x00, + 0x8b,0x44,0xe4,0x04,0x31,0xf6,0x8b,0x28,0x8b,0x4c,0xe4,0x0c,0x8b,0x99,0xb8,0x00, + 0x00,0x00,0x81,0xec,0x40,0x02,0x00,0x00,0x8d,0x7c,0xe4,0x40,0x89,0xe0,0x56,0x6a, + 0x1c,0x50,0x56,0x53,0x6a,0xff,0xff,0x15,0x18,0x00,0x00,0x00,0x85,0xc0,0x75,0x98, + 0x89,0xe0,0x50,0x68,0x00,0x02,0x00,0x00,0x57,0x6a,0x02,0x53,0x6a,0xff,0xff,0x15, + 0x18,0x00,0x00,0x00,0x85,0xc0,0x75,0xe6,0x5e,0x0f,0xc1,0xf7,0xfd,0xb0,0x5c,0x66, + 0xf2,0xaf,0x66,0xc7,0x47,0x02,0x3a,0x00,0x89,0xd9,0x2b,0x0c,0xe4,0xe8,0x7e,0xff, + 0xff,0xff,0x47,0x47,0x87,0xfe,0x89,0xe9,0xe8,0x73,0xff,0xff,0xff,0x47,0x47,0x31, + 0xc0,0x89,0x47,0x10,0x6a,0x00,0x57,0x56,0x6a,0x00,0xfc,0xff,0x15,0x1c,0x00,0x00, + 0x00,0x83,0xc8,0xff,0xeb,0xbe +}; + +// jichi 8/24/2013: Could be initialized using NtMapViewOfSection/ZwMapViewOfSection +// This class cannot have constructor / destructor +struct _ThreadView { + UINT_PTR mutex, + count; + DWORD proc_record[1]; +}; + +class : private _ThreadView { // ThreadStartManager + + enum { + ADDR0 = 0xD + , ADDR1 = 0x48 + , ADDR2 = 0x60 + , ADDR3 = 0x9D + }; + +public: + LPVOID GetProcAddr(HANDLE hProc) + { + AcquireLock(); + DWORD pid,addr,len; + if (hProc == NtCurrentProcess()) + pid = ::current_process_id; + else { + PROCESS_BASIC_INFORMATION info; + NtQueryInformationProcess(hProc, ProcessBasicInformation, &info, sizeof(info), &len); + pid=info.uUniqueProcessId; + } + pid >>= 2; + for (UINT_PTR i = 0; i < count; i++) + if (pid == (proc_record[i] & 0xfff)) { + addr = proc_record[i] & ~0xfff; + ReleaseLock(); + return (LPVOID)addr; + } + len = 0x1000; + NtAllocateVirtualMemory(hProc, (PVOID *)(proc_record + count), 0, &len, + MEM_COMMIT,PAGE_EXECUTE_READWRITE); + DWORD base = proc_record[count]; + proc_record[count] |= pid; + union { + LPVOID buffer; + DWORD b; + }; + b = base; + LPVOID fun_table[3]; + *(DWORD *)(normal_routine + ADDR0) += base; + NtWriteVirtualMemory(hProc, buffer, normal_routine, 0x14, 0); + *(DWORD *)(normal_routine + ADDR0) -= base; + b += 0x14; + fun_table[0] = NtTerminateThread; + fun_table[1] = NtQueryVirtualMemory; + fun_table[2] = MessageBoxW; + NtWriteVirtualMemory(hProc, buffer, fun_table, 0xC, 0); + b += 0xc; + *(DWORD *)(except_routine + ADDR1) += base; + *(DWORD *)(except_routine + ADDR2) += base; + *(DWORD *)(except_routine + ADDR3) += base; + NtWriteVirtualMemory(hProc, buffer, except_routine, 0xE0, 0); + *(DWORD *)(except_routine + ADDR1) -= base; + *(DWORD *)(except_routine + ADDR2) -= base; + *(DWORD *)(except_routine + ADDR3) -= base; + count++; + ReleaseLock(); + return (LPVOID)base; + } + void ReleaseProcessMemory(HANDLE hProc) + { + DWORD pid,addr,len; + AcquireLock(); + if (hProc==NtCurrentProcess()) + pid = ::current_process_id; + else { + PROCESS_BASIC_INFORMATION info; + NtQueryInformationProcess(hProc,ProcessBasicInformation,&info,sizeof(info),&len); + pid = info.uUniqueProcessId; + } + pid >>= 2; + //NtWaitForSingleObject(thread_man_mutex,0,0); + for (UINT_PTR i = 0; i < count; i++) { + if ((proc_record[i]&0xfff) == pid) { + addr = proc_record[i] & ~0xfff; + DWORD size=0x1000; + NtFreeVirtualMemory(hProc, (PVOID *)&addr, &size, MEM_RELEASE); + count--; + for (UINT_PTR j = i; j < count; j++) + proc_record[j] = proc_record[j + 1]; + proc_record[count] = 0; + ReleaseLock(); + //NtReleaseMutant(thread_man_mutex,0); + return; + } + } + ReleaseLock(); + //NtReleaseMutant(thread_man_mutex,0); + } + void CheckProcessMemory() + { + UINT_PTR i, j, flag, addr; + DWORD len; + CLIENT_ID id; + OBJECT_ATTRIBUTES oa = {}; + HANDLE hProc; + BYTE buffer[8]; + AcquireLock(); + id.UniqueThread = 0; + oa.uLength = sizeof(oa); + for (i = 0; i < count ; i++) { + id.UniqueProcess = (proc_record[i]&0xfff)<<2; + addr = proc_record[i] & ~0xfff; + flag = 0; + if (NT_SUCCESS(NtOpenProcess(&hProc, PROCESS_VM_OPERATION|PROCESS_VM_READ, &oa, &id))) { + if (NT_SUCCESS(NtReadVirtualMemory(hProc, (PVOID)addr, buffer, 8, &len))) + if (::memcmp(buffer, normal_routine, 4) == 0) + flag = 1; + NtClose(hProc); + } + if (flag == 0) { + for (j = i; j < count; j++) + proc_record[j] = proc_record[j + 1]; + count--; + i--; + } + } + ReleaseLock(); + } + void AcquireLock() + { + LONG *p = (LONG *)&mutex; + while (_interlockedbittestandset(p,0)) + YieldProcessor(); + } + void ReleaseLock() + { + LONG *p = (LONG*)&mutex; + _interlockedbittestandreset(p, 0); + } +} *thread_man_ = nullptr; // global singleton + +} // unnamed namespace + +// - API functions - + +extern "C" { + +void FreeThreadStart(HANDLE hProc) +{ + if (thread_man_) + ::thread_man_->ReleaseProcessMemory(hProc); +} + +void CheckThreadStart() +{ + if (thread_man_) + ::thread_man_->CheckProcessMemory(); + + // jichi 2/2/2015: This function is only used to wait for injected threads vnrhost. + // Sleep for 100 ms to wait for remote thread to start + //IthSleep(100); + //IthCoolDown(); +} + +void IthSleep(int time) +{ + __asm + { + mov eax,0x2710 // jichi = 10000 + mov ecx,time + mul ecx + neg eax + adc edx,0 + neg edx + push edx + push eax + push esp + push 0 + call dword ptr [NtDelayExecution] + add esp,8 + } +} + +void IthSystemTimeToLocalTime(LARGE_INTEGER *time) +{ time->QuadPart -= GetTimeBias()->QuadPart; } + +int FillRange(LPCWSTR name, DWORD *lower, DWORD *upper) +{ + PLDR_DATA_TABLE_ENTRY it; + LIST_ENTRY *begin; + __asm + { + mov eax,fs:[0x30] + mov eax,[eax+0xc] + mov eax,[eax+0xc] + mov it,eax + mov begin,eax + } + + while (it->SizeOfImage) { + if (::_wcsicmp(it->BaseDllName.Buffer, name) == 0) { + *lower = *upper = (DWORD)it->DllBase; + MEMORY_BASIC_INFORMATION info = {}; + DWORD l,size; + size = 0; + do { + NtQueryVirtualMemory(NtCurrentProcess(), (LPVOID)(*upper), MemoryBasicInformation, &info, sizeof(info), &l); + if (info.Protect&PAGE_NOACCESS) { + it->SizeOfImage=size; + break; + } + size += info.RegionSize; + *upper += info.RegionSize; + } while (size < it->SizeOfImage); + return 1; + } + it = (PLDR_DATA_TABLE_ENTRY)it->InLoadOrderModuleList.Flink; + if (it->InLoadOrderModuleList.Flink == begin) + break; + } + return 0; +} + +DWORD SearchPattern(DWORD base, DWORD base_length, LPCVOID search, DWORD search_length) // KMP +{ + __asm + { + mov eax,search_length +alloc: + push 0 + sub eax,1 + jnz alloc + + mov edi,search + mov edx,search_length + mov ecx,1 + xor esi,esi +build_table: + mov al,byte ptr [edi+esi] + cmp al,byte ptr [edi+ecx] + sete al + test esi,esi + jz pre + test al,al + jnz pre + mov esi,[esp+esi*4-4] + jmp build_table +pre: + test al,al + jz write_table + inc esi +write_table: + mov [esp+ecx*4],esi + + inc ecx + cmp ecx,edx + jb build_table + + mov esi,base + xor edx,edx + mov ecx,edx +matcher: + mov al,byte ptr [edi+ecx] + cmp al,byte ptr [esi+edx] + sete al + test ecx,ecx + jz match + test al,al + jnz match + mov ecx, [esp+ecx*4-4] + jmp matcher +match: + test al,al + jz pre2 + inc ecx + cmp ecx,search_length + je finish +pre2: + inc edx + cmp edx,base_length // search_length + jb matcher + mov edx,search_length + dec edx +finish: + mov ecx,search_length + sub edx,ecx + lea eax,[edx+1] + lea ecx,[ecx*4] + add esp,ecx + } +} + +// jichi 2/5/2014: '?' = 0xff +// See: http://sakuradite.com/topic/124 +DWORD SearchPatternEx(DWORD base, DWORD base_length, LPCVOID search, DWORD search_length, BYTE wildcard) // KMP +{ + __asm + { + // jichi 2/5/2014 BEGIN + mov bl,wildcard + // jichi 2/5/2014 END + mov eax,search_length +alloc: + push 0 + sub eax,1 + jnz alloc // jichi 2/5/2014: this will also set %eax to zero + + mov edi,search + mov edx,search_length + mov ecx,1 + xor esi,esi +build_table: + mov al,byte ptr [edi+esi] + cmp al,byte ptr [edi+ecx] + sete al + test esi,esi + jz pre + test al,al + jnz pre + mov esi,[esp+esi*4-4] + jmp build_table +pre: + test al,al + jz write_table + inc esi +write_table: + mov [esp+ecx*4],esi + + inc ecx + cmp ecx,edx + jb build_table + + mov esi,base + xor edx,edx + mov ecx,edx +matcher: + mov al,byte ptr [edi+ecx] // search + // jichi 2/5/2014 BEGIN + mov bh,al // save loaded byte to reduce cache access. %ah is not used and always zero + cmp al,bl // %bl is the wildcard byte + sete al + test al,al + jnz wildcard_matched + mov al,bh // restore the loaded byte + // jichi 2/5/2014 END + cmp al,byte ptr [esi+edx] // base + sete al + // jichi 2/5/2014 BEGIN +wildcard_matched: + // jichi 2/5/2014 END + test ecx,ecx + jz match + test al,al + jnz match + mov ecx, [esp+ecx*4-4] + jmp matcher +match: + test al,al + jz pre2 + inc ecx + cmp ecx,search_length + je finish +pre2: + inc edx + cmp edx,base_length // search_length + jb matcher + mov edx,search_length + dec edx +finish: + mov ecx,search_length + sub edx,ecx + lea eax,[edx+1] + lea ecx,[ecx*4] + add esp,ecx + } +} + +DWORD IthGetMemoryRange(LPCVOID mem, DWORD *base, DWORD *size) +{ + DWORD r; + MEMORY_BASIC_INFORMATION info; + NtQueryVirtualMemory(NtCurrentProcess(), const_cast(mem), MemoryBasicInformation, &info, sizeof(info), &r); + if (base) + *base = (DWORD)info.BaseAddress; + if (size) + *size = info.RegionSize; + return (info.Type&PAGE_NOACCESS) == 0; +} + +// jichi 9/25/2013 +// See: http://publib.boulder.ibm.com/infocenter/pseries/v5r3/index.jsp?topic=/com.ibm.aix.nls/doc/nlsgdrf/multi-byte_widechar_subr.htm +// SJIS->Unicode. 'mb' must be null-terminated. 'wc' should have enough space ( 2*strlen(mb) is safe). +//#ifdef ITH_WINE +//int MB_WC(char *mb, wchar_t *wc) +//{ return mbstowcs(wc, mb, 0x100); } +// +//#else +int MB_WC(char *mb, wchar_t *wc) +{ + __asm + { + mov esi,mb + mov edi,wc + mov edx,page + lea ebx,LeadByteTable + add edx,0x220 + push 0 +_mb_translate: + movzx eax,word ptr [esi] + test al,al + jz _mb_fin + movzx ecx,al + xlat + test al,1 + cmovnz cx, word ptr [ecx*2+edx-0x204] + jnz _mb_next + mov cx,word ptr [ecx*2+edx] + mov cl,ah + mov cx, word ptr [ecx*2+edx] +_mb_next: + mov [edi],cx + add edi,2 + movzx eax,al + add esi,eax + inc dword ptr [esp] + jmp _mb_translate +_mb_fin: + pop eax + } +} + +// Count characters of 'mb' string. 'mb_length' is max length. +// jichi 9/25/2013: This function is not used +//int MB_WC_count(char *mb, int mb_length) +//{ +// __asm +// { +// xor eax,eax +// xor edx,edx +// mov esi,mb +// mov edi,mb_length +// lea ebx,LeadByteTable +//_mbc_count: +// mov dl,byte ptr [esi] +// test dl,dl +// jz _mbc_finish +// movzx ecx, byte ptr [ebx+edx] +// add esi,ecx +// inc eax +// sub edi,ecx +// ja _mbc_count +//_mbc_finish: +// } +//} + +// jichi 9/25/2013 +// See: http://publib.boulder.ibm.com/infocenter/pseries/v5r3/index.jsp?topic=/com.ibm.aix.nls/doc/nlsgdrf/multi-byte_widechar_subr.htm +// Unicode->SJIS. Analogous to MB_WC. +//#ifdef ITH_WINE +//int WC_MB(wchar_t *wc, char *mb) +//{ return wcstombs(mb, wc, 0x100); } +// +//#else +int WC_MB(wchar_t *wc, char *mb) +{ + __asm + { + mov esi,wc + mov edi,mb + mov edx,page + add edx,0x7c22 + xor ebx,ebx +_wc_translate: + movzx eax,word ptr [esi] + test eax,eax + jz _wc_fin + mov cx,word ptr [eax*2+edx] + test ch,ch + jz _wc_single + mov [edi+ebx],ch + inc ebx +_wc_single: + mov [edi+ebx],cl + inc ebx + add esi,2 + jmp _wc_translate +_wc_fin: + mov eax,ebx + } +} + +//Initialize environment for NT native calls. Not thread safe so only call it once in one module. +//1. Create new heap. Future memory requests are handled by this heap. +//Destroying this heap will completely release all dynamically allocated memory, thus prevent memory leaks on unload. +//2. Create handle to root directory of process objects (section/event/mutex/semaphore). +//NtCreate* calls will use this handle as base directory. +//3. Load SJIS code page. First check for Japanese locale. If not then load from 'C_932.nls' in system folder. +//MB_WC & WC_MB use this code page for translation. +//4. Locate current NT path (start with \??\). +//NtCreateFile requires full path or a root handle. But this handle is different from object. +//5. Map shared memory for ThreadStartManager into virtual address space. +//This will allow IthCreateThread function properly. +BOOL IthInitSystemService() +{ + PPEB peb; + //NTSTATUS status; + DWORD size; + ULONG LowFragmentHeap; + UNICODE_STRING us; + OBJECT_ATTRIBUTES oa = {sizeof(oa), 0, &us, OBJ_CASE_INSENSITIVE, 0, 0}; + IO_STATUS_BLOCK ios; + HANDLE codepage_file; + LARGE_INTEGER sec_size = {0x1000, 0}; + __asm + { + mov eax,fs:[0x18] + mov ecx,[eax+0x20] + mov eax,[eax+0x30] + mov peb,eax + mov current_process_id,ecx + } + debug = peb->BeingDebugged; + LowFragmentHeap = 2; + +#ifdef ITH_HAS_HEAP + ::hHeap = RtlCreateHeap(0x1002, 0, 0, 0, 0, 0); + RtlSetHeapInformation(::hHeap, HeapCompatibilityInformation, &LowFragmentHeap, sizeof(LowFragmentHeap)); +#endif // ITH_HAS_HEAP + + LPWSTR t = nullptr, // jichi: path to system32, such as "c:\windows\system32" + obj = nullptr; // jichi: path to current kernel session, such as "Sessions\\1\\BaseNamedObjects" + // jichi 9/22/2013: This would crash wine with access violation exception. + if (!IthIsWine()) { + // jichi 9/22/2013: For ChuSingura46+1 on Windows 7 + // t = L"C:\\Windows\\system32"; + // obj = L"\\Sessions\\1\\BaseNamedObjects"; + // On Windows XP + // t = L"C:\\WINDOWS\\system32"; + // obj = L"\\BaseNamedObjects"; + MEMORY_BASIC_INFORMATION info; + if (!NT_SUCCESS(NtQueryVirtualMemory(NtCurrentProcess(), peb->ReadOnlySharedMemoryBase, MemoryBasicInformation, &info, sizeof(info), &size))) + return FALSE; + DWORD base = (DWORD)peb->ReadOnlySharedMemoryBase; + DWORD end = base + info.RegionSize - 0x40; + static WCHAR system32[] = L"system32"; + for (;base < end; base += 2) + if (::memcmp((PVOID)base, system32, 0x10) == 0) { + t = (LPWSTR)base; + while (*t-- != L':'); + obj = (LPWSTR)base; + while (*obj != L'\\') obj++; + break; + } + if (base == end) + return FALSE; + } + //ITH_MSG(t); + //ITH_MSG(obj); + + LDR_DATA_TABLE_ENTRY *ldr_entry = (LDR_DATA_TABLE_ENTRY*)peb->Ldr->InLoadOrderModuleList.Flink; + + // jichi 7/12/2015: This will fail when the file path is a remote path such as: + // Original remote file path: \\??\\\\\\psf\\Host\\Local\\Windows\\Games\\ShinaRio\\Ayakashibito_trial\\"); + // Correct UNC path: \\??\\\\UNC\\psf\\Host\\Local\\Windows\\Games\\ShinaRio\\Ayakashibito_trial\\"); + //RtlInitUnicodeString(&us, L"\\??\\UNC\\psf\\Host\\Local\\Windows\\Games\\ShinaRio\\Ayakashibito_trial\\"); + //WCHAR file_path[MAX_PATH] = L"\\??\\"; + LPCWSTR modulePath = ldr_entry->FullDllName.Buffer; + if (modulePath[0] == '\\' && modulePath[1] == '\\') { // This is a remote path + ::file_path[4] = 'U'; + ::file_path[5] = 'N'; + ::file_path[6] = 'C'; + ::wcscpy(::file_path + 7, modulePath + 1); + } else + ::wcscpy(::file_path + 4, modulePath); + + current_dir = ::wcsrchr(::file_path, L'\\') + 1; + *current_dir = 0; + + //GROWL(::file_path); + RtlInitUnicodeString(&us, ::file_path); + + if (!NT_SUCCESS(NtOpenFile(&dir_obj,FILE_LIST_DIRECTORY|FILE_TRAVERSE|SYNCHRONIZE, + &oa,&ios,FILE_SHARE_READ|FILE_SHARE_WRITE,FILE_DIRECTORY_FILE|FILE_SYNCHRONOUS_IO_NONALERT))) + return FALSE; + + // jichi 9/22/2013: Get kernel object session ID + // See: http://www.brianbondy.com/blog/id/100/ + // It seems that on sessionId is 0 on Windows XP, and 1 on Windows Vista and later + // I assume that sessionId is in [0,9] + // For ChuSingura46+1 on Windows 7 + // obj = L"\\Sessions\\1\\BaseNamedObjects"; + // On Windows XP + // obj = L"\\BaseNamedObjects"; + //ITH_MSG(obj); + { + if (obj) + RtlInitUnicodeString(&us, obj); + else { // jichi ITH is on Wine + // Get session ID in PEB + // See: http://msdn.microsoft.com/en-us/library/bb432286%28v=vs.85%29.aspx + DWORD sessionId = peb->SessionId; + if (!sessionId) // Windows XP + RtlInitUnicodeString(&us, L"\\BaseNamedObjects"); + else { // Windows Vista + + wchar_t path[] = L"\\Sessions\\0\\BaseNamedObjects"; + path[10] += (wchar_t)sessionId; // replace 0 with the session ID + RtlInitUnicodeString(&us, path); + } + } + } + + if (!NT_SUCCESS(NtOpenDirectoryObject(&::root_obj, READ_CONTROL|0xf, &oa))) + return FALSE; + + ::page = peb->InitAnsiCodePageData; + + enum { CP932 = 932 }; + + // jichi 9/23/2013: Access violation on Wine + if (IthIsWine()) + // One wine, there is no C_932.nls + //page_locale = 0x4e4; // 1252, English + //page_locale = GetACP(); // This will return 932 when LC_ALL=ja_JP.UTF-8 on wine + // Always set locale to CP932 on Wine, since C_932.nls could be missing. + ::page_locale = CP932; + else + ::page_locale = *(DWORD *)page >> 16; + + if (::page_locale == CP932) { + oa.hRootDirectory = ::root_obj; + oa.uAttributes |= OBJ_OPENIF; + } else { // Unreachable or wine +//#ifdef ITH_WINE +// // jichi 9/22/2013: For ChuSingura46+1 on Windows 7 +// //t = L"C:\\Windows\\system32"; +// wchar_t buffer[MAX_PATH]; +// if (!t) { // jichi 9/22/2013: ITH is one wine +// if (UINT sz = ::GetSystemDirectoryW(buffer, MAX_PATH)) { +// buffer[sz] = 0; +// t = buffer; +// } else +// t = L"C:\\Windows\\System32"; // jichi 9/29/2013: sth is wrong here +// } +//#endif // ITH_WINE + + ::wcscpy(::file_path + 4, t); + t = ::file_path; + while(*++t); + if (*(t-1)!=L'\\') + *t++=L'\\'; + ::wcscpy(t,L"C_932.nls"); + RtlInitUnicodeString(&us, ::file_path); + if (!NT_SUCCESS(NtOpenFile(&codepage_file, FILE_READ_DATA, &oa, &ios,FILE_SHARE_READ,0))) + return FALSE; + oa.hRootDirectory = ::root_obj; + oa.uAttributes |= OBJ_OPENIF; + RtlInitUnicodeString(&us, L"JPN_CodePage"); + if (!NT_SUCCESS(NtCreateSection(&codepage_section, SECTION_MAP_READ, + &oa,0, PAGE_READONLY, SEC_COMMIT, codepage_file))) + return FALSE; + NtClose(codepage_file); + size = 0; + ::page = nullptr; + if (!NT_SUCCESS(NtMapViewOfSection(::codepage_section, NtCurrentProcess(), + &::page, + 0, 0, 0, &size, ViewUnmap, 0, + PAGE_READONLY))) + return FALSE; + } + if (ITH_ENABLE_THREADMAN) { + RtlInitUnicodeString(&us, ITH_THREADMAN_SECTION); + if (!NT_SUCCESS(NtCreateSection(&thread_man_section, SECTION_ALL_ACCESS, &oa, &sec_size, + PAGE_EXECUTE_READWRITE, SEC_COMMIT, 0))) + return FALSE; + size = 0; + // http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Section/NtMapViewOfSection.html + thread_man_ = nullptr; + if (!NT_SUCCESS(NtMapViewOfSection(thread_man_section, NtCurrentProcess(), + (LPVOID *)&thread_man_, + 0,0,0, &size, ViewUnmap, 0, + PAGE_EXECUTE_READWRITE))) + return FALSE; + } + return TRUE; +} + +//Release resources allocated by IthInitSystemService. +//After destroying the heap, all memory allocated by ITH module is returned to system. +void IthCloseSystemService() +{ + if (::page_locale != 0x3a4) { + NtUnmapViewOfSection(NtCurrentProcess(), ::page); + NtClose(::codepage_section); + } + if (ITH_ENABLE_THREADMAN) { + NtUnmapViewOfSection(NtCurrentProcess(), ::thread_man_); + NtClose(::thread_man_section); + } + NtClose(::root_obj); +#ifdef ITH_HAS_HEAP + RtlDestroyHeap(::hHeap); +#endif // ITH_HAS_HEAP +} + +//Check for existence of a file in current folder. Thread safe after init. +//For ITH main module, it's ITH folder. For target process it's the target process's current folder. +BOOL IthCheckFile(LPCWSTR file) +{ + //return PathFileExistsW(file); // jichi: need Shlwapi.lib + + //return (dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); + //return GetFileAttributesW(file) != INVALID_FILE_ATTRIBUTES; // jichi: does not consider the current app's path + + // jichi 9/22/2013: Following code does not work in Wine + // See: http://stackoverflow.com/questions/3828835/how-can-we-check-if-a-file-exists-or-not-using-win32-program + //WIN32_FIND_DATA FindFileData; + //HANDLE handle = FindFirstFileW(file, &FindFileData); + //if (handle != INVALID_HANDLE_VALUE) { + // FindClose(handle); + // return TRUE; + //} + //return FALSE; + if (IthIsWine()) { + HANDLE hFile = CreateFileW(file, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, 0); + if (hFile != INVALID_HANDLE_VALUE) { + CloseHandle(hFile); + return TRUE; + } else if (!wcschr(file, L':')) { // jichi: this is relative path + // jichi 9/22/2013: Change current directory to the same as main module path + // Otherwise NtFile* would not work for files with relative paths. + if (const wchar_t *path = GetMainModulePath()) // path to VNR's python exe + if (const wchar_t *base = wcsrchr(path, L'\\')) { + size_t dirlen = base - path + 1; + if (dirlen + wcslen(file) < MAX_PATH) { + wchar_t buf[MAX_PATH]; + wcsncpy(buf, path, dirlen); + wcscpy(buf + dirlen, file); + return IthCheckFile(buf); + } + } + } + } else { // not wine + HANDLE hFile; + IO_STATUS_BLOCK isb; + UNICODE_STRING us; + RtlInitUnicodeString(&us, file); + OBJECT_ATTRIBUTES oa = { sizeof(oa), dir_obj, &us, 0, 0, 0}; + // jichi 9/22/2013: Following code does not work in Wine + if (NT_SUCCESS(NtCreateFile(&hFile, FILE_READ_DATA, &oa, &isb, 0, 0, FILE_SHARE_READ, FILE_OPEN, 0, 0, 0))) { + NtClose(hFile); + return TRUE; + } + } + return FALSE; + //return IthGetFileInfo(file,file_info); + //wcscpy(current_dir,file); +} + +//Check for existence of files in current folder. +//Unlike IthCheckFile, this function allows wildcard character. +BOOL IthFindFile(LPCWSTR file) +{ + NTSTATUS status; + HANDLE h; + UNICODE_STRING us; + OBJECT_ATTRIBUTES oa = {sizeof(oa), dir_obj, &us, OBJ_CASE_INSENSITIVE, 0, 0}; + us.Buffer = const_cast(file); + LPCWSTR path = wcsrchr(file, L'\\'); + if (path) { + us.Length = (path - file) << 1; + us.MaximumLength = us.Length; + } else { + us.Length = 0; + us.MaximumLength = 0; + } + IO_STATUS_BLOCK ios; + if (NT_SUCCESS(NtOpenFile(&h,FILE_LIST_DIRECTORY|SYNCHRONIZE, + &oa,&ios,FILE_SHARE_READ,FILE_DIRECTORY_FILE|FILE_SYNCHRONOUS_IO_NONALERT))) { + BYTE info[0x400]; + if (path) + RtlInitUnicodeString(&us, path + 1); + else + RtlInitUnicodeString(&us, file); + status = NtQueryDirectoryFile(h,0,0,0,&ios,info,0x400,FileBothDirectoryInformation,TRUE,&us,TRUE); + NtClose(h); + return NT_SUCCESS(status); + } + return FALSE; +} +//Analogous to IthFindFile, but return detail information in 'info'. +BOOL IthGetFileInfo(LPCWSTR file, LPVOID info, DWORD size) +{ + NTSTATUS status; + HANDLE h; + UNICODE_STRING us; + LPCWSTR path = wcsrchr(file, L'\\'); + us.Buffer = const_cast(file); + if (path) { + us.Length = (path - file) << 1; + us.MaximumLength = us.Length; + } else { + us.Length = 0; + us.MaximumLength = 0; + } + //RtlInitUnicodeString(&us,file); + OBJECT_ATTRIBUTES oa = {sizeof(oa), dir_obj, &us, OBJ_CASE_INSENSITIVE, 0, 0}; + IO_STATUS_BLOCK ios; + if (NT_SUCCESS(NtOpenFile(&h,FILE_LIST_DIRECTORY|SYNCHRONIZE, + &oa,&ios,FILE_SHARE_READ,FILE_DIRECTORY_FILE|FILE_SYNCHRONOUS_IO_NONALERT))) { + RtlInitUnicodeString(&us,file); + status = NtQueryDirectoryFile(h,0,0,0,&ios,info,size,FileBothDirectoryInformation,0,&us,0); + status = NT_SUCCESS(status); + NtClose(h); + } else + status = FALSE; + return status; +} + +//Check for existence of a file with full NT path(start with \??\). +BOOL IthCheckFileFullPath(LPCWSTR file) +{ + UNICODE_STRING us; + RtlInitUnicodeString(&us, file); + OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &us, OBJ_CASE_INSENSITIVE, 0, 0}; + HANDLE hFile; + IO_STATUS_BLOCK isb; + if (NT_SUCCESS(NtCreateFile(&hFile,FILE_READ_DATA,&oa,&isb,0,0,FILE_SHARE_READ,FILE_OPEN,0,0,0))) { + NtClose(hFile); + return TRUE; + } else + return FALSE; +} +//Create or open file in current folder. Analogous to Win32 CreateFile. +//option: GENERIC_READ / GENERIC_WRITE. +//share: FILE_SHARE_READ / FILE_SHARE_WRITE / FILE_SHARE_DELETE. 0 for exclusive access. +//disposition: FILE_OPEN / FILE_OPEN_IF. +//Use FILE_OPEN instead of OPEN_EXISTING and FILE_OPEN_IF for CREATE_ALWAYS. +HANDLE IthCreateFile(LPCWSTR name, DWORD option, DWORD share, DWORD disposition) +{ + UNICODE_STRING us; + RtlInitUnicodeString(&us, name); + OBJECT_ATTRIBUTES oa = { sizeof(oa), dir_obj, &us, OBJ_CASE_INSENSITIVE, 0, 0 }; + HANDLE hFile; + IO_STATUS_BLOCK isb; + return NT_SUCCESS(NtCreateFile(&hFile, + option|FILE_READ_ATTRIBUTES|SYNCHRONIZE, + &oa,&isb,0,0,share,disposition, + FILE_SYNCHRONOUS_IO_NONALERT|FILE_NON_DIRECTORY_FILE,0,0)) ? + hFile : INVALID_HANDLE_VALUE; +} +//Create a directory file in current folder. +HANDLE IthCreateDirectory(LPCWSTR name) +{ + UNICODE_STRING us; + RtlInitUnicodeString(&us,name); + OBJECT_ATTRIBUTES oa = {sizeof(oa), dir_obj, &us, OBJ_CASE_INSENSITIVE, 0, 0}; + HANDLE hFile; + IO_STATUS_BLOCK isb; + return NT_SUCCESS(NtCreateFile(&hFile,FILE_LIST_DIRECTORY|FILE_TRAVERSE|SYNCHRONIZE,&oa,&isb,0,0, + FILE_SHARE_READ|FILE_SHARE_WRITE,FILE_OPEN_IF,FILE_DIRECTORY_FILE|FILE_SYNCHRONOUS_IO_NONALERT,0,0)) ? + hFile : INVALID_HANDLE_VALUE; +} + +HANDLE IthCreateFileInDirectory(LPCWSTR name, HANDLE dir, DWORD option, DWORD share, DWORD disposition) +{ + UNICODE_STRING us; + RtlInitUnicodeString(&us,name); + if (dir == 0) dir = dir_obj; + OBJECT_ATTRIBUTES oa = {sizeof(oa), dir, &us, OBJ_CASE_INSENSITIVE, 0, 0}; + HANDLE hFile; + IO_STATUS_BLOCK isb; + return NT_SUCCESS(NtCreateFile(&hFile, + option|FILE_READ_ATTRIBUTES|SYNCHRONIZE, + &oa,&isb,0,0,share,disposition, + FILE_SYNCHRONOUS_IO_NONALERT|FILE_NON_DIRECTORY_FILE,0,0)) ? + hFile : INVALID_HANDLE_VALUE; +} + +//Analogous to IthCreateFile, but with full NT path. +HANDLE IthCreateFileFullPath(LPCWSTR path, DWORD option, DWORD share, DWORD disposition) +{ + UNICODE_STRING us; + RtlInitUnicodeString(&us,path); + OBJECT_ATTRIBUTES oa = {sizeof(oa), 0, &us, OBJ_CASE_INSENSITIVE, 0, 0}; + HANDLE hFile; + IO_STATUS_BLOCK isb; + return NT_SUCCESS(NtCreateFile(&hFile, + option|FILE_READ_ATTRIBUTES|SYNCHRONIZE, + &oa,&isb,0,0,share,disposition, + FILE_SYNCHRONOUS_IO_NONALERT|FILE_NON_DIRECTORY_FILE,0,0)) ? + hFile : INVALID_HANDLE_VALUE; +} + +//Create section object for sharing memory between processes. +//Similar to CreateFileMapping. +HANDLE IthCreateSection(LPCWSTR name, DWORD size, DWORD right) +{ +// jichi 9/25/2013: GENERIC_ALL does NOT work one wine +// See ZwCreateSection: http://msdn.microsoft.com/en-us/library/windows/hardware/ff566428%28v=vs.85%29.aspx +//#ifdef ITH_WINE + enum { DesiredAccess = SECTION_ALL_ACCESS }; +//#else +// enum { DesiredAccess = GENERIC_ALL }; // jichi 9/25/2013: not sure whhy ITH is usin GENERIC_ALL +//#endif // ITH_WINE +#define eval (NT_SUCCESS(NtCreateSection(&hSection, DesiredAccess, poa, &s, \ + right, SEC_COMMIT, 0)) ? hSection : INVALID_HANDLE_VALUE) + HANDLE hSection; + LARGE_INTEGER s = {size, 0}; + OBJECT_ATTRIBUTES *poa = nullptr; + // jichi 9/25/2013: What the fxxx?! poa in the orignal source code of ITH + // is pointed to freed object on the stack?! This will crash wine! + if (name) { + UNICODE_STRING us; + RtlInitUnicodeString(&us, name); + OBJECT_ATTRIBUTES oa = {sizeof(oa), root_obj, &us,OBJ_OPENIF,0,0}; + poa = &oa; + return eval; + } else + return eval; +#undef retval +} + +//Create event object. Similar to CreateEvent. +HANDLE IthCreateEvent(LPCWSTR name, DWORD auto_reset, DWORD init_state) +{ +#define eval (NT_SUCCESS(NtCreateEvent(&hEvent, EVENT_ALL_ACCESS, poa, auto_reset, init_state)) ? \ + hEvent : INVALID_HANDLE_VALUE) + HANDLE hEvent; + OBJECT_ATTRIBUTES *poa = nullptr; + // jichi 9/25/2013: What the fxxx?! poa in the orignal source code of ITH + // is pointed to freed object on the stack?! This will crash wine! + if (name) { + UNICODE_STRING us; + RtlInitUnicodeString(&us,name); + OBJECT_ATTRIBUTES oa = {sizeof(oa), root_obj, &us, OBJ_OPENIF, 0, 0}; + poa = &oa; + return eval; + } else + return eval; +#undef eval +} + +HANDLE IthOpenEvent(LPCWSTR name) +{ + UNICODE_STRING us; + RtlInitUnicodeString(&us, name); + OBJECT_ATTRIBUTES oa = { sizeof(oa), root_obj, &us, 0, 0, 0 }; + HANDLE hEvent; + return NT_SUCCESS(NtOpenEvent(&hEvent, EVENT_ALL_ACCESS, &oa)) ? + hEvent : INVALID_HANDLE_VALUE; +} + +void IthSetEvent(HANDLE hEvent) { NtSetEvent(hEvent, 0); } + +void IthResetEvent(HANDLE hEvent) { NtClearEvent(hEvent); } + +//Create mutex object. Similar to CreateMutex. +//If 'exist' is not null, it will be written 1 if mutex exist. +HANDLE IthCreateMutex(LPCWSTR name, BOOL InitialOwner, DWORD *exist) +{ +#ifdef ITH_ENABLE_WINAPI + HANDLE ret = ::CreateMutexW(nullptr, InitialOwner, name); + if (exist) + *exist = ret == INVALID_HANDLE_VALUE || ::GetLastError() == ERROR_ALREADY_EXISTS; + return ret; +#else +#define eval NtCreateMutant(&hMutex, MUTEX_ALL_ACCESS, poa, InitialOwner) + UNICODE_STRING us; + HANDLE hMutex; + NTSTATUS status; + OBJECT_ATTRIBUTES *poa = nullptr; + // jichi 9/25/2013: What the fxxx?! poa in the orignal source code of ITH + // is pointed to freed object on the stack?! This will crash wine! + if (name) { + //GROWL(name); + RtlInitUnicodeString(&us, name); + OBJECT_ATTRIBUTES oa = {sizeof(oa), root_obj, &us, OBJ_OPENIF, 0, 0}; + poa = &oa; + status = eval; + //GROWL_DWORD(status); + } else + status = eval; + if (NT_SUCCESS(status)) { + if (exist) + *exist = status == STATUS_OBJECT_NAME_EXISTS; + return hMutex; + } else + return INVALID_HANDLE_VALUE; +#undef eval +#endif // ITH_ENABLE_WINAPI +} + +HANDLE IthOpenMutex(LPCWSTR name) +{ +#ifdef ITH_ENABLE_WINAPI + return ::OpenMutexW(MUTEX_ALL_ACCESS, FALSE, name); +#else + UNICODE_STRING us; + RtlInitUnicodeString(&us, name); + OBJECT_ATTRIBUTES oa = {sizeof(oa), root_obj, &us, 0, 0, 0}; + HANDLE hMutex; + if (NT_SUCCESS(NtOpenMutant(&hMutex, MUTEX_ALL_ACCESS, &oa))) + return hMutex; + else + return INVALID_HANDLE_VALUE; +#endif // ITH_ENABLE_WINAPI +} + +BOOL IthReleaseMutex(HANDLE hMutex) +{ return NT_SUCCESS(NtReleaseMutant(hMutex, 0)); } + +//Create new thread. 'hProc' must have following right. +//PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE. +HANDLE IthCreateThread(LPCVOID start_addr, DWORD param, HANDLE hProc) +{ + HANDLE hThread; + // jichi 9/27/2013: NtCreateThread is not implemented in Wine 1.7 + if (thread_man_) { // Windows XP + // jichi 9/29/2013: Reserved && commit stack size + // See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366803%28v=vs.85%29.aspx + // See: http://msdn.microsoft.com/en-us/library/ms810627.aspx + enum { DEFAULT_STACK_LIMIT = 0x400000 }; + enum { DEFAULT_STACK_COMMIT = 0x10000 }; + enum { PAGE_SIZE = 0x1000 }; + CLIENT_ID id; + LPVOID protect; + USER_STACK stack = {}; + CONTEXT ctx = {CONTEXT_FULL}; + DWORD size = DEFAULT_STACK_LIMIT, + commit = DEFAULT_STACK_COMMIT; + if (!NT_SUCCESS(NtAllocateVirtualMemory(hProc, &stack.ExpandableStackBottom, 0, &size, MEM_RESERVE, PAGE_READWRITE))) + return INVALID_HANDLE_VALUE; + + stack.ExpandableStackBase = (char *)stack.ExpandableStackBottom + size; + stack.ExpandableStackLimit = (char *)stack.ExpandableStackBase - commit; + size = PAGE_SIZE; + commit += size; + protect = (char *)stack.ExpandableStackBase - commit; + NtAllocateVirtualMemory(hProc, &protect, 0, &commit, MEM_COMMIT, PAGE_READWRITE); + DWORD oldAccess; // jichi 9/29/2013: unused + NtProtectVirtualMemory(hProc, &protect, &size, PAGE_READWRITE|PAGE_GUARD, &oldAccess); + ctx.SegGs = 0; + ctx.SegFs = 0x38; + ctx.SegEs = 0x20; + ctx.SegDs = 0x20; + ctx.SegSs = 0x20; + ctx.SegCs = 0x18; + ctx.EFlags = 0x3000; + ctx.Eip = (DWORD)thread_man_->GetProcAddr(hProc); + ctx.Eax = (DWORD)start_addr; + ctx.Ecx = ctx.Eip + 0x40; + ctx.Edx = 0xffffffff; + ctx.Esp = (DWORD)stack.ExpandableStackBase - 0x10; + ctx.Ebp = param; + + // NTSYSAPI + // NTSTATUS + // NTAPI + // NtCreateThread( + // _Out_ PHANDLE ThreadHandle, + // _In_ ACCESS_MASK DesiredAccess, + // _In_ POBJECT_ATTRIBUTES ObjectAttributes, + // _In_ HANDLE ProcessHandle, + // _Out_ PCLIENT_ID ClientId, + // _In_ PCONTEXT ThreadContext, + // _In_ PUSER_STACK UserStack, + // _In_ BOOLEAN CreateSuspended + // ); + if (NT_SUCCESS(NtCreateThread( + &hThread, // _Out_ PHANDLE ThreadHandle, + THREAD_ALL_ACCESS, // _In_ ACCESS_MASK DesiredAccess, + nullptr, // _In_ POBJECT_ATTRIBUTES ObjectAttributes, + hProc, // _In_ HANDLE ProcessHandle, + &id, // _Out_ PCLIENT_ID ClientId, + &ctx, // _In_ PCONTEXT ThreadContext, + &stack, // _In_ PUSER_STACK UserStack, + TRUE // _In_ BOOLEAN CreateSuspended + ))) { + // On x64 Windows, NtCreateThread in ntdll calls NtCreateThread in ntoskrnl via WOW64, + // which maps 32-bit system call to the correspond 64-bit version. + // This layer doesn't correctly copy whole CONTEXT structure, so we must set it manually + // after the thread is created. + // On x86 Windows, this step is not necessary. + NtSetContextThread(hThread, &ctx); + NtResumeThread(hThread, 0); + } else + hThread = INVALID_HANDLE_VALUE; + + } else { + // jichi 9/27/2013: CreateRemoteThread works on both Wine and Windows 7 + // Use CreateRemoteThread instead + // FIXME 10/5/2031: Though sometimes works, CreateRemoteThread randomly crashes on wine. + // See: + // - http://www.unknowncheats.me/forum/c-and-c/64775-createremotethread-dll-injection.html + // - http://source.winehq.org/WineAPI/CreateRemoteThread.html + // - http://msdn.microsoft.com/en-us/library/windows/desktop/ms682437%28v=vs.85%29.aspx + // HANDLE WINAPI CreateRemoteThread( + // _In_ HANDLE hProcess, + // _In_ LPSECURITY_ATTRIBUTES lpThreadAttributes, + // _In_ SIZE_T dwStackSize, + // _In_ LPTHREAD_START_ROUTINE lpStartAddress, + // _In_ LPVOID lpParameter, + // _In_ DWORD dwCreationFlags, + // _Out_ LPDWORD lpThreadId + // ); + //ITH_TRY { + if (hProc == INVALID_HANDLE_VALUE) + hProc = GetCurrentProcess(); + //DWORD dwThreadId; + hThread = CreateRemoteThread( + hProc, // _In_ HANDLE hProcess, + nullptr, // _In_ LPSECURITY_ATTRIBUTES lpThreadAttributes, + 0, // _In_ SIZE_T dwStackSize, + (LPTHREAD_START_ROUTINE)start_addr, // _In_ LPTHREAD_START_ROUTINE lpStartAddress, + (LPVOID)param, // _In_ LPVOID lpParameter, + 0, //STACK_SIZE_PARAM_IS_A_RESERVATION // _In_ DWORD dwCreationFlags, + nullptr // _Out_ LPDWORD lpThreadId + ); + if (!hThread) // jichi: this function returns nullptr instead of -1 + hThread = INVALID_HANDLE_VALUE; + //} ITH_EXCEPT { + // ITH_WARN(L"exception"); + // hThread = INVALID_HANDLE_VALUE; + //} + } + /* + else { + // jichi 9/29/2013: Also work on Wine and Windows 7 + // See: http://waleedassar.blogspot.com/2012/06/createremotethread-vs.html + CLIENT_ID id; + //DWORD size = DEFAULT_STACK_LIMIT, + // commit = DEFAULT_STACK_COMMIT; + DWORD reserve = 0, + commit = 0; + // http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Executable%20Images/RtlCreateUserThread.html + // NTSYSAPI + // NTSTATUS + // NTAPI + // RtlCreateUserThread( + // IN HANDLE ProcessHandle, + // IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL, + // IN BOOLEAN CreateSuspended, + // IN ULONG StackZeroBits, + // IN OUT PULONG StackReserved, + // IN OUT PULONG StackCommit, + // IN PVOID StartAddress, + // IN PVOID StartParameter OPTIONAL, + // OUT PHANDLE ThreadHandle, + // OUT PCLIENT_ID ClientID); + if (!NT_SUCCESS(RtlCreateUserThread( + hProc, // HANDLE hProcess, + nullptr, // IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL, + FALSE, // IN BOOLEAN CreateSuspended, + 0, // IN ULONG StackZeroBits, + &reserve, // IN OUT PULONG StackReserved, + &commit, // IN OUT PULONG StackCommit, + (LPVOID)start_addr, // IN PVOID StartAddress, + (LPVOID)param,// IN PVOID StartParameter OPTIONAL, + &hThread, // OUT PHANDLE ThreadHandle, + &id // OUT PCLIENT_ID ClientID + ))) + hThread = INVALID_HANDLE_VALUE; + } + */ + return hThread; +} + +//Query module export table. Return function address if found. +//Similar to GetProcAddress +DWORD GetExportAddress(DWORD hModule,DWORD hash) +{ + IMAGE_DOS_HEADER *DosHdr; + IMAGE_NT_HEADERS *NtHdr; + IMAGE_EXPORT_DIRECTORY *ExtDir; + UINT uj; + char* pcExportAddr,*pcFuncPtr,*pcBuffer; + DWORD dwReadAddr,dwFuncAddr,dwFuncName; + WORD wOrd; + DosHdr = (IMAGE_DOS_HEADER*)hModule; + if (IMAGE_DOS_SIGNATURE==DosHdr->e_magic) { + dwReadAddr=hModule+DosHdr->e_lfanew; + NtHdr=(IMAGE_NT_HEADERS*)dwReadAddr; + if (IMAGE_NT_SIGNATURE == NtHdr->Signature) { + pcExportAddr = (char*)((DWORD)hModule+ + (DWORD)NtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + if (!pcExportAddr) + return 0; + ExtDir = (IMAGE_EXPORT_DIRECTORY*)pcExportAddr; + pcExportAddr = (char*)((DWORD)hModule+(DWORD)ExtDir->AddressOfNames); + + for (uj = 0; uj < ExtDir->NumberOfNames; uj++) { + dwFuncName = *(DWORD *)pcExportAddr; + pcBuffer = (char*)((DWORD)hModule+dwFuncName); + if (GetHash(pcBuffer) == hash) { + pcFuncPtr = (char*)((DWORD)hModule+(DWORD)ExtDir->AddressOfNameOrdinals+(uj*sizeof(WORD))); + wOrd = *(WORD*)pcFuncPtr; + pcFuncPtr = (char*)((DWORD)hModule+(DWORD)ExtDir->AddressOfFunctions+(wOrd*sizeof(DWORD))); + dwFuncAddr = *(DWORD *)pcFuncPtr; + return hModule+dwFuncAddr; + } + pcExportAddr += sizeof(DWORD); + } + } + } + return 0; +} + +} // extern "C" + +// EOF + +/*__declspec(naked) void normal_asm() +{ + __asm + { + push ecx + push edx + mov fs:[0],esp + push ebp + call eax +_terminate: + push eax + push -2 + call dword ptr [NtTerminateThread] + } +}*/ + +/* +__declspec(naked) void RegToStrAsm() +{ + __asm + { + mov edx, 8 +_cvt_loop: + mov eax, ecx + and eax, 0xF + cmp eax, 0xA + jb _below_ten + add al,7 +_below_ten: + add al,0x30 + stosw + ror ecx,4 + dec edx + jne _cvt_loop + retn + } +} +__declspec(naked) void except_asm() +{ + __asm + { + mov eax,[esp + 4] + xor esi,esi + mov ebp,[eax] + mov ecx,[esp + 0xC] + mov ebx,[ecx + 0xB8] + sub esp,0x240 + lea edi,[esp + 0x40] + mov eax,esp + push esi + push 0x1C + push eax + push esi + push ebx + push -1 + call dword ptr [NtQueryVirtualMemory] + test eax,eax + jne _terminate + mov eax,esp + push eax + push 0x200 + push edi + push 2 + push ebx + push -1 + call dword ptr [NtQueryVirtualMemory] + test eax,eax + jne _terminate + pop esi + xadd edi,esi + std + mov al,0x5C + repen scasw + mov word ptr [edi + 2], 0x3A + mov ecx,ebx + sub ecx,[esp] + call RegToStrAsm + inc edi + inc edi + xchg esi,edi + mov ecx,ebp + call RegToStrAsm + inc edi + inc edi + xor eax,eax + mov [edi + 0x10], eax + push 0 + push edi + push esi + push 0 + call dword ptr [MessageBoxW] + or eax, -1 + jmp _terminate + } +} + +//Prompt for file name. +HANDLE IthPromptCreateFile(DWORD option, DWORD share, DWORD disposition) +{ + OPENFILENAME ofn = {sizeof(ofn)}; // common dialog box structure + WCHAR szFile[MAX_PATH]; // buffer for file name + wcscpy(current_dir,L"ITH_export.txt"); + wcscpy(szFile,file_path); + + //szFile[0]=0; + ofn.lpstrFile = szFile + 4; + ofn.nMaxFile = MAX_PATH; + ofn.lpstrFilter = L"Text\0*.txt"; + BOOL result; + if (disposition==FILE_OPEN) + result=GetOpenFileName(&ofn); + else + result=GetSaveFileName(&ofn); + if (result) + { + LPWSTR s=szFile+wcslen(szFile) - 4; + if (_wcsicmp(s,L".txt")!=0) wcscpy(s + 4,L".txt"); + return IthCreateFileFullPath(szFile,option,share,disposition); + } + else return INVALID_HANDLE_VALUE; +} +*/ diff --git a/vnr/ithsys/ithsys.h b/vnr/ithsys/ithsys.h new file mode 100644 index 0000000..87c67f3 --- /dev/null +++ b/vnr/ithsys/ithsys.h @@ -0,0 +1,132 @@ +#pragma once + +// ithsys.h +// 8/23/2013 jichi +// Branch: ITH/IHF_SYS.h, rev 111 + +#ifdef _MSC_VER +# pragma warning(disable:4800) // C4800: forcing value to bool +#endif // _MSC_VER + +#include "ntdll/ntdll.h" + +// jichi 8/24/2013: Why extern "C"? Any specific reason to use C instead of C++ naming? +extern "C" { +//int disasm(BYTE *opcode0); // jichi 8/15/2013: move disasm to separate file +extern WORD *NlsAnsiCodePage; +int FillRange(LPCWSTR name,DWORD *lower, DWORD *upper); +int MB_WC(char *mb, wchar_t *wc); +//int MB_WC_count(char *mb, int mb_length); +int WC_MB(wchar_t *wc, char *mb); + +// jichi 10/1/2013: Return 0 if failed. So, it is ambiguous if the search pattern starts at 0 +DWORD SearchPattern(DWORD base, DWORD base_length, LPCVOID search, DWORD search_length); // KMP + +// jichi 2/5/2014: The same as SearchPattern except it uses 0xff to match everything +// According to @Andys, 0xff seldom appear in the source code: http://sakuradite.com/topic/124 +enum : BYTE { SP_ANY = 0xff }; +#define SP_ANY_2 SP_ANY,SP_ANY +#define SP_ANY_3 SP_ANY,SP_ANY,SP_ANY +#define SP_ANY_4 SP_ANY,SP_ANY,SP_ANY,SP_ANY +DWORD SearchPatternEx(DWORD base, DWORD base_length, LPCVOID search, DWORD search_length, BYTE wildcard=SP_ANY); + +BOOL IthInitSystemService(); +void IthCloseSystemService(); +DWORD IthGetMemoryRange(LPCVOID mem, DWORD *base, DWORD *size); +BOOL IthCheckFile(LPCWSTR file); +BOOL IthFindFile(LPCWSTR file); +BOOL IthGetFileInfo(LPCWSTR file, LPVOID info, DWORD size = 0x1000); +BOOL IthCheckFileFullPath(LPCWSTR file); +HANDLE IthCreateFile(LPCWSTR name, DWORD option, DWORD share, DWORD disposition); +HANDLE IthCreateFileInDirectory(LPCWSTR name, HANDLE dir, DWORD option, DWORD share, DWORD disposition); +HANDLE IthCreateDirectory(LPCWSTR name); +HANDLE IthCreateFileFullPath(LPCWSTR fullpath, DWORD option, DWORD share, DWORD disposition); +HANDLE IthPromptCreateFile(DWORD option, DWORD share, DWORD disposition); +HANDLE IthCreateSection(LPCWSTR name, DWORD size, DWORD right); +HANDLE IthCreateEvent(LPCWSTR name, DWORD auto_reset=0, DWORD init_state=0); +HANDLE IthOpenEvent(LPCWSTR name); +void IthSetEvent(HANDLE hEvent); +void IthResetEvent(HANDLE hEvent); +HANDLE IthCreateMutex(LPCWSTR name, BOOL InitialOwner, DWORD *exist=0); +HANDLE IthOpenMutex(LPCWSTR name); +BOOL IthReleaseMutex(HANDLE hMutex); +//DWORD IthWaitForSingleObject(HANDLE hObject, DWORD dwTime); +HANDLE IthCreateThread(LPCVOID start_addr, DWORD param, HANDLE hProc=(HANDLE)-1); +DWORD GetExportAddress(DWORD hModule,DWORD hash); +void IthSleep(int time); // jichi 9/28/2013: in ms +void IthSystemTimeToLocalTime(LARGE_INTEGER *ptime); +void FreeThreadStart(HANDLE hProc); +void CheckThreadStart(); +} // extern "C" + +#ifdef ITH_HAS_HEAP +extern HANDLE hHeap; // used in ith/common/memory.h +#endif // ITH_HAS_HEAP + +extern DWORD current_process_id; +extern DWORD debug; +extern BYTE LeadByteTable[]; +extern LPVOID page; +extern BYTE launch_time[]; + +inline DWORD GetHash(LPSTR str) +{ + DWORD hash = 0; + //for (; *str; str++) + while (*str) + hash = ((hash>>7) | (hash<<25)) + *str++; + return hash; +} + +inline DWORD GetHash(LPCWSTR str) +{ + DWORD hash = 0; + //for (; *str; str++) + while (*str) + hash = ((hash>>7) | (hash<<25)) + *str++; + return hash; +} + +inline void IthBreak() +{ if (debug) __debugbreak(); } + +inline LPCWSTR GetMainModulePath() +{ + __asm + { + mov eax, fs:[0x30] + mov eax, [eax + 0xC] + mov eax, [eax + 0xC] + mov eax, [eax + 0x28] + } +} + +// jichi 9/28/2013: Add this to lock NtWriteFile in wine +class IthMutexLocker +{ + HANDLE m; +public: + explicit IthMutexLocker(HANDLE mutex) : m(mutex) + { NtWaitForSingleObject(m, 0, 0); } + + ~IthMutexLocker() { if (m != INVALID_HANDLE_VALUE) IthReleaseMutex(m); } + + bool locked() const { return m != INVALID_HANDLE_VALUE; } + + void unlock() { if (m != INVALID_HANDLE_VALUE) { IthReleaseMutex(m); m = INVALID_HANDLE_VALUE; } } +}; + +void IthCoolDown(); + +BOOL IthIsWine(); +BOOL IthIsWindowsXp(); +//BOOL IthIsWindows8OrGreater(); // not public + +/** Get current dll path. + * @param buf + * @param len + * @return length of the path excluding \0 + */ +size_t IthGetCurrentModulePath(wchar_t *buf, size_t len); + +// EOF diff --git a/vnr/ithsys/ithsys.pri b/vnr/ithsys/ithsys.pri new file mode 100644 index 0000000..7653038 --- /dev/null +++ b/vnr/ithsys/ithsys.pri @@ -0,0 +1,13 @@ +# ithsys.pri +# 8/21/2013 jichi + +DEFINES += WITH_LIB_ITHSYS +LIBS += -lithsys +DEPENDPATH += $$PWD +HEADERS += $$PWD/ithsys.h +#SOURCES += $$PWD/ithsys.cc + +#include($$LIBDIR/winddk/winddk.pri) +#LIBS += -L$$WDK/lib/wxp/i386 + +# EOF diff --git a/vnr/ithsys/ithsys.pro b/vnr/ithsys/ithsys.pro new file mode 100644 index 0000000..954d181 --- /dev/null +++ b/vnr/ithsys/ithsys.pro @@ -0,0 +1,52 @@ +# ithsys.pro +# 8/21/2013 jichi +# Build ithsys.lib + +#CONFIG += noqt noeh staticlib +CONFIG += noqt staticlib + +include(../../../config.pri) +include($$LIBDIR/ntdll/ntdll.pri) + +#LIBS += -L$$WDK7_HOME/lib/wxp/i386 -lntdll + +#include($$LIBDIR/winddk/winddk.pri) +#LIBS += -L$$WDK/lib/wxp/i386 + +# jichi 9/22/2013: When ITH is on wine, certain NT functions are replaced +#DEFINES += ITH_WINE + +# jichi 7/12/2015: Always enable SEH +DEFINES += ITH_HAS_SEH + +# jichi 11/24/2013: Disable manual heap +DEFINES -= ITH_HAS_HEAP + +## Libraries + +#INCLUDEPATH += $$ITH_HOME/include +#INCLUDEPATH += $$WDK7_HOME/inc/ddk + +#LIBS += -lgdi32 -luser32 -lkernel32 +#LIBS += -L$$WDK7_HOME/lib/wxp/i386 -lntdll +#LIBS += $$WDK7_HOME/lib/crt/i386/msvcrt.lib # Override msvcrt10 + +#DEFINES += ITH_HAS_CXX + +#LIBS += -lith_sys -lntdll +#LIBS += -lith_tls -lntdll +#LIBS += -lntoskrnl + +DEFINES += _CRT_NON_CONFORMING_SWPRINTFS + +## Sources + +TEMPLATE = lib +TARGET = ithsys + +HEADERS += ithsys.h +SOURCES += ithsys.cc + +OTHER_FILES += ithsys.pri + +# EOF diff --git a/vnr/memdbg/memsearch.cc b/vnr/memdbg/memsearch.cc index 025b8bf..a589baf 100644 --- a/vnr/memdbg/memsearch.cc +++ b/vnr/memdbg/memsearch.cc @@ -198,76 +198,190 @@ finish: } } +#if 0 +/** + * Search from stopAddress back to startAddress - range + * This function is not well debugged + */ +DWORD reverseSearchPattern(DWORD base, DWORD base_length, LPCVOID search, DWORD search_length) // KMP +{ + __asm + { + mov eax,search_length +alloc: + push 0 + sub eax,1 + jnz alloc + + mov edi,search + mov edx,search_length + mov ecx,1 + xor esi,esi +build_table: + mov al,byte ptr [edi+esi] + cmp al,byte ptr [edi+ecx] + sete al + test esi,esi + jz pre + test al,al + jnz pre + mov esi,[esp+esi*4-4] + jmp build_table +pre: + test al,al + jz write_table + inc esi +write_table: + mov [esp+ecx*4],esi + + inc ecx + cmp ecx,edx + jb build_table + + mov esi,base + xor edx,edx + mov ecx,edx +matcher: + mov al,byte ptr [edi+ecx] + cmp al,byte ptr [esi-edx] // jichi 6/1/2014: The only place that is modified + sete al + test ecx,ecx + jz match + test al,al + jnz match + mov ecx, [esp+ecx*4-4] + jmp matcher +match: + test al,al + jz pre2 + inc ecx + cmp ecx,search_length + je finish +pre2: + inc edx + cmp edx,base_length // search_length + jb matcher + mov edx,search_length + dec edx +finish: + mov ecx,search_length + sub edx,ecx + lea eax,[edx+1] + lea ecx,[ecx*4] + add esp,ecx + } +} +#endif // 0 + // Modified from ITH findCallOrJmpAbs // Example call: // 00449063 |. ff15 5cf05300 call dword ptr ds:[<&gdi32.getglyphoutli>; \GetGlyphOutlineA enum : WORD { - word_jmp = 0x25ff + word_jmp = 0x25ff // long jump , word_call = 0x15ff // far call }; -/*** - * Return the absolute address of op. Op takes 1 parameter. - * - * @param op first half of the operator - * @param arg1 the function address - * @param start address - * @param search range - * @return absolute address or 0 - */ -DWORD findWordCall(WORD op, DWORD arg1, DWORD start, DWORD size) -{ - typedef WORD optype; - typedef DWORD argtype; - - enum { START = 0x1000 }; // leading size to skip - for (DWORD i = START; i < size - sizeof(argtype); i++) - if (op == *(optype *)(start + i)) { - DWORD t = *(DWORD *)(start + i + sizeof(optype)); - if (t > start && t < start + size) { - if (arg1 == *(argtype *)t) - return start + i; - else - i += sizeof(optype) + sizeof(argtype) - 1; // == 5 - } - } - return 0; -} // Modified from ITH findCallOrJmpAbs enum : BYTE { - byte_call = 0xe8 // near call + byte_jmp = 0xe9 // long call + , byte_call = 0xe8 // near call , byte_push_small = 0x6a // push byte operand , byte_push_large = 0x68 // push operand > 0xff }; /*** - * Return the absolute address of op. Op takes 1 address parameter. + * Return the absolute address of op. Op takes 1 parameter. + * DWORD call with absolute address. * * @param op first half of the operator * @param arg1 the function address * @param start address - * @param search range + * @param stop address + * @param offset search after start address + * @param range search size * @return absolute address or 0 */ -DWORD findByteCall(BYTE op, DWORD arg1, DWORD start, DWORD size) +DWORD findWordCall(WORD op, DWORD arg1, DWORD start, DWORD stop, DWORD offset, DWORD range) +{ + typedef WORD optype; + typedef DWORD argtype; + + for (DWORD i = offset; i < offset + range - sizeof(argtype); i++) + if (op == *(optype *)(start + i)) { + DWORD t = *(DWORD *)(start + i + sizeof(optype)); + if (t > start && t < stop) { + if (arg1 == *(argtype *)t) // absolute address + return start + i; + //i += sizeof(optype) + sizeof(argtype) - 1; // == 5 + } + } + return 0; +} + +DWORD findLastWordCall(WORD op, DWORD arg1, DWORD start, DWORD stop, DWORD offset, DWORD range) +{ + typedef WORD optype; + typedef DWORD argtype; + DWORD ret = 0; + + for (DWORD i = offset; i < offset + range - sizeof(argtype); i++) + if (op == *(optype *)(start + i)) { + DWORD t = *(DWORD *)(start + i + sizeof(optype)); + if (t > start && t < stop) { + if (arg1 == *(argtype *)t) // absolute address + ret = start + i; + //i += sizeof(optype) + sizeof(argtype) - 1; // == 5 + } + } + return ret; +} + + +/*** + * Return the absolute address of op. Op takes 1 address parameter. + * BYTE call with relative address. + * + * @param op first half of the operator + * @param arg1 the function address + * @param start address + * @param offset search after start address + * @param range search size + * @return absolute address or 0 + */ +DWORD findByteCall(BYTE op, DWORD arg1, DWORD start, DWORD offset, DWORD range) { typedef BYTE optype; typedef DWORD argtype; - enum { START = 0x1000 }; // leading size to skip - for (DWORD i = START; i < size - sizeof(argtype); i++) + for (DWORD i = offset; i < offset + range - sizeof(argtype); i++) if (op == *(optype *)(start + i)) { - DWORD t = *(DWORD *)(start + i + sizeof(optype)); - if (t > start && t < start + size) { - if (arg1 == *(argtype *)t) - return start + i; - else - i += sizeof(optype) + sizeof(argtype) - 1; // == 4 - } + DWORD t = *(argtype *)(start + i + sizeof(optype)); + //if (t > start && t < stop) { + if (arg1 == t + start + i + sizeof(optype) + sizeof(argtype)) // relative address + return start + i; + //i += sizeof(optype) + sizeof(argtype) - 1; // == 4 + //} } return 0; } +DWORD findLastByteCall(BYTE op, DWORD arg1, DWORD start, DWORD offset, DWORD range) +{ + typedef BYTE optype; + typedef DWORD argtype; + DWORD ret = 0; + for (DWORD i = offset; i < offset + range - sizeof(argtype); i++) + if (op == *(optype *)(start + i)) { + DWORD t = *(argtype *)(start + i + sizeof(optype)); + //if (t > start && t < stop) { + if (arg1 == t + start + i + sizeof(optype) + sizeof(argtype)) // relative address + ret = start + i; + //i += sizeof(optype) + sizeof(argtype) - 1; // == 4 + //} + } + return ret; +} + /*** * Return the absolute address of op. Op takes 1 parameter. * @@ -277,13 +391,12 @@ DWORD findByteCall(BYTE op, DWORD arg1, DWORD start, DWORD size) * @param search range * @return absolute address or 0 */ -//DWORD findByteOp1(BYTE op, DWORD arg1, DWORD start, DWORD size) +//DWORD findByteOp1(BYTE op, DWORD arg1, DWORD start, DWORD size, DWORD offset) //{ // typedef BYTE optype; // typedef DWORD argtype; // -// enum { START = 0x1000 }; // leading size to skip -// for (DWORD i = START; i < size - sizeof(argtype); i++) +// for (DWORD i = offset; i < size - sizeof(argtype); i++) // if (op == *(optype *)(start + i)) { // DWORD t = *(DWORD *)(start + i + sizeof(optype)); // if (t == arg1) { @@ -299,14 +412,29 @@ DWORD findByteCall(BYTE op, DWORD arg1, DWORD start, DWORD size) MEMDBG_BEGIN_NAMESPACE -DWORD findJumpAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound) -{ return findWordCall(word_jmp, funcAddr, lowerBound, upperBound - lowerBound); } +DWORD findLongJumpAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound, DWORD offset, DWORD range) +{ return findWordCall(word_jmp, funcAddr, lowerBound, upperBound, offset, range ? range : (upperBound - lowerBound - offset)); } -DWORD findFarCallAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound) -{ return findWordCall(word_call, funcAddr, lowerBound, upperBound - lowerBound); } +DWORD findShortJumpAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound, DWORD offset, DWORD range) +{ return findByteCall(byte_jmp, funcAddr, lowerBound, offset, range ? range : (upperBound - lowerBound - offset)); } -DWORD findNearCallAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound) -{ return findByteCall(byte_call, funcAddr, lowerBound, upperBound - lowerBound); } +DWORD findFarCallAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound, DWORD offset, DWORD range) +{ return findWordCall(word_call, funcAddr, lowerBound, upperBound, offset, range ? range : (upperBound - lowerBound - offset)); } + +DWORD findNearCallAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound, DWORD offset, DWORD range) +{ return findByteCall(byte_call, funcAddr, lowerBound, offset, range ? range : (upperBound - lowerBound - offset)); } + +DWORD findLastLongJumpAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound, DWORD offset, DWORD range) +{ return findLastWordCall(word_jmp, funcAddr, lowerBound, upperBound, offset, range ? range : (upperBound - lowerBound - offset)); } + +DWORD findLastShortJumpAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound, DWORD offset, DWORD range) +{ return findLastByteCall(byte_jmp, funcAddr, lowerBound, offset, range ? range : (upperBound - lowerBound - offset)); } + +DWORD findLastFarCallAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound, DWORD offset, DWORD range) +{ return findLastWordCall(word_call, funcAddr, lowerBound, upperBound, offset, range ? range : (upperBound - lowerBound - offset)); } + +DWORD findLastNearCallAddress(DWORD funcAddr, DWORD lowerBound, DWORD upperBound, DWORD offset, DWORD range) +{ return findLastByteCall(byte_call, funcAddr, lowerBound, offset, range ? range : (upperBound - lowerBound - offset)); } DWORD findPushDwordAddress(DWORD value, DWORD lowerBound, DWORD upperBound) { @@ -322,9 +450,8 @@ DWORD findPushByteAddress(BYTE value, DWORD lowerBound, DWORD upperBound) return findBytes(bytes, sizeof(bytes), lowerBound, upperBound); } -DWORD findCallerAddress(DWORD funcAddr, DWORD sig, DWORD lowerBound, DWORD upperBound, DWORD reverseLength) +DWORD findCallerAddress(DWORD funcAddr, DWORD sig, DWORD lowerBound, DWORD upperBound, DWORD reverseLength, DWORD offset) { - enum { Start = 0x1000 }; enum { PatternSize = 4 }; const DWORD size = upperBound - lowerBound - PatternSize; const DWORD fun = (DWORD)funcAddr; @@ -332,9 +459,9 @@ DWORD findCallerAddress(DWORD funcAddr, DWORD sig, DWORD lowerBound, DWORD upper // 00449063 |. ff15 5cf05300 call dword ptr ds:[<&gdi32.getglyphoutli>; \GetGlyphOutlineA //WCHAR str[0x40]; const DWORD mask = sigMask(sig); - for (DWORD i = Start; i < size; i++) + for (DWORD i = offset; i < size; i++) if (*(WORD *)(lowerBound + i) == word_call) { - DWORD t = *(DWORD *)(lowerBound + i + 2); + DWORD t = *(DWORD *)(lowerBound + i + 2); // 2 = sizeof(word) if (t >= lowerBound && t <= upperBound - PatternSize) { if (*(DWORD *)t == fun) //swprintf(str,L"CALL addr: 0x%.8X",lowerBound + i); @@ -352,9 +479,154 @@ DWORD findCallerAddress(DWORD funcAddr, DWORD sig, DWORD lowerBound, DWORD upper return 0; } -DWORD findMultiCallerAddress(DWORD funcAddr, const DWORD sigs[], DWORD sigCount, DWORD lowerBound, DWORD upperBound, DWORD reverseLength) +#ifndef MEMDBG_NO_STL + +bool iterFindBytes(const address_fun_t &fun, const void *pattern, DWORD patternSize, DWORD lowerBound, DWORD upperBound) +{ + for (DWORD addr = lowerBound; addr < upperBound - patternSize; addr += patternSize) { + addr = findBytes(pattern, patternSize, addr, upperBound); + if (!addr || !fun(addr)) + return false; + } + return true; +} + +bool iterMatchBytes(const address_fun_t &fun, const void *pattern, DWORD patternSize, DWORD lowerBound, DWORD upperBound) +{ + for (DWORD addr = lowerBound; addr < upperBound - patternSize; addr += patternSize) { ; + addr = matchBytes(pattern, patternSize, addr, upperBound); + if (!addr || !fun(addr)) + return false; + } + return true; +} + +bool iterWordCall(const address_fun_t &callback, WORD op, DWORD arg1, DWORD start, DWORD stop, DWORD offset, DWORD range) +{ + typedef WORD optype; + typedef DWORD argtype; + + for (DWORD i = offset; i < offset + range - sizeof(argtype); i++) + if (op == *(optype *)(start + i)) { + DWORD t = *(DWORD *)(start + i + sizeof(optype)); + if (t > start && t < stop) { + if (arg1 == *(argtype *)t // absolute address + && !callback(start + i)) + return false; + //i += sizeof(optype) + sizeof(argtype) - 1; // == 5 + } + } + return true; +} + +bool iterByteCall(const address_fun_t &callback, BYTE op, DWORD arg1, DWORD start, DWORD offset, DWORD range) +{ + typedef BYTE optype; + typedef DWORD argtype; + + for (DWORD i = offset; i < offset + range - sizeof(argtype); i++) + if (op == *(optype *)(start + i)) { + DWORD t = *(argtype *)(start + i + sizeof(optype)); + //if (t > start && t < stop) { + if (arg1 == t + start + i + sizeof(optype) + sizeof(argtype) // relative address + && !callback(start + i)) + return false; + //i += sizeof(optype) + sizeof(argtype) - 1; // == 4 + //} + } + return true; +} + +bool iterCallerAddress(const address2_fun_t &callback, DWORD funcAddr, DWORD sig, DWORD lowerBound, DWORD upperBound, DWORD reverseLength, DWORD offset) +{ + enum { PatternSize = 4 }; + const DWORD size = upperBound - lowerBound - PatternSize; + const DWORD fun = (DWORD)funcAddr; + // Example function call: + // 00449063 |. ff15 5cf05300 call dword ptr ds:[<&gdi32.getglyphoutli>; \GetGlyphOutlineA + //WCHAR str[0x40]; + const DWORD mask = sigMask(sig); + for (DWORD i = offset; i < size; i++) + if (*(WORD *)(lowerBound + i) == word_call) { + DWORD t = *(DWORD *)(lowerBound + i + 2); // 2 = sizeof(word) + if (t >= lowerBound && t <= upperBound - PatternSize) { + if (*(DWORD *)t == fun) + //swprintf(str,L"CALL addr: 0x%.8X",lowerBound + i); + //OutputConsole(str); + for (DWORD j = i ; j > i - reverseLength; j--) + if ((*(DWORD *)(lowerBound + j) & mask) == sig) { + if (!callback(lowerBound + j, lowerBound + i)) + return false; + break; + } + + } else + i += 6; + } + //OutputConsole(L"Find call and entry failed."); + return true; +} + +bool iterCallerAddressAfterInt3(const address2_fun_t &fun, dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize, dword_t offset) +{ + auto callback = [&fun](dword_t addr, dword_t call) -> bool { + while (byte_int3 == *(BYTE *)++addr); // skip leading int3 + return fun(addr, call); + }; + return iterCallerAddress(callback, funcAddr, word_2int3, lowerBound, upperBound, callerSearchSize, offset); +} + +bool iterUniqueCallerAddress(const address_fun_t &fun, dword_t funcAddr, dword_t funcInst, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize, dword_t offset) +{ + dword_t prevAddr = 0; + auto callback = [&fun, &prevAddr](dword_t addr, dword_t) -> bool { + if (prevAddr == addr) + return true; + prevAddr = addr; + return fun(addr); + }; + return iterCallerAddress(callback, funcAddr, funcInst, lowerBound, upperBound, callerSearchSize, offset); +} + +bool iterUniqueCallerAddressAfterInt3(const address_fun_t &fun, dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize, dword_t offset) +{ + auto callback = [&fun](dword_t addr) -> bool { + while (byte_int3 == *(BYTE *)++addr); // skip leading int3 + return fun(addr); + }; + return iterUniqueCallerAddress(callback, funcAddr, word_2int3, lowerBound, upperBound, callerSearchSize, offset); +} + +bool iterLongJumpAddress(const address_fun_t &fun, DWORD funcAddr, DWORD lowerBound, DWORD upperBound, DWORD offset, DWORD range) +{ return iterWordCall(fun, word_jmp, funcAddr, lowerBound, upperBound, offset, range ? range : (upperBound - lowerBound - offset)); } + +bool iterShortJumpAddress(const address_fun_t &fun, DWORD funcAddr, DWORD lowerBound, DWORD upperBound, DWORD offset, DWORD range) +{ return iterByteCall(fun, byte_jmp, funcAddr, lowerBound, offset, range ? range : (upperBound - lowerBound - offset)); } + +bool iterFarCallAddress(const address_fun_t &fun, DWORD funcAddr, DWORD lowerBound, DWORD upperBound, DWORD offset, DWORD range) +{ return iterWordCall(fun, word_call, funcAddr, lowerBound, upperBound, offset, range ? range : (upperBound - lowerBound - offset)); } + +bool iterNearCallAddress(const address_fun_t &fun, DWORD funcAddr, DWORD lowerBound, DWORD upperBound, DWORD offset, DWORD range) +{ return iterByteCall(fun, byte_call, funcAddr, lowerBound, offset, range ? range : (upperBound - lowerBound - offset)); } + +bool iterAlignedNearCallerAddress(const address_fun_t &fun, dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize, dword_t offset) +{ + dword_t prevAddr = 0; + auto callback = [&fun, &prevAddr, callerSearchSize](dword_t addr) -> bool { + if ((addr = findEnclosingAlignedFunction(addr, callerSearchSize)) + && prevAddr != addr) { + prevAddr = addr; + return fun(addr); + } + return true; + }; + return iterNearCallAddress(callback, funcAddr, lowerBound, upperBound, offset); +} + +#endif // MEMDBG_NO_STL + +DWORD findMultiCallerAddress(DWORD funcAddr, const DWORD sigs[], DWORD sigCount, DWORD lowerBound, DWORD upperBound, DWORD reverseLength, DWORD offset) { - enum { Start = 0x1000 }; enum { PatternSize = 4 }; const DWORD size = upperBound - lowerBound - PatternSize; const DWORD fun = (DWORD)funcAddr; @@ -367,9 +639,9 @@ DWORD findMultiCallerAddress(DWORD funcAddr, const DWORD sigs[], DWORD sigCount, for (DWORD k = 0; k < sigCount; k++) masks[k] = sigMask(sigs[k]); - for (DWORD i = Start; i < size; i++) + for (DWORD i = offset; i < size; i++) if (*(WORD *)(lowerBound + i) == word_call) { - DWORD t = *(DWORD *)(lowerBound + i + 2); + DWORD t = *(DWORD *)(lowerBound + i + 2); // 2 = sizeof(word) if (t >= lowerBound && t <= upperBound - PatternSize) { if (*(DWORD *)t == fun) //swprintf(str,L"CALL addr: 0x%.8X",lowerBound + i); @@ -391,16 +663,15 @@ DWORD findMultiCallerAddress(DWORD funcAddr, const DWORD sigs[], DWORD sigCount, return 0; } -DWORD findLastCallerAddress(DWORD funcAddr, DWORD sig, DWORD lowerBound, DWORD upperBound, DWORD reverseLength) +DWORD findLastCallerAddress(DWORD funcAddr, DWORD sig, DWORD lowerBound, DWORD upperBound, DWORD reverseLength, DWORD offset) { - enum { Start = 0x1000 }; enum { PatternSize = 4 }; const DWORD size = upperBound - lowerBound - PatternSize; const DWORD fun = (DWORD)funcAddr; //WCHAR str[0x40]; DWORD ret = 0; const DWORD mask = sigMask(sig); - for (DWORD i = Start; i < size; i++) + for (DWORD i = offset; i < size; i++) if (*(WORD *)(lowerBound + i) == word_call) { DWORD t = *(DWORD *)(lowerBound + i + 2); if (t >= lowerBound && t <= upperBound - PatternSize) { @@ -408,10 +679,12 @@ DWORD findLastCallerAddress(DWORD funcAddr, DWORD sig, DWORD lowerBound, DWORD u //swprintf(str,L"CALL addr: 0x%.8X",lowerBound + i); //OutputConsole(str); for (DWORD j = i ; j > i - reverseLength; j--) - if ((*(DWORD *)(lowerBound + j) & mask) == sig) // Fun entry 1. + if ((*(DWORD *)(lowerBound + j) & mask) == sig) { // Fun entry 1. //swprintf(str,L"Entry: 0x%.8X",lowerBound + j); //OutputConsole(str); ret = lowerBound + j; + break; + } } else i += 6; @@ -420,22 +693,36 @@ DWORD findLastCallerAddress(DWORD funcAddr, DWORD sig, DWORD lowerBound, DWORD u return ret; } -DWORD findCallerAddressAfterInt3(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize) +DWORD findCallerAddressAfterInt3(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize, dword_t offset) { - DWORD addr = findCallerAddress(funcAddr, word_2int3, lowerBound, upperBound, callerSearchSize); + DWORD addr = findCallerAddress(funcAddr, word_2int3, lowerBound, upperBound, callerSearchSize, offset); if (addr) while (byte_int3 == *(BYTE *)++addr); return addr; } -DWORD findLastCallerAddressAfterInt3(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize) +DWORD findLastCallerAddressAfterInt3(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize, dword_t offset) { - DWORD addr = findLastCallerAddress(funcAddr, word_2int3, lowerBound, upperBound, callerSearchSize); + DWORD addr = findLastCallerAddress(funcAddr, word_2int3, lowerBound, upperBound, callerSearchSize, offset); if (addr) while (byte_int3 == *(BYTE *)++addr); return addr; } +DWORD findAlignedNearCallerAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize, dword_t offset) +{ + if (DWORD addr = findNearCallAddress(funcAddr, lowerBound, upperBound, offset)) + return findEnclosingAlignedFunction(addr, callerSearchSize); + return 0; +} + +DWORD findLastAlignedNearCallerAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize, dword_t offset) +{ + if (DWORD addr = findLastCallerAddressAfterInt3(funcAddr, lowerBound, upperBound, callerSearchSize, offset)) + return findEnclosingAlignedFunction(addr, callerSearchSize); + return 0; +} + DWORD findEnclosingAlignedFunction(DWORD start, DWORD back_range) { start &= ~0xf; @@ -466,6 +753,31 @@ DWORD findEnclosingAlignedFunction(DWORD start, DWORD back_range) return 0; } +DWORD findEnclosingFunctionAfterDword(DWORD sig, DWORD start, DWORD back_range, DWORD step) +{ + start &= ~0xf; + for (DWORD i = start, j = start - back_range; i > j; i-=step) { // 0x10 is aligned + DWORD k = *(DWORD *)(i-4); // 4 = sizeof(DWORD) + if (k == sig) + return i; + } + return 0; +} + +DWORD findEnclosingFunctionBeforeDword(DWORD sig, DWORD start, DWORD back_range,DWORD step) +{ + DWORD addr = findEnclosingFunctionAfterDword(sig, start, back_range, step); + if (addr) + addr -= sizeof(DWORD); + return addr; +} + +DWORD findEnclosingFunctionAfterInt3(DWORD start, DWORD back_range, DWORD step) +{ return findEnclosingFunctionAfterDword(0xcccccccc, start, back_range, step); } + +DWORD findEnclosingFunctionAfterNop(DWORD start, DWORD back_range, DWORD step) +{ return findEnclosingFunctionAfterDword(0x90909090,start, back_range, step); } + DWORD findBytes(const void *pattern, DWORD patternSize, DWORD lowerBound, DWORD upperBound) { DWORD reladdr = searchPattern(lowerBound, upperBound - lowerBound, pattern, patternSize); @@ -478,6 +790,12 @@ DWORD matchBytes(const void *pattern, DWORD patternSize, DWORD lowerBound, DWORD return reladdr ? lowerBound + reladdr : 0; } +//DWORD reverseFindBytes(const void *pattern, DWORD patternSize, DWORD lowerBound, DWORD upperBound) +//{ +// DWORD reladdr = reverseSearchPattern(lowerBound, upperBound - lowerBound, pattern, patternSize); +// return reladdr ? lowerBound + reladdr : 0; +//} + #if 0 // not used DWORD findBytesInPages(const void *pattern, DWORD patternSize, DWORD lowerBound, DWORD upperBound, SearchType search) { @@ -552,81 +870,3 @@ DWORD matchBytesInPages(const void *pattern, DWORD patternSize, DWORD lowerBound MEMDBG_END_NAMESPACE // EOF - -#if 0 // disabled - -/** - * Search from stopAddres back to startAddress - range - * This function is not well debugged - */ -DWORD reverseSearchPattern(DWORD base, DWORD base_length, LPCVOID search, DWORD search_length) // KMP -{ - __asm - { - mov eax,search_length -alloc: - push 0 - sub eax,1 - jnz alloc - - mov edi,search - mov edx,search_length - mov ecx,1 - xor esi,esi -build_table: - mov al,byte ptr [edi+esi] - cmp al,byte ptr [edi+ecx] - sete al - test esi,esi - jz pre - test al,al - jnz pre - mov esi,[esp+esi*4-4] - jmp build_table -pre: - test al,al - jz write_table - inc esi -write_table: - mov [esp+ecx*4],esi - - inc ecx - cmp ecx,edx - jb build_table - - mov esi,base - xor edx,edx - mov ecx,edx -matcher: - mov al,byte ptr [edi+ecx] - cmp al,byte ptr [esi-edx] // jichi 6/1/2014: The only place that is modified - sete al - test ecx,ecx - jz match - test al,al - jnz match - mov ecx, [esp+ecx*4-4] - jmp matcher -match: - test al,al - jz pre2 - inc ecx - cmp ecx,search_length - je finish -pre2: - inc edx - cmp edx,base_length // search_length - jb matcher - mov edx,search_length - dec edx -finish: - mov ecx,search_length - sub edx,ecx - lea eax,[edx+1] - lea ecx,[ecx*4] - add esp,ecx - } -} - -#endif // 0, disabled - diff --git a/vnr/memdbg/memsearch.h b/vnr/memdbg/memsearch.h index 8bb15d0..5bd3132 100644 --- a/vnr/memdbg/memsearch.h +++ b/vnr/memdbg/memsearch.h @@ -5,14 +5,53 @@ // 4/20/2014 jichi #include "memdbg/memdbg.h" +#ifndef MEMDBG_NO_STL +# include +#endif // MEMDBG_NO_STL MEMDBG_BEGIN_NAMESPACE /// Estimated maximum size of the caller function, the same as ITH FindCallAndEntryAbs enum { MaximumFunctionSize = 0x800 }; +/// Offset added to the beginning of the searched address +enum { MemoryPaddingOffset = 0x1000 }; + +enum { MemoryAlignedStep = 0x10 }; + +#ifndef MEMDBG_NO_STL +/// Iterate address and return false if abort iteration. +typedef std::function address_fun_t; +typedef std::function address2_fun_t; + /** - * Return the absolute address of the caller function + * Iterate all call and caller addresses + * @param fun the first parameter is the address of the caller, and the second parameter is the address of the call itself + * @return false if return early, and true if iterate all elements + */ +bool iterCallerAddress(const address2_fun_t &fun, dword_t funcAddr, dword_t funcInst, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize, dword_t offset = MemoryPaddingOffset); +bool iterCallerAddressAfterInt3(const address2_fun_t &fun, dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize, dword_t offset = MemoryPaddingOffset); +bool iterUniqueCallerAddress(const address_fun_t &fun, dword_t funcAddr, dword_t funcInst, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize, dword_t offset = MemoryPaddingOffset); +bool iterUniqueCallerAddressAfterInt3(const address_fun_t &fun, dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize, dword_t offset = MemoryPaddingOffset); + +/** + * Iterate all call and caller addresses + * @param fun the parameter is the address of the call + * @return false if return early, and true if iterate all elements + */ +bool iterFarCallAddress(const address_fun_t &fun, dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0); +bool iterNearCallAddress(const address_fun_t &fun, dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0); +bool iterLongJumpAddress(const address_fun_t &fun, dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0); +bool iterShortJumpAddress(const address_fun_t &fun, dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0); + +bool iterAlignedNearCallerAddress(const address_fun_t &fun, dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize, dword_t offset = MemoryPaddingOffset); + +bool iterFindBytes(const address_fun_t &fun, const void *pattern, dword_t patternSize, dword_t lowerBound, dword_t upperBound); +bool iterMatchBytes(const address_fun_t &fun, const void *pattern, dword_t patternSize, dword_t lowerBound, dword_t upperBound); +#endif // MEMDBG_NO_STL + +/** + * Return the absolute address of the far caller function * The same as ITH FindCallAndEntryAbs(). * * @param funcAddr callee function address @@ -27,12 +66,15 @@ enum { MaximumFunctionSize = 0x800 }; * 0x81,0xec: sub esp XXOO (0xec81) * 0x83,0xec: sub esp XXOO (0xec83) */ -dword_t findCallerAddress(dword_t funcAddr, dword_t funcInst, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize); -dword_t findCallerAddressAfterInt3(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize); -dword_t findLastCallerAddress(dword_t funcAddr, dword_t funcInst, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize); -dword_t findLastCallerAddressAfterInt3(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize); +dword_t findCallerAddress(dword_t funcAddr, dword_t funcInst, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize, dword_t offset = MemoryPaddingOffset); +dword_t findCallerAddressAfterInt3(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize, dword_t offset = MemoryPaddingOffset); +dword_t findLastCallerAddress(dword_t funcAddr, dword_t funcInst, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize, dword_t offset = MemoryPaddingOffset); +dword_t findLastCallerAddressAfterInt3(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize, dword_t offset = MemoryPaddingOffset); -dword_t findMultiCallerAddress(dword_t funcAddr, const dword_t funcInsts[], dword_t funcInstCount, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize); +dword_t findMultiCallerAddress(dword_t funcAddr, const dword_t funcInsts[], dword_t funcInstCount, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize, dword_t offset = MemoryPaddingOffset); + +dword_t findAlignedNearCallerAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize, dword_t offset = MemoryPaddingOffset); +dword_t findLastAlignedNearCallerAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t callerSearchSize = MaximumFunctionSize, dword_t offset = MemoryPaddingOffset); /** * Return the absolute address of the long jump (not short jump) instruction address. @@ -41,9 +83,14 @@ dword_t findMultiCallerAddress(dword_t funcAddr, const dword_t funcInsts[], dwor * @param funcAddr callee function address * @param lowerBound the lower memory address to search * @param upperBound the upper memory address to search + * @param* offset the relative address to search from the lowerBound + * @param* range the relative size to search, use lowerBound - upperBound when zero * @return the call instruction address if succeed or 0 if fail */ -dword_t findJumpAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound); +dword_t findLongJumpAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0); +dword_t findShortJumpAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0); +dword_t findLastLongJumpAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0); +dword_t findLastShortJumpAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0); /** * Return the absolute address of the far call (inter-module) instruction address. @@ -52,16 +99,28 @@ dword_t findJumpAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound * @param funcAddr callee function address * @param lowerBound the lower memory address to search * @param upperBound the upper memory address to search + * @param* offset the relative address to search from the lowerBound + * @param* range the relative size to search, use lowerBound - upperBound when zero * @return the call instruction address if succeed or 0 if fail */ -dword_t findFarCallAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound); +dword_t findFarCallAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0); +dword_t findLastFarCallAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0); /// Near call (intra-module) -dword_t findNearCallAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound); +dword_t findNearCallAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0); +dword_t findLastNearCallAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0); -/// Default to far call -inline dword_t findCallAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound) -{ return findFarCallAddress(funcAddr, lowerBound, upperBound); } +/// Default to far call, for backward compatibility +inline dword_t findCallAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0) +{ return findFarCallAddress(funcAddr, lowerBound, upperBound, offset, range); } +inline dword_t findLastCallAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0) +{ return findLastFarCallAddress(funcAddr, lowerBound, upperBound, offset, range); } + +/// Default to long jump, for backward compatibility +inline dword_t findJumpAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0) +{ return findLongJumpAddress(funcAddr, lowerBound, upperBound, offset, range); } +inline dword_t findLastJumpAddress(dword_t funcAddr, dword_t lowerBound, dword_t upperBound, dword_t offset = MemoryPaddingOffset, dword_t range = 0) +{ return findLastLongJumpAddress(funcAddr, lowerBound, upperBound, offset, range); } /// Push value >= 0xff dword_t findPushDwordAddress(dword_t value, dword_t lowerBound, dword_t upperBound); @@ -87,6 +146,10 @@ inline dword_t findPushAddress(dword_t value, dword_t lowerBound, dword_t upperB * @exception illegal memory access */ dword_t findEnclosingAlignedFunction(dword_t addr, dword_t searchSize = MaximumFunctionSize); +dword_t findEnclosingFunctionBeforeDword(dword_t sig, dword_t addr, dword_t searchSize = MaximumFunctionSize, dword_t step = MemoryAlignedStep); +dword_t findEnclosingFunctionAfterDword(dword_t sig, dword_t addr, dword_t searchSize = MaximumFunctionSize, dword_t step = MemoryAlignedStep); +dword_t findEnclosingFunctionAfterInt3(dword_t addr, dword_t searchSize = MaximumFunctionSize, dword_t step = MemoryAlignedStep); +dword_t findEnclosingFunctionAfterNop(dword_t addr, dword_t searchSize = MaximumFunctionSize, dword_t step = MemoryAlignedStep); /** * Return the address of the first matched pattern. @@ -102,6 +165,7 @@ dword_t findEnclosingAlignedFunction(dword_t addr, dword_t searchSize = MaximumF * @exception illegal memory access */ dword_t findBytes(const void *pattern, dword_t patternSize, dword_t lowerBound, dword_t upperBound); +//dword_t reverseFindBytes(const void *pattern, dword_t patternSize, dword_t lowerBound, dword_t upperBound); /** * jichi 2/5/2014: The same as findBytes except it uses widecard to match everything. diff --git a/vnr/mono/mono.pri b/vnr/mono/mono.pri new file mode 100644 index 0000000..69ae806 --- /dev/null +++ b/vnr/mono/mono.pri @@ -0,0 +1,12 @@ +# mono.pri +# 9/26/2012 jichi + +DEFINES += WITH_LIB_MONO + +DEPENDPATH += $$PWD + +HEADERS += \ + $$PWD/monoobject.h \ + $$PWD/monotype.h + +# EOF diff --git a/vnr/mono/monoobject.h b/vnr/mono/monoobject.h new file mode 100644 index 0000000..b8d6307 --- /dev/null +++ b/vnr/mono/monoobject.h @@ -0,0 +1,48 @@ +#ifndef MONOOBJECT_H +#define MONOOBJECT_H + +// monoobject.h +// 12/26/2014 jichi +// https://github.com/mono/mono/blob/master/mono/metadata/object.h +// https://github.com/mono/mono/blob/master/mono/metadata/object-internals.h +// https://github.com/mono/mono/blob/master/mono/util/mono-publib.h + +#include + +#define MONO_ZERO_LEN_ARRAY 1 + +// mono/io-layer/uglify.h +//typedef int8_t gint8; +//typedef int32_t gint32; +//typedef wchar_t gunichar2; // either char or wchar_t, depending on how mono is compiled + +typedef int32_t mono_bool; +typedef uint8_t mono_byte; +typedef uint16_t mono_unichar2; +typedef uint32_t mono_unichar4; + +// mono/metadata/object.h + +typedef mono_bool MonoBoolean; + +struct MonoArray; +struct MonoDelegate; +struct MonoDomain; +struct MonoException; +struct MonoString; +struct MonoThreadsSync; +struct MonoThread; +struct MonoVTable; + +struct MonoObject { + MonoVTable *vtable; + MonoThreadsSync *synchronisation; +}; + +struct MonoString { + MonoObject object; + int32_t length; + mono_unichar2 chars[MONO_ZERO_LEN_ARRAY]; +}; + +#endif // MONOOBJECT_H diff --git a/vnr/mono/monotype.h b/vnr/mono/monotype.h new file mode 100644 index 0000000..df3d6af --- /dev/null +++ b/vnr/mono/monotype.h @@ -0,0 +1,17 @@ +#ifndef MONOTYPE_H +#define MONOTYPE_H + +// monotype.h +// 12/26/2014 jichi +// https://github.com/mono/mono/blob/master/mono/metadata/object.h + +#include "mono/monoobject.h" + +// Function typedefs +typedef MonoDomain *(* mono_object_get_domain_fun_t)(MonoObject *obj); + +typedef MonoString *(* mono_string_new_utf16_fun_t)(MonoDomain *domain, const mono_unichar2 *text, int32_t len); + +typedef char * (* mono_string_to_utf8_fun_t)(MonoString *string_obj); + +#endif // MONOTYPE_H diff --git a/vnr/ntinspect/ntinspect.cc b/vnr/ntinspect/ntinspect.cc index 69d49d4..935c8e9 100644 --- a/vnr/ntinspect/ntinspect.cc +++ b/vnr/ntinspect/ntinspect.cc @@ -3,11 +3,15 @@ #include "ntdll/ntdll.h" #include "ntinspect/ntinspect.h" +// https://social.msdn.microsoft.com/Forums/vstudio/en-US/4cb11cd3-8ce0-49d7-9dda-d62e9ae0180b/how-to-get-current-module-handle?forum=vcgeneral +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + //#ifdef _MSC_VER //# pragma warning(disable:4018) // C4018: signed/unsigned mismatch //#endif // _MSC_VER namespace { // unnamed + // Replacement of wcscpy_s which is not available on Windows XP's msvcrt // http://sakuradite.com/topic/247 errno_t wcscpy_safe(wchar_t *buffer, size_t bufferSize, const wchar_t *source) @@ -22,7 +26,12 @@ errno_t wcscpy_safe(wchar_t *buffer, size_t bufferSize, const wchar_t *source) NTINSPECT_BEGIN_NAMESPACE -BOOL getCurrentProcessName(LPWSTR buffer, int bufferSize) +// https://social.msdn.microsoft.com/Forums/vstudio/en-US/4cb11cd3-8ce0-49d7-9dda-d62e9ae0180b/how-to-get-current-module-handle?forum=vcgeneral +HMODULE getCurrentModuleHandle() { return (HMODULE)&__ImageBase; } + +/** Memory range */ + +BOOL getProcessName(LPWSTR buffer, int bufferSize) { //assert(name); PLDR_DATA_TABLE_ENTRY it; @@ -38,6 +47,7 @@ BOOL getCurrentProcessName(LPWSTR buffer, int bufferSize) return 0 == wcscpy_safe(buffer, bufferSize, it->BaseDllName.Buffer); } +// See: ITH FillRange BOOL getModuleMemoryRange(LPCWSTR moduleName, DWORD *lowerBound, DWORD *upperBound) { //assert(lower); @@ -86,15 +96,114 @@ BOOL getModuleMemoryRange(LPCWSTR moduleName, DWORD *lowerBound, DWORD *upperBou return FALSE; } -BOOL getCurrentMemoryRange(DWORD *lowerBound, DWORD *upperBound) +BOOL getProcessMemoryRange(DWORD *lowerBound, DWORD *upperBound) { WCHAR procName[MAX_PATH]; // cached *lowerBound = 0; *upperBound = 0; - return getCurrentProcessName(procName, MAX_PATH) + return getProcessName(procName, MAX_PATH) && getModuleMemoryRange(procName, lowerBound, upperBound); } +/** Module header */ + +// See: ITH AddAllModules +bool iterModule(const iter_module_fun_t &fun) +{ + // Iterate loaded modules + PPEB ppeb; + __asm { + mov eax, fs:[0x30] + mov ppeb, eax + } + const DWORD start = *(DWORD *)&ppeb->Ldr->InLoadOrderModuleList; + for (auto it = (PLDR_DATA_TABLE_ENTRY)start; + it->SizeOfImage && *(DWORD *)it != start; + it = (PLDR_DATA_TABLE_ENTRY)it->InLoadOrderModuleList.Flink) + if (!fun((HMODULE)it->DllBase, it->BaseDllName.Buffer)) + return false; + return true; +} + + +// See: ITH AddAllModules +DWORD getExportFunction(LPCSTR funcName) +{ + // Iterate loaded modules + PPEB ppeb; + __asm { + mov eax, fs:[0x30] + mov ppeb, eax + } + const DWORD start = *(DWORD *)&ppeb->Ldr->InLoadOrderModuleList; + for (auto it = (PLDR_DATA_TABLE_ENTRY)start; + it->SizeOfImage && *(DWORD *)it != start; + it = (PLDR_DATA_TABLE_ENTRY)it->InLoadOrderModuleList.Flink) { + //if (moduleName && ::wcscmp(moduleName, it->BaseDllName.Buffer)) // BaseDllName.Buffer == moduleName + // continue; + if (DWORD addr = getModuleExportFunction((HMODULE)it->DllBase, funcName)) + return addr; + } + return 0; +} + +// See: ITH AddModule +DWORD getModuleExportFunction(HMODULE hModule, LPCSTR funcName) +{ + if (!hModule) + return 0; + DWORD startAddress = (DWORD)hModule; + IMAGE_DOS_HEADER *DosHdr = (IMAGE_DOS_HEADER *)hModule; + if (IMAGE_DOS_SIGNATURE == DosHdr->e_magic) { + DWORD dwReadAddr = startAddress + DosHdr->e_lfanew; + IMAGE_NT_HEADERS *NtHdr = (IMAGE_NT_HEADERS *)dwReadAddr; + if (IMAGE_NT_SIGNATURE == NtHdr->Signature) { + DWORD dwExportAddr = NtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (dwExportAddr == 0) + return 0; + dwExportAddr += startAddress; + IMAGE_EXPORT_DIRECTORY *ExtDir = (IMAGE_EXPORT_DIRECTORY *)dwExportAddr; + dwExportAddr = startAddress + ExtDir->AddressOfNames; + for (UINT uj = 0; uj < ExtDir->NumberOfNames; uj++) { + DWORD dwFuncName = *(DWORD *)dwExportAddr; + LPCSTR pcFuncName = (LPCSTR)(startAddress + dwFuncName); + if (::strcmp(funcName, pcFuncName) == 0) { + char *pcFuncPtr = (char *)(startAddress + (DWORD)ExtDir->AddressOfNameOrdinals+(uj * sizeof(WORD))); + WORD word = *(WORD *)pcFuncPtr; + pcFuncPtr = (char *)(startAddress + (DWORD)ExtDir->AddressOfFunctions+(word * sizeof(DWORD))); + return startAddress + *(DWORD *)pcFuncPtr; // absolute address + } + dwExportAddr += sizeof(DWORD); + } + } + } + return 0; +} + +// See: ITH FindImportEntry +DWORD getModuleImportAddress(HMODULE hModule, DWORD exportAddress) +{ + if (!hModule) + return 0; + DWORD startAddress = (DWORD)hModule; + IMAGE_DOS_HEADER *DosHdr = (IMAGE_DOS_HEADER *)hModule; + if (IMAGE_DOS_SIGNATURE == DosHdr->e_magic) { + IMAGE_NT_HEADERS *NtHdr = (IMAGE_NT_HEADERS *)(startAddress + DosHdr->e_lfanew); + if (IMAGE_NT_SIGNATURE == NtHdr->Signature) { + DWORD IAT = NtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress; + DWORD end = NtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size; + IAT += startAddress; + end += IAT; + for (DWORD pt = IAT; pt < end; pt += 4) { + DWORD addr = *(DWORD *)pt; + if (addr == (DWORD)exportAddress) + return pt; + } + } + } + return 0; +} + NTINSPECT_END_NAMESPACE // EOF diff --git a/vnr/ntinspect/ntinspect.h b/vnr/ntinspect/ntinspect.h index 6cfb0c5..a4c3d4e 100644 --- a/vnr/ntinspect/ntinspect.h +++ b/vnr/ntinspect/ntinspect.h @@ -4,6 +4,9 @@ // 4/20/2014 jichi #include +#ifndef MEMDBG_NO_STL +# include +#endif // MEMDBG_NO_STL #ifndef NTINSPECT_BEGIN_NAMESPACE # define NTINSPECT_BEGIN_NAMESPACE namespace NtInspect { @@ -14,17 +17,73 @@ NTINSPECT_BEGIN_NAMESPACE +// Get the module handle of the current module (not the current process that is GetModuleHandleA(0)) +HMODULE getCurrentModuleHandle(); + /// Get current module name in fs:0x30 -BOOL getCurrentProcessName(_Out_ LPWSTR buffer, _In_ int bufferSize); +BOOL getProcessName(_Out_ LPWSTR buffer, _In_ int bufferSize); /** * Get the memory range of the module if succeed - * See: ITH FillRange + * @param moduleName + * @param[out[ lowerBound + * @param[out] upperBound + * @return if succeed */ BOOL getModuleMemoryRange(_In_ LPCWSTR moduleName, _Out_ DWORD *lowerBound, _Out_ DWORD *upperBound); -/// Get memory of the current process -BOOL getCurrentMemoryRange(_Out_ DWORD *lowerBound, _Out_ DWORD *upperBound); +/// Get memory of the current process module +BOOL getProcessMemoryRange(_Out_ DWORD *lowerBound, _Out_ DWORD *upperBound); + +#ifndef NTINSPECT_NO_STL +/// Iterate module information and return false if abort iteration. +typedef std::function iter_module_fun_t; +#else +typedef bool (* iter_module_fun_t)(HMODULE hModule, LPCWSTR moduleName); +#endif // NTINSPECT_NO_STL + +/** + * Iterate all modules + * @param fun the first parameter is the address of the caller, and the second parameter is the address of the call itself + * @return false if return early, and true if iterate all elements + */ +bool iterModule(const iter_module_fun_t &fun); + +/** + * Return the absolute address of the function imported from the given module + * @param functionName + * @param* hModule find from any module when null + * @return function address or 0 + */ +DWORD getModuleExportFunction(HMODULE hModule, LPCSTR functionName); + +inline DWORD getModuleExportFunctionA(LPCSTR moduleName, LPCSTR functionName) +{ return getModuleExportFunction(::GetModuleHandleA(moduleName), functionName); } + +inline DWORD getModuleExportFunctionW(LPCWSTR moduleName, LPCSTR functionName) +{ return getModuleExportFunction(::GetModuleHandleW(moduleName), functionName); } + +/// Get the function address exported from any module +DWORD getExportFunction(LPCSTR functionName); + +/** + * Get the import address in the specified module + * @param hModule + * @param exportAddress absolute address of the function exported from other modules + * @return function address or 0 + */ +DWORD getModuleImportAddress(HMODULE hModule, DWORD exportAddress); + +inline DWORD getModuleImportAddressA(LPCSTR moduleName, DWORD exportAddress) +{ return getModuleImportAddress(::GetModuleHandleA(moduleName), exportAddress); } + +inline DWORD getModuleImportAddressW(LPCWSTR moduleName, DWORD exportAddress) +{ return getModuleImportAddress(::GetModuleHandleW(moduleName), exportAddress); } + +/// Get the import address in the current executable +inline DWORD getProcessImportAddress(DWORD exportAddress) +{ return getModuleImportAddress(::GetModuleHandleA(nullptr), exportAddress); } + NTINSPECT_END_NAMESPACE diff --git a/vnr/profile/CMakeLists.txt b/vnr/profile/CMakeLists.txt new file mode 100644 index 0000000..152afb4 --- /dev/null +++ b/vnr/profile/CMakeLists.txt @@ -0,0 +1,23 @@ +set(profile_src + Profile.h + Profile.cpp + pugiconfig.hpp + pugixml.cpp + pugixml.hpp + misc.h + misc.cpp +) + +add_library(profile STATIC ${profile_src}) + +target_compile_options(profile PRIVATE + $<$:> + $<$:> +) + +#target_link_libraries(profile comctl32.lib) + +#target_compile_definitions(profile +# PRIVATE +# _CRT_NON_CONFORMING_SWPRINTFS +#) diff --git a/vnr/profile/Profile.cpp b/vnr/profile/Profile.cpp new file mode 100644 index 0000000..7323f83 --- /dev/null +++ b/vnr/profile/Profile.cpp @@ -0,0 +1,293 @@ +/* Copyright (C) 2010-2012 kaosu (qiupf2000@gmail.com) + * This file is part of the Interactive Text Hooker. + + * Interactive Text Hooker is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "host/host.h" +#include "host/hookman.h" +#include "vnrhook/include/types.h" +#include "vnrhook/include/const.h" +#include "Profile.h" +#include "misc.h" +#include +#include "pugixml.hpp" + +extern HookManager* man; + +Profile::Profile(const std::wstring& title) : +select_index(-1), +title(title) +{} + +const std::unordered_set& Profile::Hooks() const +{ + return hooks; +} + +const std::vector& Profile::Threads() const +{ + return threads; +} + +const std::unordered_set& Profile::Links() const +{ + return links; +} + +bool Profile::XmlReadProfile(pugi::xml_node profile) +{ + auto hooks_node = profile.child(L"Hooks"); + auto threads_node = profile.child(L"Threads"); + auto links_node = profile.child(L"Links"); + if (hooks_node && !XmlReadProfileHook(hooks_node)) + return false; + if (threads_node && !XmlReadProfileThread(threads_node)) + return false; + if (links_node && !XmlReadProfileLink(links_node)) + return false; + auto select_node = profile.child(L"Select"); + if (select_node) + { + auto thread_index = select_node.attribute(L"ThreadIndex"); + if (!thread_index) + return false; + DWORD tmp_select = std::stoul(thread_index.value(), NULL, 16); + select_index = tmp_select & 0xFFFF; + } + return true; +} + +bool Profile::XmlReadProfileHook(pugi::xml_node hooks_node) +{ + for (auto hook = hooks_node.begin(); hook != hooks_node.end(); ++hook) + { + std::wstring name = hook->name(); + if (name.empty() || name.compare(L"Hook") != 0) + return false; + auto type = hook->attribute(L"Type"); + if (!type || type.empty()) + return false; + auto code = hook->attribute(L"Code"); + if (!code) + return false; + std::wstring code_value = code.value(); + HookParam hp = {}; + switch (type.value()[0]) + { + case L'H': + if (code_value[0] != L'/') + return false; + if (code_value[1] != L'H' && code_value[1] != L'h') + return false; + if (Parse(code_value.substr(2), hp)) + { + auto name = hook->attribute(L"Name"); + if (!name || name.empty()) + AddHook(hook_ptr(new HookProfile(hp, L""))); + else + AddHook(hook_ptr(new HookProfile(hp, name.value()))); + } + break; + default: + return false; + } + } + return true; +} + +bool Profile::XmlReadProfileThread(pugi::xml_node threads_node) +{ + std::wstring hook_name_buffer; + for (auto thread = threads_node.begin(); thread != threads_node.end(); ++thread) + { + std::wstring name = thread->name(); + if (name.empty() || name.compare(L"Thread") != 0) + return false; + auto hook_name = thread->attribute(L"HookName"); + if (!hook_name) + return false; + auto context = thread->attribute(L"Context"); + if (!context) + return false; + auto sub_context = thread->attribute(L"SubContext"); + if (!sub_context) + return false; + auto mask = thread->attribute(L"Mask"); + if (!mask) + return false; + DWORD mask_tmp = std::stoul(mask.value(), NULL, 16); + auto comment = thread->attribute(L"Comment"); + auto retn = std::stoul(context.value(), NULL, 16); + auto split = std::stoul(sub_context.value(), NULL, 16); + WORD flags = mask_tmp & 0xFFFF; + auto tp = new ThreadProfile(hook_name.value(), retn, split, 0, 0, flags, + comment.value()); + AddThread(thread_ptr(tp)); + } + return true; +} + +bool Profile::XmlReadProfileLink(pugi::xml_node links_node) +{ + for (auto link = links_node.begin(); link != links_node.end(); ++link) + { + std::wstring name = link->name(); + if (name.empty() || name.compare(L"Link") != 0) + return false; + auto from = link->attribute(L"From"); + if (!from) + return false; + DWORD link_from = std::stoul(from.value(), NULL, 16); + auto to = link->attribute(L"To"); + if (!to) + return false; + DWORD link_to = std::stoul(to.value(), NULL, 16); + auto lp = new LinkProfile(link_from & 0xFFFF, link_to & 0xFFFF); + AddLink(link_ptr(lp)); + } + return true; +} + +bool Profile::XmlWriteProfile(pugi::xml_node profile_node) +{ + if (!hooks.empty()) + { + auto node = profile_node.append_child(L"Hooks"); + XmlWriteProfileHook(node); + } + if (!threads.empty()) + { + auto node = profile_node.append_child(L"Threads"); + XmlWriteProfileThread(node); + } + if (!links.empty()) + { + auto node = profile_node.append_child(L"Links"); + XmlWriteProfileLink(node); + } + if (select_index != 0xFFFF) + { + auto node = profile_node.append_child(L"Select"); + node.append_attribute(L"ThreadIndex") = select_index; + } + return true; +} + +bool Profile::XmlWriteProfileHook(pugi::xml_node hooks_node) +{ + for (auto hook = hooks.begin(); hook != hooks.end(); ++hook) + { + auto hook_node = hooks_node.append_child(L"Hook"); + hook_node.append_attribute(L"Type") = L"H"; + hook_node.append_attribute(L"Code") = ParseCode((*hook)->HP()).c_str(); + if (!(*hook)->Name().empty()) + hook_node.append_attribute(L"Name") = (*hook)->Name().c_str(); + } + return true; +} + +bool Profile::XmlWriteProfileThread(pugi::xml_node threads_node) +{ + for (auto thread = threads.begin(); thread != threads.end(); ++thread) + { + const std::wstring& name = (*thread)->HookName(); + if (name.empty()) + return false; + auto node = threads_node.append_child(L"Thread"); + node.append_attribute(L"HookName") = name.c_str(); + std::wstring hex = ToHexString((*thread)->Flags() & (THREAD_MASK_RETN | THREAD_MASK_SPLIT)); + node.append_attribute(L"Mask") = hex.c_str(); + hex = ToHexString((*thread)->Split()); + node.append_attribute(L"SubContext") = hex.c_str(); + hex = ToHexString((*thread)->Return()); + node.append_attribute(L"Context") = hex.c_str(); + if (!(*thread)->Comment().empty()) + node.append_attribute(L"Comment") = (*thread)->Comment().c_str(); + } + return true; +} + +bool Profile::XmlWriteProfileLink(pugi::xml_node links_node) +{ + for (auto link = links.begin(); link != links.end(); ++link) + { + auto node = links_node.append_child(L"Link"); + node.append_attribute(L"From") = ToHexString((*link)->FromIndex()).c_str(); + node.append_attribute(L"To") = ToHexString((*link)->ToIndex()).c_str(); + } + return true; +} + +void Profile::Clear() +{ + title = L""; + select_index = -1; + hooks.clear(); + threads.clear(); + links.clear(); +} + +void Profile::AddHook(hook_ptr hook) +{ + hooks.insert(std::move(hook)); +} + +// add the thread profile and return its index +int Profile::AddThread(thread_ptr tp) +{ + auto it = std::find_if(threads.begin(), threads.end(), [&tp](thread_ptr& thread) + { + return thread->HookName().compare(tp->HookName()) == 0 && + thread->Return() == tp->Return() && + thread->Split() == tp->Split(); + }); + if (it != threads.end()) + return it - threads.begin(); + threads.push_back(std::move(tp)); + return threads.size() - 1; +} + +void Profile::AddLink(link_ptr link) +{ + links.insert(std::move(link)); +} + +const std::wstring& Profile::Title() const +{ + return title; +} + +bool Profile::IsThreadSelected(thread_ptr_iter thread_profile) +{ + if (thread_profile != threads.end()) + { + auto thread_index = thread_profile - threads.begin(); + return select_index == thread_index; + } + return false; +} + +thread_ptr_iter Profile::FindThread(const ThreadParameter* tp, const std::wstring& hook_name) const +{ + auto thread_profile = std::find_if(threads.begin(), threads.end(), + [&tp, &hook_name](const thread_ptr& thread_profile) -> bool + { + return thread_profile->HookName().compare(hook_name) == 0 + && thread_profile->Return() == tp->retn + && thread_profile->Split() == tp->spl; + }); + return thread_profile; +} diff --git a/vnr/profile/Profile.h b/vnr/profile/Profile.h new file mode 100644 index 0000000..72334e9 --- /dev/null +++ b/vnr/profile/Profile.h @@ -0,0 +1,171 @@ +/* Copyright (C) 2010-2012 kaosu (qiupf2000@gmail.com) + * This file is part of the Interactive Text Hooker. + + * Interactive Text Hooker is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "vnrhook/include/types.h" // HookParam +#include +#include +#include +#include + +struct ThreadParameter; +class TextThread; +class HookProfile; +class ThreadProfile; +class LinkProfile; +typedef std::unique_ptr hook_ptr; +typedef std::unique_ptr thread_ptr; +typedef std::unique_ptr link_ptr; +typedef std::vector::const_iterator thread_ptr_iter; +namespace pugi { + class xml_node; +} + +#define THREAD_MASK_RETN 1 +#define THREAD_MASK_SPLIT 2 + +class HookProfile +{ + HookParam hp; + std::wstring name; +public: + HookProfile(const HookParam& hp, const std::wstring& name) : + hp(hp), + name(name) + {} + const HookParam& HP() const { return hp; }; + const std::wstring& Name() const { return name; }; +}; + +class ThreadProfile +{ + std::wstring hook_name; + DWORD retn; + DWORD split; + DWORD hook_addr; + WORD hm_index, flags; + std::wstring comment; +public: + ThreadProfile(const std::wstring& hook_name, + DWORD retn, + DWORD split, + DWORD hook_addr, + WORD hm_index, + WORD flags, + const std::wstring& comment) : + hook_name(hook_name), + retn(retn), + split(split), + hook_addr(hook_addr), + hm_index(hm_index), + flags(flags), + comment(comment) + { + } + const std::wstring& HookName() const { return hook_name; } + const std::wstring& Comment() const { return comment; } + DWORD Return() const { return retn; } + DWORD Split() const { return split; } + DWORD& HookAddress() { return hook_addr; } + WORD& HookManagerIndex() { return hm_index; } + WORD Flags() const { return flags; } +}; + +class LinkProfile +{ + WORD from_index, to_index; +public: + LinkProfile(WORD from_index, WORD to_index) : + from_index(from_index), + to_index(to_index) + {} + WORD FromIndex() const { return from_index; } + WORD ToIndex() const { return to_index; } +}; + +namespace std { + template<> + struct hash { + size_t operator()(const hook_ptr &r) const + { + return hash{}(r->HP().address) + ^ hash{}(r->HP().module) + ^ hash{}(r->HP().function); + } + }; + template<> + struct equal_to { + bool operator()(const hook_ptr& r, const hook_ptr& r2) const + { + return r->HP().address == r2->HP().address + && r->HP().module == r2->HP().module + && r->HP().function == r2->HP().function; + } + }; + + template<> + struct hash { + size_t operator()(const link_ptr &r) const + { + return hash{}(r->FromIndex()) + ^ hash{}(r->ToIndex()); + } + }; + template<> + struct equal_to { + bool operator()(const link_ptr& r, const link_ptr& r2) const + { + return r->FromIndex() == r2->FromIndex() + && r->ToIndex() == r2->ToIndex(); + } + }; +} + +class Profile +{ +public: + Profile(const std::wstring& title); + bool XmlReadProfile(pugi::xml_node profile_node); + bool XmlWriteProfile(pugi::xml_node profile_node); + void AddHook(hook_ptr hook); + int AddThread(thread_ptr tp); + void AddLink(link_ptr lp); + void Clear(); + const std::unordered_set& Hooks() const; + const std::vector& Threads() const; + const std::unordered_set& Links() const; + const std::wstring& Title() const; + thread_ptr_iter FindThread(const ThreadParameter* tp, const std::wstring& hook_name) const; + WORD& SelectedIndex() { return select_index; } + bool IsThreadSelected(thread_ptr_iter thread_profile); + +private: + bool XmlReadProfileHook(pugi::xml_node hooks_node); + bool XmlReadProfileThread(pugi::xml_node threads_node); + bool XmlReadProfileLink(pugi::xml_node links_node); + bool XmlWriteProfileHook(pugi::xml_node hooks_node); + bool XmlWriteProfileThread(pugi::xml_node threads_node); + bool XmlWriteProfileLink(pugi::xml_node links_node); + + std::wstring title; + std::unordered_set hooks; + std::vector threads; + std::unordered_set links; + + WORD select_index; +}; diff --git a/vnr/profile/misc.cpp b/vnr/profile/misc.cpp new file mode 100644 index 0000000..b0f6068 --- /dev/null +++ b/vnr/profile/misc.cpp @@ -0,0 +1,275 @@ +#include "misc.h" +#include +#include +#include "host/host.h" +#include "vnrhook/include/const.h" +#include "vnrhook/include/types.h" + +DWORD Hash(const std::wstring& module, int length) +{ + DWORD hash = 0; + auto end = (length < 0 || static_cast(length) > module.length()) ? + module.end() : + module.begin() + length; + for (auto it = module.begin(); it != end; ++it) + hash = _rotr(hash, 7) + *it; + return hash; +} + +bool Parse(const std::wstring& cmd, HookParam& hp) +{ + using std::wregex; + using std::regex_search; + // /H[X]{A|B|W|S|Q}[N][data_offset[*drdo]][:sub_offset[*drso]]@addr[:[module[:{name|#ordinal}]]] + wregex rx(L"^X?([ABWSQ])(N)?", wregex::icase); + std::match_results m; + auto start = cmd.begin(); + auto end = cmd.end(); + bool result = regex_search(start, end, m, rx); + if (!result) + return result; + start = m[0].second; + if (m[2].matched) + hp.type |= NO_CONTEXT; + + switch (m[1].first[0]) + { + case L's': + case L'S': + hp.type |= USING_STRING; + break; + case L'e': + case L'E': + hp.type |= STRING_LAST_CHAR; + case L'a': + case L'A': + hp.type |= BIG_ENDIAN; + hp.length_offset = 1; + break; + case L'b': + case L'B': + hp.length_offset = 1; + break; + case L'h': + case L'H': + hp.type |= PRINT_DWORD; + case L'q': + case L'Q': + hp.type |= USING_STRING | USING_UNICODE; + break; + case L'l': + case L'L': + hp.type |= STRING_LAST_CHAR; + case L'w': + case L'W': + hp.type |= USING_UNICODE; + hp.length_offset = 1; + break; + default: + break; + } + + // [data_offset[*drdo]] + std::wstring data_offset(L"(-?[[:xdigit:]]+)"), drdo(L"(\\*-?[[:xdigit:]]+)?"); + rx = wregex(L"^" + data_offset + drdo, wregex::icase); + result = regex_search(start, end, m, rx); + if (result) + { + start = m[0].second; + hp.offset = std::stoul(m[1].str(), NULL, 16); + if (m[2].matched) + { + hp.type |= DATA_INDIRECT; + hp.index = std::stoul(m[2].str().substr(1), NULL, 16); + } + } + + // [:sub_offset[*drso]] + std::wstring sub_offset(L"(-?[[:xdigit:]]+)"), drso(L"(\\*-?[[:xdigit:]]+)?"); + rx = wregex(L"^:" + sub_offset + drso, wregex::icase); + result = regex_search(start, end, m, rx); + if (result) + { + start = m[0].second; + hp.type |= USING_SPLIT; + hp.split = std::stoul(m[1].str(), NULL, 16); + if (m[2].matched) + { + hp.type |= SPLIT_INDIRECT; + hp.split_index = std::stoul(m[2].str().substr(1), NULL, 16); + } + } + // @addr + rx = wregex(L"^@[[:xdigit:]]+", wregex::icase); + result = regex_search(start, end, m, rx); + if (!result) + return false; + start = m[0].second; + hp.address = std::stoul(m[0].str().substr(1), NULL, 16); + if (hp.offset & 0x80000000) + hp.offset -= 4; + if (hp.split & 0x80000000) + hp.split -= 4; + + // [:[module[:{name|#ordinal}]]] + // ":" -> module == NULL &% function == NULL + // "" -> MODULE_OFFSET && module == NULL && function == addr + // ":GDI.dll" -> MODULE_OFFSET && module != NULL + // ":GDI.dll:strlen" -> MODULE_OFFSET | FUNCTION_OFFSET && module != NULL && function != NULL + // ":GDI.dll:#123" -> MODULE_OFFSET | FUNCTION_OFFSET && module != NULL && function != NULL + std::wstring module(L"([[:graph:]]+)"), name(L"[[:graph:]]+"), ordinal(L"\\d+"); + rx = wregex(L"^:(" + module + L"(:" + name + L"|#" + ordinal + L")?)?$", wregex::icase); + result = regex_search(start, end, m, rx); + if (result) // :[module[:{name|#ordinal}]] + { + if (m[1].matched) // module + { + hp.type |= MODULE_OFFSET; + std::wstring module = m[2]; + std::transform(module.begin(), module.end(), module.begin(), ::towlower); + hp.module = Hash(module); + if (m[3].matched) // :name|#ordinal + { + hp.type |= FUNCTION_OFFSET; + hp.function = Hash(m[3].str().substr(1)); + } + } + } + else + { + rx = wregex(L"^!([[:xdigit:]]+)(!([[:xdigit:]]+))?$", wregex::icase); + result = regex_search(start, end, m, rx); + if (result) + { + hp.type |= MODULE_OFFSET; + hp.module = std::stoul(m[1].str(), NULL, 16); + if (m[2].matched) + { + hp.type |= FUNCTION_OFFSET; + hp.function = std::stoul(m[2].str().substr(1), NULL, 16); + } + } + else + { + // Hack. Hook is relative to the executable. Store the original address in function. + // hp.module == NULL && hp.function != NULL + hp.type |= MODULE_OFFSET; + hp.function = hp.address; + } + } + return true; +} + +std::wstring ParseCode(const HookParam& hp) +{ + std::wstring code(L"/H"); + WCHAR c; + if (hp.type & PRINT_DWORD) + c = L'H'; + else if (hp.type & USING_UNICODE) + { + if (hp.type & USING_STRING) + c = L'Q'; + else if (hp.type & STRING_LAST_CHAR) + c = L'L'; + else + c = L'W'; + } + else + { + if (hp.type & USING_STRING) + c = L'S'; + else if (hp.type & BIG_ENDIAN) + c = L'A'; + else if (hp.type & STRING_LAST_CHAR) + c = L'E'; + else + c = L'B'; + } + code += c; + if (hp.type & NO_CONTEXT) + code += L'N'; + if (hp.offset >> 31) + code += L'-' + ToHexString(-(hp.offset + 4)); + else + code += ToHexString(hp.offset); + if (hp.type & DATA_INDIRECT) + { + if (hp.index >> 31) + code += L'*-' + ToHexString(-hp.index); + else + code += L'*' + ToHexString(hp.index); + } + if (hp.type & USING_SPLIT) + { + if (hp.split >> 31) + code += L":-" + ToHexString(-(4 + hp.split)); + else + code += L":" + ToHexString(hp.split); + } + if (hp.type & SPLIT_INDIRECT) + { + if (hp.split_index >> 31) + code += L"*-" + ToHexString(-hp.split_index); + else + code += L"*" + ToHexString(hp.split_index); + } + if (hp.module) + { + code += L"@" + ToHexString(hp.address) + L"!" + ToHexString(hp.module); + if (hp.function) + code += L"!" + ToHexString(hp.function); + } + else + { + // Hack. The original address is stored in the function field + // if (module == NULL && function != NULL). + // MODULE_OFFSET and FUNCTION_OFFSET are removed from HookParam.type in + // TextHook::UnsafeInsertHookCode() and can not be used here. + if (hp.function) + code += L"@" + ToHexString(hp.function); + else + code += L"@" + ToHexString(hp.address) + L":"; + } + return code; +} + + +std::string toMultiByteString(const std::wstring& unicodeString) +{ + int cbMultiByte = WideCharToMultiByte(932, 0, unicodeString.c_str(), unicodeString.length(), + NULL, 0, NULL, NULL); + auto lpMultiByteStr = std::make_unique(cbMultiByte); + WideCharToMultiByte(932, 0, unicodeString.c_str(), unicodeString.length(), + lpMultiByteStr.get(), cbMultiByte, NULL, NULL); + return std::string(lpMultiByteStr.get(), cbMultiByte); +} + +std::wstring toUnicodeString(const std::string& mbString) +{ + int cchWideChar = MultiByteToWideChar(932, 0, mbString.c_str(), mbString.length(), NULL, 0); + auto lpWideCharStr = std::make_unique(cchWideChar); + MultiByteToWideChar(932, 0, mbString.c_str(), mbString.length(), lpWideCharStr.get(), cchWideChar); + return std::wstring(lpWideCharStr.get(), cchWideChar); +} + +std::wstring GetHookNameByAddress(const ProcessRecord& pr, DWORD hook_address) +{ + std::wstring hook_name; + WaitForSingleObject(pr.hookman_mutex, 0); + auto hooks = (const Hook*)pr.hookman_map; + for (int i = 0; i < MAX_HOOK; ++i) + { + auto& hook = hooks[i]; + if (hook.Address() == hook_address) + { + std::unique_ptr name(new CHAR[hook.NameLength()]); + // name is zero terminated + if (ReadProcessMemory(pr.process_handle, hooks[i].Name(), name.get(), hook.NameLength(), NULL)) + hook_name = toUnicodeString(name.get()); + break; + } + } + ReleaseMutex(pr.hookman_mutex); + return hook_name; +} diff --git a/vnr/profile/misc.h b/vnr/profile/misc.h new file mode 100644 index 0000000..5ad3d94 --- /dev/null +++ b/vnr/profile/misc.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +struct HookParam; +struct ProcessRecord; + +bool Parse(const std::wstring& cmd, HookParam& hp); +DWORD Hash(const std::wstring& module, int length = -1); +std::wstring ParseCode(const HookParam& hp); +std::string toMultiByteString(const std::wstring& unicodeString); +std::wstring toUnicodeString(const std::string& mbString); +std::wstring GetHookNameByAddress(const ProcessRecord& pr, DWORD hook_address); + +template +std::wstring ToHexString(T i) { + std::wstringstream ss; + ss << std::uppercase << std::hex << i; + return ss.str(); +} diff --git a/vnr/profile/pugiconfig.hpp b/vnr/profile/pugiconfig.hpp new file mode 100644 index 0000000..4d431ab --- /dev/null +++ b/vnr/profile/pugiconfig.hpp @@ -0,0 +1,71 @@ +/** + * pugixml parser - version 1.6 + * -------------------------------------------------------- + * Copyright (C) 2006-2015, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Report bugs and download new versions at http://pugixml.org/ + * + * This library is distributed under the MIT License. See notice at the end + * of this file. + * + * This work is based on the pugxml parser, which is: + * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) + */ + +#ifndef HEADER_PUGICONFIG_HPP +#define HEADER_PUGICONFIG_HPP + +// Uncomment this to enable wchar_t mode +#define PUGIXML_WCHAR_MODE + +// Uncomment this to disable XPath +// #define PUGIXML_NO_XPATH + +// Uncomment this to disable STL +// #define PUGIXML_NO_STL + +// Uncomment this to disable exceptions +// #define PUGIXML_NO_EXCEPTIONS + +// Set this to control attributes for public classes/functions, i.e.: +// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL +// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL +// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall +// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead + +// Tune these constants to adjust memory-related behavior +// #define PUGIXML_MEMORY_PAGE_SIZE 32768 +// #define PUGIXML_MEMORY_OUTPUT_STACK 10240 +// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096 + +// Uncomment this to switch to header-only version +// #define PUGIXML_HEADER_ONLY + +// Uncomment this to enable long long support +// #define PUGIXML_HAS_LONG_LONG + +#endif + +/** + * Copyright (c) 2006-2015 Arseny Kapoulkine + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ diff --git a/vnr/profile/pugixml.cpp b/vnr/profile/pugixml.cpp new file mode 100644 index 0000000..5b77a27 --- /dev/null +++ b/vnr/profile/pugixml.cpp @@ -0,0 +1,11554 @@ +/** + * pugixml parser - version 1.6 + * -------------------------------------------------------- + * Copyright (C) 2006-2015, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Report bugs and download new versions at http://pugixml.org/ + * + * This library is distributed under the MIT License. See notice at the end + * of this file. + * + * This work is based on the pugxml parser, which is: + * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) + */ + +#ifndef SOURCE_PUGIXML_CPP +#define SOURCE_PUGIXML_CPP + +#include "pugixml.hpp" + +#include +#include +#include +#include + +#ifdef PUGIXML_WCHAR_MODE +# include +#endif + +#ifndef PUGIXML_NO_XPATH +# include +# include +# ifdef PUGIXML_NO_EXCEPTIONS +# include +# endif +#endif + +#ifndef PUGIXML_NO_STL +# include +# include +# include +#endif + +// For placement new +#include + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) // conditional expression is constant +# pragma warning(disable: 4324) // structure was padded due to __declspec(align()) +# pragma warning(disable: 4611) // interaction between '_setjmp' and C++ object destruction is non-portable +# pragma warning(disable: 4702) // unreachable code +# pragma warning(disable: 4996) // this function or variable may be unsafe +# pragma warning(disable: 4793) // function compiled as native: presence of '_setjmp' makes a function unmanaged +#endif + +#ifdef __INTEL_COMPILER +# pragma warning(disable: 177) // function was declared but never referenced +# pragma warning(disable: 279) // controlling expression is constant +# pragma warning(disable: 1478 1786) // function was declared "deprecated" +# pragma warning(disable: 1684) // conversion from pointer to same-sized integral type +#endif + +#if defined(__BORLANDC__) && defined(PUGIXML_HEADER_ONLY) +# pragma warn -8080 // symbol is declared but never used; disabling this inside push/pop bracket does not make the warning go away +#endif + +#ifdef __BORLANDC__ +# pragma option push +# pragma warn -8008 // condition is always false +# pragma warn -8066 // unreachable code +#endif + +#ifdef __SNC__ +// Using diag_push/diag_pop does not disable the warnings inside templates due to a compiler bug +# pragma diag_suppress=178 // function was declared but never referenced +# pragma diag_suppress=237 // controlling expression is constant +#endif + +// Inlining controls +#if defined(_MSC_VER) && _MSC_VER >= 1300 +# define PUGI__NO_INLINE __declspec(noinline) +#elif defined(__GNUC__) +# define PUGI__NO_INLINE __attribute__((noinline)) +#else +# define PUGI__NO_INLINE +#endif + +// Branch weight controls +#if defined(__GNUC__) +# define PUGI__UNLIKELY(cond) __builtin_expect(cond, 0) +#else +# define PUGI__UNLIKELY(cond) (cond) +#endif + +// Simple static assertion +#define PUGI__STATIC_ASSERT(cond) { static const char condition_failed[(cond) ? 1 : -1] = {0}; (void)condition_failed[0]; } + +// Digital Mars C++ bug workaround for passing char loaded from memory via stack +#ifdef __DMC__ +# define PUGI__DMC_VOLATILE volatile +#else +# define PUGI__DMC_VOLATILE +#endif + +// Borland C++ bug workaround for not defining ::memcpy depending on header include order (can't always use std::memcpy because some compilers don't have it at all) +#if defined(__BORLANDC__) && !defined(__MEM_H_USING_LIST) +using std::memcpy; +using std::memmove; +#endif + +// In some environments MSVC is a compiler but the CRT lacks certain MSVC-specific features +#if defined(_MSC_VER) && !defined(__S3E__) +# define PUGI__MSVC_CRT_VERSION _MSC_VER +#endif + +#ifdef PUGIXML_HEADER_ONLY +# define PUGI__NS_BEGIN namespace pugi { namespace impl { +# define PUGI__NS_END } } +# define PUGI__FN inline +# define PUGI__FN_NO_INLINE inline +#else +# if defined(_MSC_VER) && _MSC_VER < 1300 // MSVC6 seems to have an amusing bug with anonymous namespaces inside namespaces +# define PUGI__NS_BEGIN namespace pugi { namespace impl { +# define PUGI__NS_END } } +# else +# define PUGI__NS_BEGIN namespace pugi { namespace impl { namespace { +# define PUGI__NS_END } } } +# endif +# define PUGI__FN +# define PUGI__FN_NO_INLINE PUGI__NO_INLINE +#endif + +// uintptr_t +#if !defined(_MSC_VER) || _MSC_VER >= 1600 +# include +#else +# ifndef _UINTPTR_T_DEFINED +// No native uintptr_t in MSVC6 and in some WinCE versions +typedef size_t uintptr_t; +#define _UINTPTR_T_DEFINED +# endif +PUGI__NS_BEGIN + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +PUGI__NS_END +#endif + +// Memory allocation +PUGI__NS_BEGIN + PUGI__FN void* default_allocate(size_t size) + { + return malloc(size); + } + + PUGI__FN void default_deallocate(void* ptr) + { + free(ptr); + } + + template + struct xml_memory_management_function_storage + { + static allocation_function allocate; + static deallocation_function deallocate; + }; + + // Global allocation functions are stored in class statics so that in header mode linker deduplicates them + // Without a template<> we'll get multiple definitions of the same static + template allocation_function xml_memory_management_function_storage::allocate = default_allocate; + template deallocation_function xml_memory_management_function_storage::deallocate = default_deallocate; + + typedef xml_memory_management_function_storage xml_memory; +PUGI__NS_END + +// String utilities +PUGI__NS_BEGIN + // Get string length + PUGI__FN size_t strlength(const char_t* s) + { + assert(s); + + #ifdef PUGIXML_WCHAR_MODE + return wcslen(s); + #else + return strlen(s); + #endif + } + + // Compare two strings + PUGI__FN bool strequal(const char_t* src, const char_t* dst) + { + assert(src && dst); + + #ifdef PUGIXML_WCHAR_MODE + return wcscmp(src, dst) == 0; + #else + return strcmp(src, dst) == 0; + #endif + } + + // Compare lhs with [rhs_begin, rhs_end) + PUGI__FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count) + { + for (size_t i = 0; i < count; ++i) + if (lhs[i] != rhs[i]) + return false; + + return lhs[count] == 0; + } + + // Get length of wide string, even if CRT lacks wide character support + PUGI__FN size_t strlength_wide(const wchar_t* s) + { + assert(s); + + #ifdef PUGIXML_WCHAR_MODE + return wcslen(s); + #else + const wchar_t* end = s; + while (*end) end++; + return static_cast(end - s); + #endif + } + +#ifdef PUGIXML_WCHAR_MODE + // Convert string to wide string, assuming all symbols are ASCII + PUGI__FN void widen_ascii(wchar_t* dest, const char* source) + { + for (const char* i = source; *i; ++i) *dest++ = *i; + *dest = 0; + } +#endif +PUGI__NS_END + +#if !defined(PUGIXML_NO_STL) || !defined(PUGIXML_NO_XPATH) +// auto_ptr-like buffer holder for exception recovery +PUGI__NS_BEGIN + struct buffer_holder + { + void* data; + void (*deleter)(void*); + + buffer_holder(void* data_, void (*deleter_)(void*)): data(data_), deleter(deleter_) + { + } + + ~buffer_holder() + { + if (data) deleter(data); + } + + void* release() + { + void* result = data; + data = 0; + return result; + } + }; +PUGI__NS_END +#endif + +PUGI__NS_BEGIN + static const size_t xml_memory_page_size = + #ifdef PUGIXML_MEMORY_PAGE_SIZE + PUGIXML_MEMORY_PAGE_SIZE + #else + 32768 + #endif + ; + + static const uintptr_t xml_memory_page_alignment = 64; + static const uintptr_t xml_memory_page_pointer_mask = ~(xml_memory_page_alignment - 1); + static const uintptr_t xml_memory_page_contents_shared_mask = 32; + static const uintptr_t xml_memory_page_name_allocated_mask = 16; + static const uintptr_t xml_memory_page_value_allocated_mask = 8; + static const uintptr_t xml_memory_page_type_mask = 7; + static const uintptr_t xml_memory_page_name_allocated_or_shared_mask = xml_memory_page_name_allocated_mask | xml_memory_page_contents_shared_mask; + static const uintptr_t xml_memory_page_value_allocated_or_shared_mask = xml_memory_page_value_allocated_mask | xml_memory_page_contents_shared_mask; + + #define PUGI__NODETYPE(n) static_cast(((n)->header & impl::xml_memory_page_type_mask) + 1) + + struct xml_allocator; + + struct xml_memory_page + { + static xml_memory_page* construct(void* memory) + { + xml_memory_page* result = static_cast(memory); + + result->allocator = 0; + result->prev = 0; + result->next = 0; + result->busy_size = 0; + result->freed_size = 0; + + return result; + } + + xml_allocator* allocator; + + xml_memory_page* prev; + xml_memory_page* next; + + size_t busy_size; + size_t freed_size; + }; + + struct xml_memory_string_header + { + uint16_t page_offset; // offset from page->data + uint16_t full_size; // 0 if string occupies whole page + }; + + struct xml_allocator + { + xml_allocator(xml_memory_page* root): _root(root), _busy_size(root->busy_size) + { + } + + xml_memory_page* allocate_page(size_t data_size) + { + size_t size = sizeof(xml_memory_page) + data_size; + + // allocate block with some alignment, leaving memory for worst-case padding + void* memory = xml_memory::allocate(size + xml_memory_page_alignment); + if (!memory) return 0; + + // align to next page boundary (note: this guarantees at least 1 usable byte before the page) + char* page_memory = reinterpret_cast((reinterpret_cast(memory) + xml_memory_page_alignment) & ~(xml_memory_page_alignment - 1)); + + // prepare page structure + xml_memory_page* page = xml_memory_page::construct(page_memory); + assert(page); + + page->allocator = _root->allocator; + + // record the offset for freeing the memory block + assert(page_memory > memory && page_memory - static_cast(memory) <= 127); + page_memory[-1] = static_cast(page_memory - static_cast(memory)); + + return page; + } + + static void deallocate_page(xml_memory_page* page) + { + char* page_memory = reinterpret_cast(page); + + xml_memory::deallocate(page_memory - page_memory[-1]); + } + + void* allocate_memory_oob(size_t size, xml_memory_page*& out_page); + + void* allocate_memory(size_t size, xml_memory_page*& out_page) + { + if (_busy_size + size > xml_memory_page_size) return allocate_memory_oob(size, out_page); + + void* buf = reinterpret_cast(_root) + sizeof(xml_memory_page) + _busy_size; + + _busy_size += size; + + out_page = _root; + + return buf; + } + + void deallocate_memory(void* ptr, size_t size, xml_memory_page* page) + { + if (page == _root) page->busy_size = _busy_size; + + assert(ptr >= reinterpret_cast(page) + sizeof(xml_memory_page) && ptr < reinterpret_cast(page) + sizeof(xml_memory_page) + page->busy_size); + (void)!ptr; + + page->freed_size += size; + assert(page->freed_size <= page->busy_size); + + if (page->freed_size == page->busy_size) + { + if (page->next == 0) + { + assert(_root == page); + + // top page freed, just reset sizes + page->busy_size = page->freed_size = 0; + _busy_size = 0; + } + else + { + assert(_root != page); + assert(page->prev); + + // remove from the list + page->prev->next = page->next; + page->next->prev = page->prev; + + // deallocate + deallocate_page(page); + } + } + } + + char_t* allocate_string(size_t length) + { + static const size_t max_encoded_offset = (1 << 16) * sizeof(void*); + + PUGI__STATIC_ASSERT(xml_memory_page_size <= max_encoded_offset); + + // allocate memory for string and header block + size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t); + + // round size up to pointer alignment boundary + size_t full_size = (size + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1); + + xml_memory_page* page; + xml_memory_string_header* header = static_cast(allocate_memory(full_size, page)); + + if (!header) return 0; + + // setup header + ptrdiff_t page_offset = reinterpret_cast(header) - reinterpret_cast(page) - sizeof(xml_memory_page); + + assert(page_offset % sizeof(void*) == 0); + assert(page_offset >= 0 && static_cast(page_offset) < max_encoded_offset); + header->page_offset = static_cast(static_cast(page_offset) / sizeof(void*)); + + // full_size == 0 for large strings that occupy the whole page + assert(full_size % sizeof(void*) == 0); + assert(full_size < max_encoded_offset || (page->busy_size == full_size && page_offset == 0)); + header->full_size = static_cast(full_size < max_encoded_offset ? full_size / sizeof(void*) : 0); + + // round-trip through void* to avoid 'cast increases required alignment of target type' warning + // header is guaranteed a pointer-sized alignment, which should be enough for char_t + return static_cast(static_cast(header + 1)); + } + + void deallocate_string(char_t* string) + { + // this function casts pointers through void* to avoid 'cast increases required alignment of target type' warnings + // we're guaranteed the proper (pointer-sized) alignment on the input string if it was allocated via allocate_string + + // get header + xml_memory_string_header* header = static_cast(static_cast(string)) - 1; + assert(header); + + // deallocate + size_t page_offset = sizeof(xml_memory_page) + header->page_offset * sizeof(void*); + xml_memory_page* page = reinterpret_cast(static_cast(reinterpret_cast(header) - page_offset)); + + // if full_size == 0 then this string occupies the whole page + size_t full_size = header->full_size == 0 ? page->busy_size : header->full_size * sizeof(void*); + + deallocate_memory(header, full_size, page); + } + + xml_memory_page* _root; + size_t _busy_size; + }; + + PUGI__FN_NO_INLINE void* xml_allocator::allocate_memory_oob(size_t size, xml_memory_page*& out_page) + { + const size_t large_allocation_threshold = xml_memory_page_size / 4; + + xml_memory_page* page = allocate_page(size <= large_allocation_threshold ? xml_memory_page_size : size); + out_page = page; + + if (!page) return 0; + + if (size <= large_allocation_threshold) + { + _root->busy_size = _busy_size; + + // insert page at the end of linked list + page->prev = _root; + _root->next = page; + _root = page; + + _busy_size = size; + } + else + { + // insert page before the end of linked list, so that it is deleted as soon as possible + // the last page is not deleted even if it's empty (see deallocate_memory) + assert(_root->prev); + + page->prev = _root->prev; + page->next = _root; + + _root->prev->next = page; + _root->prev = page; + } + + // allocate inside page + page->busy_size = size; + + return reinterpret_cast(page) + sizeof(xml_memory_page); + } +PUGI__NS_END + +namespace pugi +{ + /// A 'name=value' XML attribute structure. + struct xml_attribute_struct + { + /// Default ctor + xml_attribute_struct(impl::xml_memory_page* page): header(reinterpret_cast(page)), name(0), value(0), prev_attribute_c(0), next_attribute(0) + { + } + + uintptr_t header; + + char_t* name; ///< Pointer to attribute name. + char_t* value; ///< Pointer to attribute value. + + xml_attribute_struct* prev_attribute_c; ///< Previous attribute (cyclic list) + xml_attribute_struct* next_attribute; ///< Next attribute + }; + + /// An XML document tree node. + struct xml_node_struct + { + /// Default ctor + /// \param type - node type + xml_node_struct(impl::xml_memory_page* page, xml_node_type type): header(reinterpret_cast(page) | (type - 1)), parent(0), name(0), value(0), first_child(0), prev_sibling_c(0), next_sibling(0), first_attribute(0) + { + } + + uintptr_t header; + + xml_node_struct* parent; ///< Pointer to parent + + char_t* name; ///< Pointer to element name. + char_t* value; ///< Pointer to any associated string data. + + xml_node_struct* first_child; ///< First child + + xml_node_struct* prev_sibling_c; ///< Left brother (cyclic list) + xml_node_struct* next_sibling; ///< Right brother + + xml_attribute_struct* first_attribute; ///< First attribute + }; +} + +PUGI__NS_BEGIN + struct xml_extra_buffer + { + char_t* buffer; + xml_extra_buffer* next; + }; + + struct xml_document_struct: public xml_node_struct, public xml_allocator + { + xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(0), extra_buffers(0) + { + } + + const char_t* buffer; + + xml_extra_buffer* extra_buffers; + }; + + inline xml_allocator& get_allocator(const xml_node_struct* node) + { + assert(node); + + return *reinterpret_cast(node->header & xml_memory_page_pointer_mask)->allocator; + } + + template inline xml_document_struct& get_document(const Object* object) + { + assert(object); + + return *static_cast(reinterpret_cast(object->header & xml_memory_page_pointer_mask)->allocator); + } +PUGI__NS_END + +// Low-level DOM operations +PUGI__NS_BEGIN + inline xml_attribute_struct* allocate_attribute(xml_allocator& alloc) + { + xml_memory_page* page; + void* memory = alloc.allocate_memory(sizeof(xml_attribute_struct), page); + + return new (memory) xml_attribute_struct(page); + } + + inline xml_node_struct* allocate_node(xml_allocator& alloc, xml_node_type type) + { + xml_memory_page* page; + void* memory = alloc.allocate_memory(sizeof(xml_node_struct), page); + + return new (memory) xml_node_struct(page, type); + } + + inline void destroy_attribute(xml_attribute_struct* a, xml_allocator& alloc) + { + uintptr_t header = a->header; + + if (header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(a->name); + if (header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(a->value); + + alloc.deallocate_memory(a, sizeof(xml_attribute_struct), reinterpret_cast(header & xml_memory_page_pointer_mask)); + } + + inline void destroy_node(xml_node_struct* n, xml_allocator& alloc) + { + uintptr_t header = n->header; + + if (header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(n->name); + if (header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(n->value); + + for (xml_attribute_struct* attr = n->first_attribute; attr; ) + { + xml_attribute_struct* next = attr->next_attribute; + + destroy_attribute(attr, alloc); + + attr = next; + } + + for (xml_node_struct* child = n->first_child; child; ) + { + xml_node_struct* next = child->next_sibling; + + destroy_node(child, alloc); + + child = next; + } + + alloc.deallocate_memory(n, sizeof(xml_node_struct), reinterpret_cast(header & xml_memory_page_pointer_mask)); + } + + inline void append_node(xml_node_struct* child, xml_node_struct* node) + { + child->parent = node; + + xml_node_struct* head = node->first_child; + + if (head) + { + xml_node_struct* tail = head->prev_sibling_c; + + tail->next_sibling = child; + child->prev_sibling_c = tail; + head->prev_sibling_c = child; + } + else + { + node->first_child = child; + child->prev_sibling_c = child; + } + } + + inline void prepend_node(xml_node_struct* child, xml_node_struct* node) + { + child->parent = node; + + xml_node_struct* head = node->first_child; + + if (head) + { + child->prev_sibling_c = head->prev_sibling_c; + head->prev_sibling_c = child; + } + else + child->prev_sibling_c = child; + + child->next_sibling = head; + node->first_child = child; + } + + inline void insert_node_after(xml_node_struct* child, xml_node_struct* node) + { + xml_node_struct* parent = node->parent; + + child->parent = parent; + + if (node->next_sibling) + node->next_sibling->prev_sibling_c = child; + else + parent->first_child->prev_sibling_c = child; + + child->next_sibling = node->next_sibling; + child->prev_sibling_c = node; + + node->next_sibling = child; + } + + inline void insert_node_before(xml_node_struct* child, xml_node_struct* node) + { + xml_node_struct* parent = node->parent; + + child->parent = parent; + + if (node->prev_sibling_c->next_sibling) + node->prev_sibling_c->next_sibling = child; + else + parent->first_child = child; + + child->prev_sibling_c = node->prev_sibling_c; + child->next_sibling = node; + + node->prev_sibling_c = child; + } + + inline void remove_node(xml_node_struct* node) + { + xml_node_struct* parent = node->parent; + + if (node->next_sibling) + node->next_sibling->prev_sibling_c = node->prev_sibling_c; + else + parent->first_child->prev_sibling_c = node->prev_sibling_c; + + if (node->prev_sibling_c->next_sibling) + node->prev_sibling_c->next_sibling = node->next_sibling; + else + parent->first_child = node->next_sibling; + + node->parent = 0; + node->prev_sibling_c = 0; + node->next_sibling = 0; + } + + inline void append_attribute(xml_attribute_struct* attr, xml_node_struct* node) + { + xml_attribute_struct* head = node->first_attribute; + + if (head) + { + xml_attribute_struct* tail = head->prev_attribute_c; + + tail->next_attribute = attr; + attr->prev_attribute_c = tail; + head->prev_attribute_c = attr; + } + else + { + node->first_attribute = attr; + attr->prev_attribute_c = attr; + } + } + + inline void prepend_attribute(xml_attribute_struct* attr, xml_node_struct* node) + { + xml_attribute_struct* head = node->first_attribute; + + if (head) + { + attr->prev_attribute_c = head->prev_attribute_c; + head->prev_attribute_c = attr; + } + else + attr->prev_attribute_c = attr; + + attr->next_attribute = head; + node->first_attribute = attr; + } + + inline void insert_attribute_after(xml_attribute_struct* attr, xml_attribute_struct* place, xml_node_struct* node) + { + if (place->next_attribute) + place->next_attribute->prev_attribute_c = attr; + else + node->first_attribute->prev_attribute_c = attr; + + attr->next_attribute = place->next_attribute; + attr->prev_attribute_c = place; + place->next_attribute = attr; + } + + inline void insert_attribute_before(xml_attribute_struct* attr, xml_attribute_struct* place, xml_node_struct* node) + { + if (place->prev_attribute_c->next_attribute) + place->prev_attribute_c->next_attribute = attr; + else + node->first_attribute = attr; + + attr->prev_attribute_c = place->prev_attribute_c; + attr->next_attribute = place; + place->prev_attribute_c = attr; + } + + inline void remove_attribute(xml_attribute_struct* attr, xml_node_struct* node) + { + if (attr->next_attribute) + attr->next_attribute->prev_attribute_c = attr->prev_attribute_c; + else + node->first_attribute->prev_attribute_c = attr->prev_attribute_c; + + if (attr->prev_attribute_c->next_attribute) + attr->prev_attribute_c->next_attribute = attr->next_attribute; + else + node->first_attribute = attr->next_attribute; + + attr->prev_attribute_c = 0; + attr->next_attribute = 0; + } + + PUGI__FN_NO_INLINE xml_node_struct* append_new_node(xml_node_struct* node, xml_allocator& alloc, xml_node_type type = node_element) + { + xml_node_struct* child = allocate_node(alloc, type); + if (!child) return 0; + + append_node(child, node); + + return child; + } + + PUGI__FN_NO_INLINE xml_attribute_struct* append_new_attribute(xml_node_struct* node, xml_allocator& alloc) + { + xml_attribute_struct* attr = allocate_attribute(alloc); + if (!attr) return 0; + + append_attribute(attr, node); + + return attr; + } +PUGI__NS_END + +// Helper classes for code generation +PUGI__NS_BEGIN + struct opt_false + { + enum { value = 0 }; + }; + + struct opt_true + { + enum { value = 1 }; + }; +PUGI__NS_END + +// Unicode utilities +PUGI__NS_BEGIN + inline uint16_t endian_swap(uint16_t value) + { + return static_cast(((value & 0xff) << 8) | (value >> 8)); + } + + inline uint32_t endian_swap(uint32_t value) + { + return ((value & 0xff) << 24) | ((value & 0xff00) << 8) | ((value & 0xff0000) >> 8) | (value >> 24); + } + + struct utf8_counter + { + typedef size_t value_type; + + static value_type low(value_type result, uint32_t ch) + { + // U+0000..U+007F + if (ch < 0x80) return result + 1; + // U+0080..U+07FF + else if (ch < 0x800) return result + 2; + // U+0800..U+FFFF + else return result + 3; + } + + static value_type high(value_type result, uint32_t) + { + // U+10000..U+10FFFF + return result + 4; + } + }; + + struct utf8_writer + { + typedef uint8_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + // U+0000..U+007F + if (ch < 0x80) + { + *result = static_cast(ch); + return result + 1; + } + // U+0080..U+07FF + else if (ch < 0x800) + { + result[0] = static_cast(0xC0 | (ch >> 6)); + result[1] = static_cast(0x80 | (ch & 0x3F)); + return result + 2; + } + // U+0800..U+FFFF + else + { + result[0] = static_cast(0xE0 | (ch >> 12)); + result[1] = static_cast(0x80 | ((ch >> 6) & 0x3F)); + result[2] = static_cast(0x80 | (ch & 0x3F)); + return result + 3; + } + } + + static value_type high(value_type result, uint32_t ch) + { + // U+10000..U+10FFFF + result[0] = static_cast(0xF0 | (ch >> 18)); + result[1] = static_cast(0x80 | ((ch >> 12) & 0x3F)); + result[2] = static_cast(0x80 | ((ch >> 6) & 0x3F)); + result[3] = static_cast(0x80 | (ch & 0x3F)); + return result + 4; + } + + static value_type any(value_type result, uint32_t ch) + { + return (ch < 0x10000) ? low(result, ch) : high(result, ch); + } + }; + + struct utf16_counter + { + typedef size_t value_type; + + static value_type low(value_type result, uint32_t) + { + return result + 1; + } + + static value_type high(value_type result, uint32_t) + { + return result + 2; + } + }; + + struct utf16_writer + { + typedef uint16_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + *result = static_cast(ch); + + return result + 1; + } + + static value_type high(value_type result, uint32_t ch) + { + uint32_t msh = static_cast(ch - 0x10000) >> 10; + uint32_t lsh = static_cast(ch - 0x10000) & 0x3ff; + + result[0] = static_cast(0xD800 + msh); + result[1] = static_cast(0xDC00 + lsh); + + return result + 2; + } + + static value_type any(value_type result, uint32_t ch) + { + return (ch < 0x10000) ? low(result, ch) : high(result, ch); + } + }; + + struct utf32_counter + { + typedef size_t value_type; + + static value_type low(value_type result, uint32_t) + { + return result + 1; + } + + static value_type high(value_type result, uint32_t) + { + return result + 1; + } + }; + + struct utf32_writer + { + typedef uint32_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + *result = ch; + + return result + 1; + } + + static value_type high(value_type result, uint32_t ch) + { + *result = ch; + + return result + 1; + } + + static value_type any(value_type result, uint32_t ch) + { + *result = ch; + + return result + 1; + } + }; + + struct latin1_writer + { + typedef uint8_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + *result = static_cast(ch > 255 ? '?' : ch); + + return result + 1; + } + + static value_type high(value_type result, uint32_t ch) + { + (void)ch; + + *result = '?'; + + return result + 1; + } + }; + + template struct wchar_selector; + + template <> struct wchar_selector<2> + { + typedef uint16_t type; + typedef utf16_counter counter; + typedef utf16_writer writer; + }; + + template <> struct wchar_selector<4> + { + typedef uint32_t type; + typedef utf32_counter counter; + typedef utf32_writer writer; + }; + + typedef wchar_selector::counter wchar_counter; + typedef wchar_selector::writer wchar_writer; + + template struct utf_decoder + { + static inline typename Traits::value_type decode_utf8_block(const uint8_t* data, size_t size, typename Traits::value_type result) + { + const uint8_t utf8_byte_mask = 0x3f; + + while (size) + { + uint8_t lead = *data; + + // 0xxxxxxx -> U+0000..U+007F + if (lead < 0x80) + { + result = Traits::low(result, lead); + data += 1; + size -= 1; + + // process aligned single-byte (ascii) blocks + if ((reinterpret_cast(data) & 3) == 0) + { + // round-trip through void* to silence 'cast increases required alignment of target type' warnings + while (size >= 4 && (*static_cast(static_cast(data)) & 0x80808080) == 0) + { + result = Traits::low(result, data[0]); + result = Traits::low(result, data[1]); + result = Traits::low(result, data[2]); + result = Traits::low(result, data[3]); + data += 4; + size -= 4; + } + } + } + // 110xxxxx -> U+0080..U+07FF + else if (static_cast(lead - 0xC0) < 0x20 && size >= 2 && (data[1] & 0xc0) == 0x80) + { + result = Traits::low(result, ((lead & ~0xC0) << 6) | (data[1] & utf8_byte_mask)); + data += 2; + size -= 2; + } + // 1110xxxx -> U+0800-U+FFFF + else if (static_cast(lead - 0xE0) < 0x10 && size >= 3 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80) + { + result = Traits::low(result, ((lead & ~0xE0) << 12) | ((data[1] & utf8_byte_mask) << 6) | (data[2] & utf8_byte_mask)); + data += 3; + size -= 3; + } + // 11110xxx -> U+10000..U+10FFFF + else if (static_cast(lead - 0xF0) < 0x08 && size >= 4 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80 && (data[3] & 0xc0) == 0x80) + { + result = Traits::high(result, ((lead & ~0xF0) << 18) | ((data[1] & utf8_byte_mask) << 12) | ((data[2] & utf8_byte_mask) << 6) | (data[3] & utf8_byte_mask)); + data += 4; + size -= 4; + } + // 10xxxxxx or 11111xxx -> invalid + else + { + data += 1; + size -= 1; + } + } + + return result; + } + + static inline typename Traits::value_type decode_utf16_block(const uint16_t* data, size_t size, typename Traits::value_type result) + { + const uint16_t* end = data + size; + + while (data < end) + { + unsigned int lead = opt_swap::value ? endian_swap(*data) : *data; + + // U+0000..U+D7FF + if (lead < 0xD800) + { + result = Traits::low(result, lead); + data += 1; + } + // U+E000..U+FFFF + else if (static_cast(lead - 0xE000) < 0x2000) + { + result = Traits::low(result, lead); + data += 1; + } + // surrogate pair lead + else if (static_cast(lead - 0xD800) < 0x400 && data + 1 < end) + { + uint16_t next = opt_swap::value ? endian_swap(data[1]) : data[1]; + + if (static_cast(next - 0xDC00) < 0x400) + { + result = Traits::high(result, 0x10000 + ((lead & 0x3ff) << 10) + (next & 0x3ff)); + data += 2; + } + else + { + data += 1; + } + } + else + { + data += 1; + } + } + + return result; + } + + static inline typename Traits::value_type decode_utf32_block(const uint32_t* data, size_t size, typename Traits::value_type result) + { + const uint32_t* end = data + size; + + while (data < end) + { + uint32_t lead = opt_swap::value ? endian_swap(*data) : *data; + + // U+0000..U+FFFF + if (lead < 0x10000) + { + result = Traits::low(result, lead); + data += 1; + } + // U+10000..U+10FFFF + else + { + result = Traits::high(result, lead); + data += 1; + } + } + + return result; + } + + static inline typename Traits::value_type decode_latin1_block(const uint8_t* data, size_t size, typename Traits::value_type result) + { + for (size_t i = 0; i < size; ++i) + { + result = Traits::low(result, data[i]); + } + + return result; + } + + static inline typename Traits::value_type decode_wchar_block_impl(const uint16_t* data, size_t size, typename Traits::value_type result) + { + return decode_utf16_block(data, size, result); + } + + static inline typename Traits::value_type decode_wchar_block_impl(const uint32_t* data, size_t size, typename Traits::value_type result) + { + return decode_utf32_block(data, size, result); + } + + static inline typename Traits::value_type decode_wchar_block(const wchar_t* data, size_t size, typename Traits::value_type result) + { + return decode_wchar_block_impl(reinterpret_cast::type*>(data), size, result); + } + }; + + template PUGI__FN void convert_utf_endian_swap(T* result, const T* data, size_t length) + { + for (size_t i = 0; i < length; ++i) result[i] = endian_swap(data[i]); + } + +#ifdef PUGIXML_WCHAR_MODE + PUGI__FN void convert_wchar_endian_swap(wchar_t* result, const wchar_t* data, size_t length) + { + for (size_t i = 0; i < length; ++i) result[i] = static_cast(endian_swap(static_cast::type>(data[i]))); + } +#endif +PUGI__NS_END + +PUGI__NS_BEGIN + enum chartype_t + { + ct_parse_pcdata = 1, // \0, &, \r, < + ct_parse_attr = 2, // \0, &, \r, ', " + ct_parse_attr_ws = 4, // \0, &, \r, ', ", \n, tab + ct_space = 8, // \r, \n, space, tab + ct_parse_cdata = 16, // \0, ], >, \r + ct_parse_comment = 32, // \0, -, >, \r + ct_symbol = 64, // Any symbol > 127, a-z, A-Z, 0-9, _, :, -, . + ct_start_symbol = 128 // Any symbol > 127, a-z, A-Z, _, : + }; + + static const unsigned char chartype_table[256] = + { + 55, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 63, 0, 0, // 0-15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 + 8, 0, 6, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 96, 64, 0, // 32-47 + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 192, 0, 1, 0, 48, 0, // 48-63 + 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 64-79 + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 16, 0, 192, // 80-95 + 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 96-111 + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 0, 0, 0, // 112-127 + + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 128+ + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192 + }; + + enum chartypex_t + { + ctx_special_pcdata = 1, // Any symbol >= 0 and < 32 (except \t, \r, \n), &, <, > + ctx_special_attr = 2, // Any symbol >= 0 and < 32 (except \t), &, <, >, " + ctx_start_symbol = 4, // Any symbol > 127, a-z, A-Z, _ + ctx_digit = 8, // 0-9 + ctx_symbol = 16 // Any symbol > 127, a-z, A-Z, 0-9, _, -, . + }; + + static const unsigned char chartypex_table[256] = + { + 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 2, 3, 3, 2, 3, 3, // 0-15 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 16-31 + 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 16, 16, 0, // 32-47 + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 3, 0, 3, 0, // 48-63 + + 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 64-79 + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 20, // 80-95 + 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 96-111 + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 0, // 112-127 + + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 128+ + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 + }; + +#ifdef PUGIXML_WCHAR_MODE + #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) ((static_cast(c) < 128 ? table[static_cast(c)] : table[128]) & (ct)) +#else + #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) (table[static_cast(c)] & (ct)) +#endif + + #define PUGI__IS_CHARTYPE(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartype_table) + #define PUGI__IS_CHARTYPEX(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartypex_table) + + PUGI__FN bool is_little_endian() + { + unsigned int ui = 1; + + return *reinterpret_cast(&ui) == 1; + } + + PUGI__FN xml_encoding get_wchar_encoding() + { + PUGI__STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4); + + if (sizeof(wchar_t) == 2) + return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + else + return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + } + + PUGI__FN xml_encoding guess_buffer_encoding(uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3) + { + // look for BOM in first few bytes + if (d0 == 0 && d1 == 0 && d2 == 0xfe && d3 == 0xff) return encoding_utf32_be; + if (d0 == 0xff && d1 == 0xfe && d2 == 0 && d3 == 0) return encoding_utf32_le; + if (d0 == 0xfe && d1 == 0xff) return encoding_utf16_be; + if (d0 == 0xff && d1 == 0xfe) return encoding_utf16_le; + if (d0 == 0xef && d1 == 0xbb && d2 == 0xbf) return encoding_utf8; + + // look for <, (contents); + + PUGI__DMC_VOLATILE uint8_t d0 = data[0], d1 = data[1], d2 = data[2], d3 = data[3]; + + return guess_buffer_encoding(d0, d1, d2, d3); + } + + PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + { + size_t length = size / sizeof(char_t); + + if (is_mutable) + { + out_buffer = static_cast(const_cast(contents)); + out_length = length; + } + else + { + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + if (contents) + memcpy(buffer, contents, length * sizeof(char_t)); + else + assert(length == 0); + + buffer[length] = 0; + + out_buffer = buffer; + out_length = length + 1; + } + + return true; + } + +#ifdef PUGIXML_WCHAR_MODE + PUGI__FN bool need_endian_swap_utf(xml_encoding le, xml_encoding re) + { + return (le == encoding_utf16_be && re == encoding_utf16_le) || (le == encoding_utf16_le && re == encoding_utf16_be) || + (le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be); + } + + PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + { + const char_t* data = static_cast(contents); + size_t length = size / sizeof(char_t); + + if (is_mutable) + { + char_t* buffer = const_cast(data); + + convert_wchar_endian_swap(buffer, data, length); + + out_buffer = buffer; + out_length = length; + } + else + { + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + convert_wchar_endian_swap(buffer, data, length); + buffer[length] = 0; + + out_buffer = buffer; + out_length = length + 1; + } + + return true; + } + + PUGI__FN bool convert_buffer_utf8(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size) + { + const uint8_t* data = static_cast(contents); + size_t data_length = size; + + // first pass: get length in wchar_t units + size_t length = utf_decoder::decode_utf8_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf8 input to wchar_t + wchar_writer::value_type obegin = reinterpret_cast(buffer); + wchar_writer::value_type oend = utf_decoder::decode_utf8_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + template PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) + { + const uint16_t* data = static_cast(contents); + size_t data_length = size / sizeof(uint16_t); + + // first pass: get length in wchar_t units + size_t length = utf_decoder::decode_utf16_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf16 input to wchar_t + wchar_writer::value_type obegin = reinterpret_cast(buffer); + wchar_writer::value_type oend = utf_decoder::decode_utf16_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + template PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) + { + const uint32_t* data = static_cast(contents); + size_t data_length = size / sizeof(uint32_t); + + // first pass: get length in wchar_t units + size_t length = utf_decoder::decode_utf32_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf32 input to wchar_t + wchar_writer::value_type obegin = reinterpret_cast(buffer); + wchar_writer::value_type oend = utf_decoder::decode_utf32_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size) + { + const uint8_t* data = static_cast(contents); + size_t data_length = size; + + // get length in wchar_t units + size_t length = data_length; + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // convert latin1 input to wchar_t + wchar_writer::value_type obegin = reinterpret_cast(buffer); + wchar_writer::value_type oend = utf_decoder::decode_latin1_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) + { + // get native encoding + xml_encoding wchar_encoding = get_wchar_encoding(); + + // fast path: no conversion required + if (encoding == wchar_encoding) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + + // only endian-swapping is required + if (need_endian_swap_utf(encoding, wchar_encoding)) return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable); + + // source encoding is utf8 + if (encoding == encoding_utf8) return convert_buffer_utf8(out_buffer, out_length, contents, size); + + // source encoding is utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + return (native_encoding == encoding) ? + convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) : + convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true()); + } + + // source encoding is utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + return (native_encoding == encoding) ? + convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) : + convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true()); + } + + // source encoding is latin1 + if (encoding == encoding_latin1) return convert_buffer_latin1(out_buffer, out_length, contents, size); + + assert(!"Invalid encoding"); + return false; + } +#else + template PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) + { + const uint16_t* data = static_cast(contents); + size_t data_length = size / sizeof(uint16_t); + + // first pass: get length in utf8 units + size_t length = utf_decoder::decode_utf16_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf16 input to utf8 + uint8_t* obegin = reinterpret_cast(buffer); + uint8_t* oend = utf_decoder::decode_utf16_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + template PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) + { + const uint32_t* data = static_cast(contents); + size_t data_length = size / sizeof(uint32_t); + + // first pass: get length in utf8 units + size_t length = utf_decoder::decode_utf32_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf32 input to utf8 + uint8_t* obegin = reinterpret_cast(buffer); + uint8_t* oend = utf_decoder::decode_utf32_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN size_t get_latin1_7bit_prefix_length(const uint8_t* data, size_t size) + { + for (size_t i = 0; i < size; ++i) + if (data[i] > 127) + return i; + + return size; + } + + PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + { + const uint8_t* data = static_cast(contents); + size_t data_length = size; + + // get size of prefix that does not need utf8 conversion + size_t prefix_length = get_latin1_7bit_prefix_length(data, data_length); + assert(prefix_length <= data_length); + + const uint8_t* postfix = data + prefix_length; + size_t postfix_length = data_length - prefix_length; + + // if no conversion is needed, just return the original buffer + if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + + // first pass: get length in utf8 units + size_t length = prefix_length + utf_decoder::decode_latin1_block(postfix, postfix_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert latin1 input to utf8 + memcpy(buffer, data, prefix_length); + + uint8_t* obegin = reinterpret_cast(buffer); + uint8_t* oend = utf_decoder::decode_latin1_block(postfix, postfix_length, obegin + prefix_length); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) + { + // fast path: no conversion required + if (encoding == encoding_utf8) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + + // source encoding is utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + return (native_encoding == encoding) ? + convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) : + convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true()); + } + + // source encoding is utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + return (native_encoding == encoding) ? + convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) : + convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true()); + } + + // source encoding is latin1 + if (encoding == encoding_latin1) return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable); + + assert(!"Invalid encoding"); + return false; + } +#endif + + PUGI__FN size_t as_utf8_begin(const wchar_t* str, size_t length) + { + // get length in utf8 characters + return utf_decoder::decode_wchar_block(str, length, 0); + } + + PUGI__FN void as_utf8_end(char* buffer, size_t size, const wchar_t* str, size_t length) + { + // convert to utf8 + uint8_t* begin = reinterpret_cast(buffer); + uint8_t* end = utf_decoder::decode_wchar_block(str, length, begin); + + assert(begin + size == end); + (void)!end; + + // zero-terminate + buffer[size] = 0; + } + +#ifndef PUGIXML_NO_STL + PUGI__FN std::string as_utf8_impl(const wchar_t* str, size_t length) + { + // first pass: get length in utf8 characters + size_t size = as_utf8_begin(str, length); + + // allocate resulting string + std::string result; + result.resize(size); + + // second pass: convert to utf8 + if (size > 0) as_utf8_end(&result[0], size, str, length); + + return result; + } + + PUGI__FN std::basic_string as_wide_impl(const char* str, size_t size) + { + const uint8_t* data = reinterpret_cast(str); + + // first pass: get length in wchar_t units + size_t length = utf_decoder::decode_utf8_block(data, size, 0); + + // allocate resulting string + std::basic_string result; + result.resize(length); + + // second pass: convert to wchar_t + if (length > 0) + { + wchar_writer::value_type begin = reinterpret_cast(&result[0]); + wchar_writer::value_type end = utf_decoder::decode_utf8_block(data, size, begin); + + assert(begin + length == end); + (void)!end; + } + + return result; + } +#endif + + inline bool strcpy_insitu_allow(size_t length, uintptr_t header, uintptr_t header_mask, char_t* target) + { + // never reuse shared memory + if (header & xml_memory_page_contents_shared_mask) return false; + + size_t target_length = strlength(target); + + // always reuse document buffer memory if possible + if ((header & header_mask) == 0) return target_length >= length; + + // reuse heap memory if waste is not too great + const size_t reuse_threshold = 32; + + return target_length >= length && (target_length < reuse_threshold || target_length - length < target_length / 2); + } + + PUGI__FN bool strcpy_insitu(char_t*& dest, uintptr_t& header, uintptr_t header_mask, const char_t* source) + { + assert(header); + + size_t source_length = strlength(source); + + if (source_length == 0) + { + // empty string and null pointer are equivalent, so just deallocate old memory + xml_allocator* alloc = reinterpret_cast(header & xml_memory_page_pointer_mask)->allocator; + + if (header & header_mask) alloc->deallocate_string(dest); + + // mark the string as not allocated + dest = 0; + header &= ~header_mask; + + return true; + } + else if (dest && strcpy_insitu_allow(source_length, header, header_mask, dest)) + { + // we can reuse old buffer, so just copy the new data (including zero terminator) + memcpy(dest, source, (source_length + 1) * sizeof(char_t)); + + return true; + } + else + { + xml_allocator* alloc = reinterpret_cast(header & xml_memory_page_pointer_mask)->allocator; + + // allocate new buffer + char_t* buf = alloc->allocate_string(source_length + 1); + if (!buf) return false; + + // copy the string (including zero terminator) + memcpy(buf, source, (source_length + 1) * sizeof(char_t)); + + // deallocate old buffer (*after* the above to protect against overlapping memory and/or allocation failures) + if (header & header_mask) alloc->deallocate_string(dest); + + // the string is now allocated, so set the flag + dest = buf; + header |= header_mask; + + return true; + } + } + + struct gap + { + char_t* end; + size_t size; + + gap(): end(0), size(0) + { + } + + // Push new gap, move s count bytes further (skipping the gap). + // Collapse previous gap. + void push(char_t*& s, size_t count) + { + if (end) // there was a gap already; collapse it + { + // Move [old_gap_end, new_gap_start) to [old_gap_start, ...) + assert(s >= end); + memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); + } + + s += count; // end of current gap + + // "merge" two gaps + end = s; + size += count; + } + + // Collapse all gaps, return past-the-end pointer + char_t* flush(char_t* s) + { + if (end) + { + // Move [old_gap_end, current_pos) to [old_gap_start, ...) + assert(s >= end); + memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); + + return s - size; + } + else return s; + } + }; + + PUGI__FN char_t* strconv_escape(char_t* s, gap& g) + { + char_t* stre = s + 1; + + switch (*stre) + { + case '#': // &#... + { + unsigned int ucsc = 0; + + if (stre[1] == 'x') // &#x... (hex code) + { + stre += 2; + + char_t ch = *stre; + + if (ch == ';') return stre; + + for (;;) + { + if (static_cast(ch - '0') <= 9) + ucsc = 16 * ucsc + (ch - '0'); + else if (static_cast((ch | ' ') - 'a') <= 5) + ucsc = 16 * ucsc + ((ch | ' ') - 'a' + 10); + else if (ch == ';') + break; + else // cancel + return stre; + + ch = *++stre; + } + + ++stre; + } + else // &#... (dec code) + { + char_t ch = *++stre; + + if (ch == ';') return stre; + + for (;;) + { + if (static_cast(static_cast(ch) - '0') <= 9) + ucsc = 10 * ucsc + (ch - '0'); + else if (ch == ';') + break; + else // cancel + return stre; + + ch = *++stre; + } + + ++stre; + } + + #ifdef PUGIXML_WCHAR_MODE + s = reinterpret_cast(wchar_writer::any(reinterpret_cast(s), ucsc)); + #else + s = reinterpret_cast(utf8_writer::any(reinterpret_cast(s), ucsc)); + #endif + + g.push(s, stre - s); + return stre; + } + + case 'a': // &a + { + ++stre; + + if (*stre == 'm') // &am + { + if (*++stre == 'p' && *++stre == ';') // & + { + *s++ = '&'; + ++stre; + + g.push(s, stre - s); + return stre; + } + } + else if (*stre == 'p') // &ap + { + if (*++stre == 'o' && *++stre == 's' && *++stre == ';') // ' + { + *s++ = '\''; + ++stre; + + g.push(s, stre - s); + return stre; + } + } + break; + } + + case 'g': // &g + { + if (*++stre == 't' && *++stre == ';') // > + { + *s++ = '>'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; + } + + case 'l': // &l + { + if (*++stre == 't' && *++stre == ';') // < + { + *s++ = '<'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; + } + + case 'q': // &q + { + if (*++stre == 'u' && *++stre == 'o' && *++stre == 't' && *++stre == ';') // " + { + *s++ = '"'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; + } + + default: + break; + } + + return stre; + } + + // Parser utilities + #define PUGI__ENDSWITH(c, e) ((c) == (e) || ((c) == 0 && endch == (e))) + #define PUGI__SKIPWS() { while (PUGI__IS_CHARTYPE(*s, ct_space)) ++s; } + #define PUGI__OPTSET(OPT) ( optmsk & (OPT) ) + #define PUGI__PUSHNODE(TYPE) { cursor = append_new_node(cursor, alloc, TYPE); if (!cursor) PUGI__THROW_ERROR(status_out_of_memory, s); } + #define PUGI__POPNODE() { cursor = cursor->parent; } + #define PUGI__SCANFOR(X) { while (*s != 0 && !(X)) ++s; } + #define PUGI__SCANWHILE(X) { while (X) ++s; } + #define PUGI__SCANWHILE_UNROLL(X) { for (;;) { char_t ss = s[0]; if (PUGI__UNLIKELY(!(X))) { break; } ss = s[1]; if (PUGI__UNLIKELY(!(X))) { s += 1; break; } ss = s[2]; if (PUGI__UNLIKELY(!(X))) { s += 2; break; } ss = s[3]; if (PUGI__UNLIKELY(!(X))) { s += 3; break; } s += 4; } } + #define PUGI__ENDSEG() { ch = *s; *s = 0; ++s; } + #define PUGI__THROW_ERROR(err, m) return error_offset = m, error_status = err, static_cast(0) + #define PUGI__CHECK_ERROR(err, m) { if (*s == 0) PUGI__THROW_ERROR(err, m); } + + PUGI__FN char_t* strconv_comment(char_t* s, char_t endch) + { + gap g; + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_comment)); + + if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a + + if (*s == '\n') g.push(s, 1); + } + else if (s[0] == '-' && s[1] == '-' && PUGI__ENDSWITH(s[2], '>')) // comment ends here + { + *g.flush(s) = 0; + + return s + (s[2] == '>' ? 3 : 2); + } + else if (*s == 0) + { + return 0; + } + else ++s; + } + } + + PUGI__FN char_t* strconv_cdata(char_t* s, char_t endch) + { + gap g; + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_cdata)); + + if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a + + if (*s == '\n') g.push(s, 1); + } + else if (s[0] == ']' && s[1] == ']' && PUGI__ENDSWITH(s[2], '>')) // CDATA ends here + { + *g.flush(s) = 0; + + return s + 1; + } + else if (*s == 0) + { + return 0; + } + else ++s; + } + } + + typedef char_t* (*strconv_pcdata_t)(char_t*); + + template struct strconv_pcdata_impl + { + static char_t* parse(char_t* s) + { + gap g; + + char_t* begin = s; + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_pcdata)); + + if (*s == '<') // PCDATA ends here + { + char_t* end = g.flush(s); + + if (opt_trim::value) + while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space)) + --end; + + *end = 0; + + return s + 1; + } + else if (opt_eol::value && *s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a + + if (*s == '\n') g.push(s, 1); + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (*s == 0) + { + char_t* end = g.flush(s); + + if (opt_trim::value) + while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space)) + --end; + + *end = 0; + + return s; + } + else ++s; + } + } + }; + + PUGI__FN strconv_pcdata_t get_strconv_pcdata(unsigned int optmask) + { + PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_trim_pcdata == 0x0800); + + switch (((optmask >> 4) & 3) | ((optmask >> 9) & 4)) // get bitmask for flags (eol escapes trim) + { + case 0: return strconv_pcdata_impl::parse; + case 1: return strconv_pcdata_impl::parse; + case 2: return strconv_pcdata_impl::parse; + case 3: return strconv_pcdata_impl::parse; + case 4: return strconv_pcdata_impl::parse; + case 5: return strconv_pcdata_impl::parse; + case 6: return strconv_pcdata_impl::parse; + case 7: return strconv_pcdata_impl::parse; + default: assert(false); return 0; // should not get here + } + } + + typedef char_t* (*strconv_attribute_t)(char_t*, char_t); + + template struct strconv_attribute_impl + { + static char_t* parse_wnorm(char_t* s, char_t end_quote) + { + gap g; + + // trim leading whitespaces + if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + char_t* str = s; + + do ++str; + while (PUGI__IS_CHARTYPE(*str, ct_space)); + + g.push(s, str - s); + } + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr_ws | ct_space)); + + if (*s == end_quote) + { + char_t* str = g.flush(s); + + do *str-- = 0; + while (PUGI__IS_CHARTYPE(*str, ct_space)); + + return s + 1; + } + else if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + *s++ = ' '; + + if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + char_t* str = s + 1; + while (PUGI__IS_CHARTYPE(*str, ct_space)) ++str; + + g.push(s, str - s); + } + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + + static char_t* parse_wconv(char_t* s, char_t end_quote) + { + gap g; + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr_ws)); + + if (*s == end_quote) + { + *g.flush(s) = 0; + + return s + 1; + } + else if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + if (*s == '\r') + { + *s++ = ' '; + + if (*s == '\n') g.push(s, 1); + } + else *s++ = ' '; + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + + static char_t* parse_eol(char_t* s, char_t end_quote) + { + gap g; + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr)); + + if (*s == end_quote) + { + *g.flush(s) = 0; + + return s + 1; + } + else if (*s == '\r') + { + *s++ = '\n'; + + if (*s == '\n') g.push(s, 1); + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + + static char_t* parse_simple(char_t* s, char_t end_quote) + { + gap g; + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr)); + + if (*s == end_quote) + { + *g.flush(s) = 0; + + return s + 1; + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + }; + + PUGI__FN strconv_attribute_t get_strconv_attribute(unsigned int optmask) + { + PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_wconv_attribute == 0x40 && parse_wnorm_attribute == 0x80); + + switch ((optmask >> 4) & 15) // get bitmask for flags (wconv wnorm eol escapes) + { + case 0: return strconv_attribute_impl::parse_simple; + case 1: return strconv_attribute_impl::parse_simple; + case 2: return strconv_attribute_impl::parse_eol; + case 3: return strconv_attribute_impl::parse_eol; + case 4: return strconv_attribute_impl::parse_wconv; + case 5: return strconv_attribute_impl::parse_wconv; + case 6: return strconv_attribute_impl::parse_wconv; + case 7: return strconv_attribute_impl::parse_wconv; + case 8: return strconv_attribute_impl::parse_wnorm; + case 9: return strconv_attribute_impl::parse_wnorm; + case 10: return strconv_attribute_impl::parse_wnorm; + case 11: return strconv_attribute_impl::parse_wnorm; + case 12: return strconv_attribute_impl::parse_wnorm; + case 13: return strconv_attribute_impl::parse_wnorm; + case 14: return strconv_attribute_impl::parse_wnorm; + case 15: return strconv_attribute_impl::parse_wnorm; + default: assert(false); return 0; // should not get here + } + } + + inline xml_parse_result make_parse_result(xml_parse_status status, ptrdiff_t offset = 0) + { + xml_parse_result result; + result.status = status; + result.offset = offset; + + return result; + } + + struct xml_parser + { + xml_allocator alloc; + char_t* error_offset; + xml_parse_status error_status; + + xml_parser(const xml_allocator& alloc_): alloc(alloc_), error_offset(0), error_status(status_ok) + { + } + + // DOCTYPE consists of nested sections of the following possible types: + // , , "...", '...' + // + // + // First group can not contain nested groups + // Second group can contain nested groups of the same type + // Third group can contain all other groups + char_t* parse_doctype_primitive(char_t* s) + { + if (*s == '"' || *s == '\'') + { + // quoted string + char_t ch = *s++; + PUGI__SCANFOR(*s == ch); + if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + + s++; + } + else if (s[0] == '<' && s[1] == '?') + { + // + s += 2; + PUGI__SCANFOR(s[0] == '?' && s[1] == '>'); // no need for ENDSWITH because ?> can't terminate proper doctype + if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + + s += 2; + } + else if (s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-') + { + s += 4; + PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && s[2] == '>'); // no need for ENDSWITH because --> can't terminate proper doctype + if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + + s += 3; + } + else PUGI__THROW_ERROR(status_bad_doctype, s); + + return s; + } + + char_t* parse_doctype_ignore(char_t* s) + { + size_t depth = 0; + + assert(s[0] == '<' && s[1] == '!' && s[2] == '['); + s += 3; + + while (*s) + { + if (s[0] == '<' && s[1] == '!' && s[2] == '[') + { + // nested ignore section + s += 3; + depth++; + } + else if (s[0] == ']' && s[1] == ']' && s[2] == '>') + { + // ignore section end + s += 3; + + if (depth == 0) + return s; + + depth--; + } + else s++; + } + + PUGI__THROW_ERROR(status_bad_doctype, s); + } + + char_t* parse_doctype_group(char_t* s, char_t endch) + { + size_t depth = 0; + + assert((s[0] == '<' || s[0] == 0) && s[1] == '!'); + s += 2; + + while (*s) + { + if (s[0] == '<' && s[1] == '!' && s[2] != '-') + { + if (s[2] == '[') + { + // ignore + s = parse_doctype_ignore(s); + if (!s) return s; + } + else + { + // some control group + s += 2; + depth++; + } + } + else if (s[0] == '<' || s[0] == '"' || s[0] == '\'') + { + // unknown tag (forbidden), or some primitive group + s = parse_doctype_primitive(s); + if (!s) return s; + } + else if (*s == '>') + { + if (depth == 0) + return s; + + depth--; + s++; + } + else s++; + } + + if (depth != 0 || endch != '>') PUGI__THROW_ERROR(status_bad_doctype, s); + + return s; + } + + char_t* parse_exclamation(char_t* s, xml_node_struct* cursor, unsigned int optmsk, char_t endch) + { + // parse node contents, starting with exclamation mark + ++s; + + if (*s == '-') // 'value = s; // Save the offset. + } + + if (PUGI__OPTSET(parse_eol) && PUGI__OPTSET(parse_comments)) + { + s = strconv_comment(s, endch); + + if (!s) PUGI__THROW_ERROR(status_bad_comment, cursor->value); + } + else + { + // Scan for terminating '-->'. + PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && PUGI__ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_comment, s); + + if (PUGI__OPTSET(parse_comments)) + *s = 0; // Zero-terminate this segment at the first terminating '-'. + + s += (s[2] == '>' ? 3 : 2); // Step over the '\0->'. + } + } + else PUGI__THROW_ERROR(status_bad_comment, s); + } + else if (*s == '[') + { + // 'value = s; // Save the offset. + + if (PUGI__OPTSET(parse_eol)) + { + s = strconv_cdata(s, endch); + + if (!s) PUGI__THROW_ERROR(status_bad_cdata, cursor->value); + } + else + { + // Scan for terminating ']]>'. + PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && PUGI__ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_cdata, s); + + *s++ = 0; // Zero-terminate this segment. + } + } + else // Flagged for discard, but we still have to scan for the terminator. + { + // Scan for terminating ']]>'. + PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && PUGI__ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_cdata, s); + + ++s; + } + + s += (s[1] == '>' ? 2 : 1); // Step over the last ']>'. + } + else PUGI__THROW_ERROR(status_bad_cdata, s); + } + else if (s[0] == 'D' && s[1] == 'O' && s[2] == 'C' && s[3] == 'T' && s[4] == 'Y' && s[5] == 'P' && PUGI__ENDSWITH(s[6], 'E')) + { + s -= 2; + + if (cursor->parent) PUGI__THROW_ERROR(status_bad_doctype, s); + + char_t* mark = s + 9; + + s = parse_doctype_group(s, endch); + if (!s) return s; + + assert((*s == 0 && endch == '>') || *s == '>'); + if (*s) *s++ = 0; + + if (PUGI__OPTSET(parse_doctype)) + { + while (PUGI__IS_CHARTYPE(*mark, ct_space)) ++mark; + + PUGI__PUSHNODE(node_doctype); + + cursor->value = mark; + } + } + else if (*s == 0 && endch == '-') PUGI__THROW_ERROR(status_bad_comment, s); + else if (*s == 0 && endch == '[') PUGI__THROW_ERROR(status_bad_cdata, s); + else PUGI__THROW_ERROR(status_unrecognized_tag, s); + + return s; + } + + char_t* parse_question(char_t* s, xml_node_struct*& ref_cursor, unsigned int optmsk, char_t endch) + { + // load into registers + xml_node_struct* cursor = ref_cursor; + char_t ch = 0; + + // parse node contents, starting with question mark + ++s; + + // read PI target + char_t* target = s; + + if (!PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_pi, s); + + PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); + PUGI__CHECK_ERROR(status_bad_pi, s); + + // determine node type; stricmp / strcasecmp is not portable + bool declaration = (target[0] | ' ') == 'x' && (target[1] | ' ') == 'm' && (target[2] | ' ') == 'l' && target + 3 == s; + + if (declaration ? PUGI__OPTSET(parse_declaration) : PUGI__OPTSET(parse_pi)) + { + if (declaration) + { + // disallow non top-level declarations + if (cursor->parent) PUGI__THROW_ERROR(status_bad_pi, s); + + PUGI__PUSHNODE(node_declaration); + } + else + { + PUGI__PUSHNODE(node_pi); + } + + cursor->name = target; + + PUGI__ENDSEG(); + + // parse value/attributes + if (ch == '?') + { + // empty node + if (!PUGI__ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_pi, s); + s += (*s == '>'); + + PUGI__POPNODE(); + } + else if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + PUGI__SKIPWS(); + + // scan for tag end + char_t* value = s; + + PUGI__SCANFOR(s[0] == '?' && PUGI__ENDSWITH(s[1], '>')); + PUGI__CHECK_ERROR(status_bad_pi, s); + + if (declaration) + { + // replace ending ? with / so that 'element' terminates properly + *s = '/'; + + // we exit from this function with cursor at node_declaration, which is a signal to parse() to go to LOC_ATTRIBUTES + s = value; + } + else + { + // store value and step over > + cursor->value = value; + PUGI__POPNODE(); + + PUGI__ENDSEG(); + + s += (*s == '>'); + } + } + else PUGI__THROW_ERROR(status_bad_pi, s); + } + else + { + // scan for tag end + PUGI__SCANFOR(s[0] == '?' && PUGI__ENDSWITH(s[1], '>')); + PUGI__CHECK_ERROR(status_bad_pi, s); + + s += (s[1] == '>' ? 2 : 1); + } + + // store from registers + ref_cursor = cursor; + + return s; + } + + char_t* parse_tree(char_t* s, xml_node_struct* root, unsigned int optmsk, char_t endch) + { + strconv_attribute_t strconv_attribute = get_strconv_attribute(optmsk); + strconv_pcdata_t strconv_pcdata = get_strconv_pcdata(optmsk); + + char_t ch = 0; + xml_node_struct* cursor = root; + char_t* mark = s; + + while (*s != 0) + { + if (*s == '<') + { + ++s; + + LOC_TAG: + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // '<#...' + { + PUGI__PUSHNODE(node_element); // Append a new node to the tree. + + cursor->name = s; + + PUGI__SCANWHILE_UNROLL(PUGI__IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator. + PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. + + if (ch == '>') + { + // end of tag + } + else if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + LOC_ATTRIBUTES: + while (true) + { + PUGI__SKIPWS(); // Eat any whitespace. + + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // <... #... + { + xml_attribute_struct* a = append_new_attribute(cursor, alloc); // Make space for this attribute. + if (!a) PUGI__THROW_ERROR(status_out_of_memory, s); + + a->name = s; // Save the offset. + + PUGI__SCANWHILE_UNROLL(PUGI__IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator. + PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. + + if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + PUGI__SKIPWS(); // Eat any whitespace. + + ch = *s; + ++s; + } + + if (ch == '=') // '<... #=...' + { + PUGI__SKIPWS(); // Eat any whitespace. + + if (*s == '"' || *s == '\'') // '<... #="...' + { + ch = *s; // Save quote char to avoid breaking on "''" -or- '""'. + ++s; // Step over the quote. + a->value = s; // Save the offset. + + s = strconv_attribute(s, ch); + + if (!s) PUGI__THROW_ERROR(status_bad_attribute, a->value); + + // After this line the loop continues from the start; + // Whitespaces, / and > are ok, symbols and EOF are wrong, + // everything else will be detected + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_attribute, s); + } + else PUGI__THROW_ERROR(status_bad_attribute, s); + } + else PUGI__THROW_ERROR(status_bad_attribute, s); + } + else if (*s == '/') + { + ++s; + + if (*s == '>') + { + PUGI__POPNODE(); + s++; + break; + } + else if (*s == 0 && endch == '>') + { + PUGI__POPNODE(); + break; + } + else PUGI__THROW_ERROR(status_bad_start_element, s); + } + else if (*s == '>') + { + ++s; + + break; + } + else if (*s == 0 && endch == '>') + { + break; + } + else PUGI__THROW_ERROR(status_bad_start_element, s); + } + + // !!! + } + else if (ch == '/') // '<#.../' + { + if (!PUGI__ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_start_element, s); + + PUGI__POPNODE(); // Pop. + + s += (*s == '>'); + } + else if (ch == 0) + { + // we stepped over null terminator, backtrack & handle closing tag + --s; + + if (endch != '>') PUGI__THROW_ERROR(status_bad_start_element, s); + } + else PUGI__THROW_ERROR(status_bad_start_element, s); + } + else if (*s == '/') + { + ++s; + + char_t* name = cursor->name; + if (!name) PUGI__THROW_ERROR(status_end_element_mismatch, s); + + while (PUGI__IS_CHARTYPE(*s, ct_symbol)) + { + if (*s++ != *name++) PUGI__THROW_ERROR(status_end_element_mismatch, s); + } + + if (*name) + { + if (*s == 0 && name[0] == endch && name[1] == 0) PUGI__THROW_ERROR(status_bad_end_element, s); + else PUGI__THROW_ERROR(status_end_element_mismatch, s); + } + + PUGI__POPNODE(); // Pop. + + PUGI__SKIPWS(); + + if (*s == 0) + { + if (endch != '>') PUGI__THROW_ERROR(status_bad_end_element, s); + } + else + { + if (*s != '>') PUGI__THROW_ERROR(status_bad_end_element, s); + ++s; + } + } + else if (*s == '?') // 'first_child) continue; + } + } + + if (!PUGI__OPTSET(parse_trim_pcdata)) + s = mark; + + if (cursor->parent || PUGI__OPTSET(parse_fragment)) + { + PUGI__PUSHNODE(node_pcdata); // Append a new node on the tree. + cursor->value = s; // Save the offset. + + s = strconv_pcdata(s); + + PUGI__POPNODE(); // Pop since this is a standalone. + + if (!*s) break; + } + else + { + PUGI__SCANFOR(*s == '<'); // '...<' + if (!*s) break; + + ++s; + } + + // We're after '<' + goto LOC_TAG; + } + } + + // check that last tag is closed + if (cursor != root) PUGI__THROW_ERROR(status_end_element_mismatch, s); + + return s; + } + + #ifdef PUGIXML_WCHAR_MODE + static char_t* parse_skip_bom(char_t* s) + { + unsigned int bom = 0xfeff; + return (s[0] == static_cast(bom)) ? s + 1 : s; + } + #else + static char_t* parse_skip_bom(char_t* s) + { + return (s[0] == '\xef' && s[1] == '\xbb' && s[2] == '\xbf') ? s + 3 : s; + } + #endif + + static bool has_element_node_siblings(xml_node_struct* node) + { + while (node) + { + if (PUGI__NODETYPE(node) == node_element) return true; + + node = node->next_sibling; + } + + return false; + } + + static xml_parse_result parse(char_t* buffer, size_t length, xml_document_struct* xmldoc, xml_node_struct* root, unsigned int optmsk) + { + // allocator object is a part of document object + xml_allocator& alloc_ = *static_cast(xmldoc); + + // early-out for empty documents + if (length == 0) + return make_parse_result(PUGI__OPTSET(parse_fragment) ? status_ok : status_no_document_element); + + // get last child of the root before parsing + xml_node_struct* last_root_child = root->first_child ? root->first_child->prev_sibling_c : 0; + + // create parser on stack + xml_parser parser(alloc_); + + // save last character and make buffer zero-terminated (speeds up parsing) + char_t endch = buffer[length - 1]; + buffer[length - 1] = 0; + + // skip BOM to make sure it does not end up as part of parse output + char_t* buffer_data = parse_skip_bom(buffer); + + // perform actual parsing + parser.parse_tree(buffer_data, root, optmsk, endch); + + // update allocator state + alloc_ = parser.alloc; + + xml_parse_result result = make_parse_result(parser.error_status, parser.error_offset ? parser.error_offset - buffer : 0); + assert(result.offset >= 0 && static_cast(result.offset) <= length); + + if (result) + { + // since we removed last character, we have to handle the only possible false positive (stray <) + if (endch == '<') + return make_parse_result(status_unrecognized_tag, length - 1); + + // check if there are any element nodes parsed + xml_node_struct* first_root_child_parsed = last_root_child ? last_root_child->next_sibling : root->first_child; + + if (!PUGI__OPTSET(parse_fragment) && !has_element_node_siblings(first_root_child_parsed)) + return make_parse_result(status_no_document_element, length - 1); + } + else + { + // roll back offset if it occurs on a null terminator in the source buffer + if (result.offset > 0 && static_cast(result.offset) == length - 1 && endch == 0) + result.offset--; + } + + return result; + } + }; + + // Output facilities + PUGI__FN xml_encoding get_write_native_encoding() + { + #ifdef PUGIXML_WCHAR_MODE + return get_wchar_encoding(); + #else + return encoding_utf8; + #endif + } + + PUGI__FN xml_encoding get_write_encoding(xml_encoding encoding) + { + // replace wchar encoding with utf implementation + if (encoding == encoding_wchar) return get_wchar_encoding(); + + // replace utf16 encoding with utf16 with specific endianness + if (encoding == encoding_utf16) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + // replace utf32 encoding with utf32 with specific endianness + if (encoding == encoding_utf32) return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + // only do autodetection if no explicit encoding is requested + if (encoding != encoding_auto) return encoding; + + // assume utf8 encoding + return encoding_utf8; + } + +#ifdef PUGIXML_WCHAR_MODE + PUGI__FN size_t get_valid_length(const char_t* data, size_t length) + { + if (length < 1) return 0; + + // discard last character if it's the lead of a surrogate pair + return (sizeof(wchar_t) == 2 && static_cast(static_cast(data[length - 1]) - 0xD800) < 0x400) ? length - 1 : length; + } + + PUGI__FN size_t convert_buffer_output(char_t* r_char, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) + { + // only endian-swapping is required + if (need_endian_swap_utf(encoding, get_wchar_encoding())) + { + convert_wchar_endian_swap(r_char, data, length); + + return length * sizeof(char_t); + } + + // convert to utf8 + if (encoding == encoding_utf8) + { + uint8_t* dest = r_u8; + uint8_t* end = utf_decoder::decode_wchar_block(data, length, dest); + + return static_cast(end - dest); + } + + // convert to utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + uint16_t* dest = r_u16; + + // convert to native utf16 + uint16_t* end = utf_decoder::decode_wchar_block(data, length, dest); + + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); + + return static_cast(end - dest) * sizeof(uint16_t); + } + + // convert to utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + uint32_t* dest = r_u32; + + // convert to native utf32 + uint32_t* end = utf_decoder::decode_wchar_block(data, length, dest); + + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); + + return static_cast(end - dest) * sizeof(uint32_t); + } + + // convert to latin1 + if (encoding == encoding_latin1) + { + uint8_t* dest = r_u8; + uint8_t* end = utf_decoder::decode_wchar_block(data, length, dest); + + return static_cast(end - dest); + } + + assert(!"Invalid encoding"); + return 0; + } +#else + PUGI__FN size_t get_valid_length(const char_t* data, size_t length) + { + if (length < 5) return 0; + + for (size_t i = 1; i <= 4; ++i) + { + uint8_t ch = static_cast(data[length - i]); + + // either a standalone character or a leading one + if ((ch & 0xc0) != 0x80) return length - i; + } + + // there are four non-leading characters at the end, sequence tail is broken so might as well process the whole chunk + return length; + } + + PUGI__FN size_t convert_buffer_output(char_t* /* r_char */, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) + { + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + uint16_t* dest = r_u16; + + // convert to native utf16 + uint16_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); + + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); + + return static_cast(end - dest) * sizeof(uint16_t); + } + + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + uint32_t* dest = r_u32; + + // convert to native utf32 + uint32_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); + + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); + + return static_cast(end - dest) * sizeof(uint32_t); + } + + if (encoding == encoding_latin1) + { + uint8_t* dest = r_u8; + uint8_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); + + return static_cast(end - dest); + } + + assert(!"Invalid encoding"); + return 0; + } +#endif + + class xml_buffered_writer + { + xml_buffered_writer(const xml_buffered_writer&); + xml_buffered_writer& operator=(const xml_buffered_writer&); + + public: + xml_buffered_writer(xml_writer& writer_, xml_encoding user_encoding): writer(writer_), bufsize(0), encoding(get_write_encoding(user_encoding)) + { + PUGI__STATIC_ASSERT(bufcapacity >= 8); + } + + ~xml_buffered_writer() + { + flush(); + } + + size_t flush() + { + flush(buffer, bufsize); + bufsize = 0; + return 0; + } + + void flush(const char_t* data, size_t size) + { + if (size == 0) return; + + // fast path, just write data + if (encoding == get_write_native_encoding()) + writer.write(data, size * sizeof(char_t)); + else + { + // convert chunk + size_t result = convert_buffer_output(scratch.data_char, scratch.data_u8, scratch.data_u16, scratch.data_u32, data, size, encoding); + assert(result <= sizeof(scratch)); + + // write data + writer.write(scratch.data_u8, result); + } + } + + void write_direct(const char_t* data, size_t length) + { + // flush the remaining buffer contents + flush(); + + // handle large chunks + if (length > bufcapacity) + { + if (encoding == get_write_native_encoding()) + { + // fast path, can just write data chunk + writer.write(data, length * sizeof(char_t)); + return; + } + + // need to convert in suitable chunks + while (length > bufcapacity) + { + // get chunk size by selecting such number of characters that are guaranteed to fit into scratch buffer + // and form a complete codepoint sequence (i.e. discard start of last codepoint if necessary) + size_t chunk_size = get_valid_length(data, bufcapacity); + assert(chunk_size); + + // convert chunk and write + flush(data, chunk_size); + + // iterate + data += chunk_size; + length -= chunk_size; + } + + // small tail is copied below + bufsize = 0; + } + + memcpy(buffer + bufsize, data, length * sizeof(char_t)); + bufsize += length; + } + + void write_buffer(const char_t* data, size_t length) + { + size_t offset = bufsize; + + if (offset + length <= bufcapacity) + { + memcpy(buffer + offset, data, length * sizeof(char_t)); + bufsize = offset + length; + } + else + { + write_direct(data, length); + } + } + + void write_string(const char_t* data) + { + // write the part of the string that fits in the buffer + size_t offset = bufsize; + + while (*data && offset < bufcapacity) + buffer[offset++] = *data++; + + // write the rest + if (offset < bufcapacity) + { + bufsize = offset; + } + else + { + // backtrack a bit if we have split the codepoint + size_t length = offset - bufsize; + size_t extra = length - get_valid_length(data - length, length); + + bufsize = offset - extra; + + write_direct(data - extra, strlength(data) + extra); + } + } + + void write(char_t d0) + { + size_t offset = bufsize; + if (offset > bufcapacity - 1) offset = flush(); + + buffer[offset + 0] = d0; + bufsize = offset + 1; + } + + void write(char_t d0, char_t d1) + { + size_t offset = bufsize; + if (offset > bufcapacity - 2) offset = flush(); + + buffer[offset + 0] = d0; + buffer[offset + 1] = d1; + bufsize = offset + 2; + } + + void write(char_t d0, char_t d1, char_t d2) + { + size_t offset = bufsize; + if (offset > bufcapacity - 3) offset = flush(); + + buffer[offset + 0] = d0; + buffer[offset + 1] = d1; + buffer[offset + 2] = d2; + bufsize = offset + 3; + } + + void write(char_t d0, char_t d1, char_t d2, char_t d3) + { + size_t offset = bufsize; + if (offset > bufcapacity - 4) offset = flush(); + + buffer[offset + 0] = d0; + buffer[offset + 1] = d1; + buffer[offset + 2] = d2; + buffer[offset + 3] = d3; + bufsize = offset + 4; + } + + void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4) + { + size_t offset = bufsize; + if (offset > bufcapacity - 5) offset = flush(); + + buffer[offset + 0] = d0; + buffer[offset + 1] = d1; + buffer[offset + 2] = d2; + buffer[offset + 3] = d3; + buffer[offset + 4] = d4; + bufsize = offset + 5; + } + + void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4, char_t d5) + { + size_t offset = bufsize; + if (offset > bufcapacity - 6) offset = flush(); + + buffer[offset + 0] = d0; + buffer[offset + 1] = d1; + buffer[offset + 2] = d2; + buffer[offset + 3] = d3; + buffer[offset + 4] = d4; + buffer[offset + 5] = d5; + bufsize = offset + 6; + } + + // utf8 maximum expansion: x4 (-> utf32) + // utf16 maximum expansion: x2 (-> utf32) + // utf32 maximum expansion: x1 + enum + { + bufcapacitybytes = + #ifdef PUGIXML_MEMORY_OUTPUT_STACK + PUGIXML_MEMORY_OUTPUT_STACK + #else + 10240 + #endif + , + bufcapacity = bufcapacitybytes / (sizeof(char_t) + 4) + }; + + char_t buffer[bufcapacity]; + + union + { + uint8_t data_u8[4 * bufcapacity]; + uint16_t data_u16[2 * bufcapacity]; + uint32_t data_u32[bufcapacity]; + char_t data_char[bufcapacity]; + } scratch; + + xml_writer& writer; + size_t bufsize; + xml_encoding encoding; + }; + + PUGI__FN void text_output_escaped(xml_buffered_writer& writer, const char_t* s, chartypex_t type) + { + while (*s) + { + const char_t* prev = s; + + // While *s is a usual symbol + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPEX(ss, type)); + + writer.write_buffer(prev, static_cast(s - prev)); + + switch (*s) + { + case 0: break; + case '&': + writer.write('&', 'a', 'm', 'p', ';'); + ++s; + break; + case '<': + writer.write('&', 'l', 't', ';'); + ++s; + break; + case '>': + writer.write('&', 'g', 't', ';'); + ++s; + break; + case '"': + writer.write('&', 'q', 'u', 'o', 't', ';'); + ++s; + break; + default: // s is not a usual symbol + { + unsigned int ch = static_cast(*s++); + assert(ch < 32); + + writer.write('&', '#', static_cast((ch / 10) + '0'), static_cast((ch % 10) + '0'), ';'); + } + } + } + } + + PUGI__FN void text_output(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) + { + if (flags & format_no_escapes) + writer.write_string(s); + else + text_output_escaped(writer, s, type); + } + + PUGI__FN void text_output_cdata(xml_buffered_writer& writer, const char_t* s) + { + do + { + writer.write('<', '!', '[', 'C', 'D'); + writer.write('A', 'T', 'A', '['); + + const char_t* prev = s; + + // look for ]]> sequence - we can't output it as is since it terminates CDATA + while (*s && !(s[0] == ']' && s[1] == ']' && s[2] == '>')) ++s; + + // skip ]] if we stopped at ]]>, > will go to the next CDATA section + if (*s) s += 2; + + writer.write_buffer(prev, static_cast(s - prev)); + + writer.write(']', ']', '>'); + } + while (*s); + } + + PUGI__FN void text_output_indent(xml_buffered_writer& writer, const char_t* indent, size_t indent_length, unsigned int depth) + { + switch (indent_length) + { + case 1: + { + for (unsigned int i = 0; i < depth; ++i) + writer.write(indent[0]); + break; + } + + case 2: + { + for (unsigned int i = 0; i < depth; ++i) + writer.write(indent[0], indent[1]); + break; + } + + case 3: + { + for (unsigned int i = 0; i < depth; ++i) + writer.write(indent[0], indent[1], indent[2]); + break; + } + + case 4: + { + for (unsigned int i = 0; i < depth; ++i) + writer.write(indent[0], indent[1], indent[2], indent[3]); + break; + } + + default: + { + for (unsigned int i = 0; i < depth; ++i) + writer.write_buffer(indent, indent_length); + } + } + } + + PUGI__FN void node_output_comment(xml_buffered_writer& writer, const char_t* s) + { + writer.write('<', '!', '-', '-'); + + while (*s) + { + const char_t* prev = s; + + // look for -\0 or -- sequence - we can't output it since -- is illegal in comment body + while (*s && !(s[0] == '-' && (s[1] == '-' || s[1] == 0))) ++s; + + writer.write_buffer(prev, static_cast(s - prev)); + + if (*s) + { + assert(*s == '-'); + + writer.write('-', ' '); + ++s; + } + } + + writer.write('-', '-', '>'); + } + + PUGI__FN void node_output_pi_value(xml_buffered_writer& writer, const char_t* s) + { + while (*s) + { + const char_t* prev = s; + + // look for ?> sequence - we can't output it since ?> terminates PI + while (*s && !(s[0] == '?' && s[1] == '>')) ++s; + + writer.write_buffer(prev, static_cast(s - prev)); + + if (*s) + { + assert(s[0] == '?' && s[1] == '>'); + + writer.write('?', ' ', '>'); + s += 2; + } + } + } + + PUGI__FN void node_output_attributes(xml_buffered_writer& writer, xml_node_struct* node, unsigned int flags) + { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); + + for (xml_attribute_struct* a = node->first_attribute; a; a = a->next_attribute) + { + writer.write(' '); + writer.write_string(a->name ? a->name : default_name); + writer.write('=', '"'); + + if (a->value) + text_output(writer, a->value, ctx_special_attr, flags); + + writer.write('"'); + } + } + + PUGI__FN bool node_output_start(xml_buffered_writer& writer, xml_node_struct* node, unsigned int flags) + { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); + const char_t* name = node->name ? node->name : default_name; + + writer.write('<'); + writer.write_string(name); + + if (node->first_attribute) + node_output_attributes(writer, node, flags); + + if (!node->first_child) + { + writer.write(' ', '/', '>'); + + return false; + } + else + { + writer.write('>'); + + return true; + } + } + + PUGI__FN void node_output_end(xml_buffered_writer& writer, xml_node_struct* node) + { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); + const char_t* name = node->name ? node->name : default_name; + + writer.write('<', '/'); + writer.write_string(name); + writer.write('>'); + } + + PUGI__FN void node_output_simple(xml_buffered_writer& writer, xml_node_struct* node, unsigned int flags) + { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); + + switch (PUGI__NODETYPE(node)) + { + case node_pcdata: + text_output(writer, node->value ? node->value : PUGIXML_TEXT(""), ctx_special_pcdata, flags); + break; + + case node_cdata: + text_output_cdata(writer, node->value ? node->value : PUGIXML_TEXT("")); + break; + + case node_comment: + node_output_comment(writer, node->value ? node->value : PUGIXML_TEXT("")); + break; + + case node_pi: + writer.write('<', '?'); + writer.write_string(node->name ? node->name : default_name); + + if (node->value) + { + writer.write(' '); + node_output_pi_value(writer, node->value); + } + + writer.write('?', '>'); + break; + + case node_declaration: + writer.write('<', '?'); + writer.write_string(node->name ? node->name : default_name); + node_output_attributes(writer, node, flags); + writer.write('?', '>'); + break; + + case node_doctype: + writer.write('<', '!', 'D', 'O', 'C'); + writer.write('T', 'Y', 'P', 'E'); + + if (node->value) + { + writer.write(' '); + writer.write_string(node->value); + } + + writer.write('>'); + break; + + default: + assert(!"Invalid node type"); + } + } + + enum indent_flags_t + { + indent_newline = 1, + indent_indent = 2 + }; + + PUGI__FN void node_output(xml_buffered_writer& writer, xml_node_struct* root, const char_t* indent, unsigned int flags, unsigned int depth) + { + size_t indent_length = ((flags & (format_indent | format_raw)) == format_indent) ? strlength(indent) : 0; + unsigned int indent_flags = indent_indent; + + xml_node_struct* node = root; + + do + { + assert(node); + + // begin writing current node + if (PUGI__NODETYPE(node) == node_pcdata || PUGI__NODETYPE(node) == node_cdata) + { + node_output_simple(writer, node, flags); + + indent_flags = 0; + } + else + { + if ((indent_flags & indent_newline) && (flags & format_raw) == 0) + writer.write('\n'); + + if ((indent_flags & indent_indent) && indent_length) + text_output_indent(writer, indent, indent_length, depth); + + if (PUGI__NODETYPE(node) == node_element) + { + indent_flags = indent_newline | indent_indent; + + if (node_output_start(writer, node, flags)) + { + node = node->first_child; + depth++; + continue; + } + } + else if (PUGI__NODETYPE(node) == node_document) + { + indent_flags = indent_indent; + + if (node->first_child) + { + node = node->first_child; + continue; + } + } + else + { + node_output_simple(writer, node, flags); + + indent_flags = indent_newline | indent_indent; + } + } + + // continue to the next node + while (node != root) + { + if (node->next_sibling) + { + node = node->next_sibling; + break; + } + + node = node->parent; + + // write closing node + if (PUGI__NODETYPE(node) == node_element) + { + depth--; + + if ((indent_flags & indent_newline) && (flags & format_raw) == 0) + writer.write('\n'); + + if ((indent_flags & indent_indent) && indent_length) + text_output_indent(writer, indent, indent_length, depth); + + node_output_end(writer, node); + + indent_flags = indent_newline | indent_indent; + } + } + } + while (node != root); + + if ((indent_flags & indent_newline) && (flags & format_raw) == 0) + writer.write('\n'); + } + + PUGI__FN bool has_declaration(xml_node_struct* node) + { + for (xml_node_struct* child = node->first_child; child; child = child->next_sibling) + { + xml_node_type type = PUGI__NODETYPE(child); + + if (type == node_declaration) return true; + if (type == node_element) return false; + } + + return false; + } + + PUGI__FN bool is_attribute_of(xml_attribute_struct* attr, xml_node_struct* node) + { + for (xml_attribute_struct* a = node->first_attribute; a; a = a->next_attribute) + if (a == attr) + return true; + + return false; + } + + PUGI__FN bool allow_insert_attribute(xml_node_type parent) + { + return parent == node_element || parent == node_declaration; + } + + PUGI__FN bool allow_insert_child(xml_node_type parent, xml_node_type child) + { + if (parent != node_document && parent != node_element) return false; + if (child == node_document || child == node_null) return false; + if (parent != node_document && (child == node_declaration || child == node_doctype)) return false; + + return true; + } + + PUGI__FN bool allow_move(xml_node parent, xml_node child) + { + // check that child can be a child of parent + if (!allow_insert_child(parent.type(), child.type())) + return false; + + // check that node is not moved between documents + if (parent.root() != child.root()) + return false; + + // check that new parent is not in the child subtree + xml_node cur = parent; + + while (cur) + { + if (cur == child) + return false; + + cur = cur.parent(); + } + + return true; + } + + PUGI__FN void node_copy_string(char_t*& dest, uintptr_t& header, uintptr_t header_mask, char_t* source, uintptr_t& source_header, xml_allocator* alloc) + { + assert(!dest && (header & header_mask) == 0); + + if (source) + { + if (alloc && (source_header & header_mask) == 0) + { + dest = source; + + // since strcpy_insitu can reuse document buffer memory we need to mark both source and dest as shared + header |= xml_memory_page_contents_shared_mask; + source_header |= xml_memory_page_contents_shared_mask; + } + else + strcpy_insitu(dest, header, header_mask, source); + } + } + + PUGI__FN void node_copy_contents(xml_node_struct* dn, xml_node_struct* sn, xml_allocator* shared_alloc) + { + node_copy_string(dn->name, dn->header, xml_memory_page_name_allocated_mask, sn->name, sn->header, shared_alloc); + node_copy_string(dn->value, dn->header, xml_memory_page_value_allocated_mask, sn->value, sn->header, shared_alloc); + + for (xml_attribute_struct* sa = sn->first_attribute; sa; sa = sa->next_attribute) + { + xml_attribute_struct* da = append_new_attribute(dn, get_allocator(dn)); + + if (da) + { + node_copy_string(da->name, da->header, xml_memory_page_name_allocated_mask, sa->name, sa->header, shared_alloc); + node_copy_string(da->value, da->header, xml_memory_page_value_allocated_mask, sa->value, sa->header, shared_alloc); + } + } + } + + PUGI__FN void node_copy_tree(xml_node_struct* dn, xml_node_struct* sn) + { + xml_allocator& alloc = get_allocator(dn); + xml_allocator* shared_alloc = (&alloc == &get_allocator(sn)) ? &alloc : 0; + + node_copy_contents(dn, sn, shared_alloc); + + xml_node_struct* dit = dn; + xml_node_struct* sit = sn->first_child; + + while (sit && sit != sn) + { + if (sit != dn) + { + xml_node_struct* copy = append_new_node(dit, alloc, PUGI__NODETYPE(sit)); + + if (copy) + { + node_copy_contents(copy, sit, shared_alloc); + + if (sit->first_child) + { + dit = copy; + sit = sit->first_child; + continue; + } + } + } + + // continue to the next node + do + { + if (sit->next_sibling) + { + sit = sit->next_sibling; + break; + } + + sit = sit->parent; + dit = dit->parent; + } + while (sit != sn); + } + } + + inline bool is_text_node(xml_node_struct* node) + { + xml_node_type type = PUGI__NODETYPE(node); + + return type == node_pcdata || type == node_cdata; + } + + // get value with conversion functions + PUGI__FN int get_integer_base(const char_t* value) + { + const char_t* s = value; + + while (PUGI__IS_CHARTYPE(*s, ct_space)) + s++; + + if (*s == '-') + s++; + + return (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) ? 16 : 10; + } + + PUGI__FN int get_value_int(const char_t* value, int def) + { + if (!value) return def; + + int base = get_integer_base(value); + + #ifdef PUGIXML_WCHAR_MODE + return static_cast(wcstol(value, 0, base)); + #else + return static_cast(strtol(value, 0, base)); + #endif + } + + PUGI__FN unsigned int get_value_uint(const char_t* value, unsigned int def) + { + if (!value) return def; + + int base = get_integer_base(value); + + #ifdef PUGIXML_WCHAR_MODE + return static_cast(wcstoul(value, 0, base)); + #else + return static_cast(strtoul(value, 0, base)); + #endif + } + + PUGI__FN double get_value_double(const char_t* value, double def) + { + if (!value) return def; + + #ifdef PUGIXML_WCHAR_MODE + return wcstod(value, 0); + #else + return strtod(value, 0); + #endif + } + + PUGI__FN float get_value_float(const char_t* value, float def) + { + if (!value) return def; + + #ifdef PUGIXML_WCHAR_MODE + return static_cast(wcstod(value, 0)); + #else + return static_cast(strtod(value, 0)); + #endif + } + + PUGI__FN bool get_value_bool(const char_t* value, bool def) + { + if (!value) return def; + + // only look at first char + char_t first = *value; + + // 1*, t* (true), T* (True), y* (yes), Y* (YES) + return (first == '1' || first == 't' || first == 'T' || first == 'y' || first == 'Y'); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN long long get_value_llong(const char_t* value, long long def) + { + if (!value) return def; + + int base = get_integer_base(value); + + #ifdef PUGIXML_WCHAR_MODE + #ifdef PUGI__MSVC_CRT_VERSION + return _wcstoi64(value, 0, base); + #else + return wcstoll(value, 0, base); + #endif + #else + #ifdef PUGI__MSVC_CRT_VERSION + return _strtoi64(value, 0, base); + #else + return strtoll(value, 0, base); + #endif + #endif + } + + PUGI__FN unsigned long long get_value_ullong(const char_t* value, unsigned long long def) + { + if (!value) return def; + + int base = get_integer_base(value); + + #ifdef PUGIXML_WCHAR_MODE + #ifdef PUGI__MSVC_CRT_VERSION + return _wcstoui64(value, 0, base); + #else + return wcstoull(value, 0, base); + #endif + #else + #ifdef PUGI__MSVC_CRT_VERSION + return _strtoui64(value, 0, base); + #else + return strtoull(value, 0, base); + #endif + #endif + } +#endif + + // set value with conversion functions + PUGI__FN bool set_value_buffer(char_t*& dest, uintptr_t& header, uintptr_t header_mask, char (&buf)[128]) + { + #ifdef PUGIXML_WCHAR_MODE + char_t wbuf[128]; + impl::widen_ascii(wbuf, buf); + + return strcpy_insitu(dest, header, header_mask, wbuf); + #else + return strcpy_insitu(dest, header, header_mask, buf); + #endif + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, int value) + { + char buf[128]; + sprintf(buf, "%d", value); + + return set_value_buffer(dest, header, header_mask, buf); + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, unsigned int value) + { + char buf[128]; + sprintf(buf, "%u", value); + + return set_value_buffer(dest, header, header_mask, buf); + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, float value) + { + char buf[128]; + sprintf(buf, "%.9g", value); + + return set_value_buffer(dest, header, header_mask, buf); + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, double value) + { + char buf[128]; + sprintf(buf, "%.17g", value); + + return set_value_buffer(dest, header, header_mask, buf); + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, bool value) + { + return strcpy_insitu(dest, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, long long value) + { + char buf[128]; + sprintf(buf, "%lld", value); + + return set_value_buffer(dest, header, header_mask, buf); + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, unsigned long long value) + { + char buf[128]; + sprintf(buf, "%llu", value); + + return set_value_buffer(dest, header, header_mask, buf); + } +#endif + + // we need to get length of entire file to load it in memory; the only (relatively) sane way to do it is via seek/tell trick + PUGI__FN xml_parse_status get_file_size(FILE* file, size_t& out_result) + { + #if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE) + // there are 64-bit versions of fseek/ftell, let's use them + typedef __int64 length_type; + + _fseeki64(file, 0, SEEK_END); + length_type length = _ftelli64(file); + _fseeki64(file, 0, SEEK_SET); + #elif defined(__MINGW32__) && !defined(__NO_MINGW_LFS) && (!defined(__STRICT_ANSI__) || defined(__MINGW64_VERSION_MAJOR)) + // there are 64-bit versions of fseek/ftell, let's use them + typedef off64_t length_type; + + fseeko64(file, 0, SEEK_END); + length_type length = ftello64(file); + fseeko64(file, 0, SEEK_SET); + #else + // if this is a 32-bit OS, long is enough; if this is a unix system, long is 64-bit, which is enough; otherwise we can't do anything anyway. + typedef long length_type; + + fseek(file, 0, SEEK_END); + length_type length = ftell(file); + fseek(file, 0, SEEK_SET); + #endif + + // check for I/O errors + if (length < 0) return status_io_error; + + // check for overflow + size_t result = static_cast(length); + + if (static_cast(result) != length) return status_out_of_memory; + + // finalize + out_result = result; + + return status_ok; + } + + PUGI__FN size_t zero_terminate_buffer(void* buffer, size_t size, xml_encoding encoding) + { + // We only need to zero-terminate if encoding conversion does not do it for us + #ifdef PUGIXML_WCHAR_MODE + xml_encoding wchar_encoding = get_wchar_encoding(); + + if (encoding == wchar_encoding || need_endian_swap_utf(encoding, wchar_encoding)) + { + size_t length = size / sizeof(char_t); + + static_cast(buffer)[length] = 0; + return (length + 1) * sizeof(char_t); + } + #else + if (encoding == encoding_utf8) + { + static_cast(buffer)[size] = 0; + return size + 1; + } + #endif + + return size; + } + + PUGI__FN xml_parse_result load_file_impl(xml_document& doc, FILE* file, unsigned int options, xml_encoding encoding) + { + if (!file) return make_parse_result(status_file_not_found); + + // get file size (can result in I/O errors) + size_t size = 0; + xml_parse_status size_status = get_file_size(file, size); + + if (size_status != status_ok) + { + fclose(file); + return make_parse_result(size_status); + } + + size_t max_suffix_size = sizeof(char_t); + + // allocate buffer for the whole file + char* contents = static_cast(xml_memory::allocate(size + max_suffix_size)); + + if (!contents) + { + fclose(file); + return make_parse_result(status_out_of_memory); + } + + // read file in memory + size_t read_size = fread(contents, 1, size, file); + fclose(file); + + if (read_size != size) + { + xml_memory::deallocate(contents); + return make_parse_result(status_io_error); + } + + xml_encoding real_encoding = get_buffer_encoding(encoding, contents, size); + + return doc.load_buffer_inplace_own(contents, zero_terminate_buffer(contents, size, real_encoding), options, real_encoding); + } + +#ifndef PUGIXML_NO_STL + template struct xml_stream_chunk + { + static xml_stream_chunk* create() + { + void* memory = xml_memory::allocate(sizeof(xml_stream_chunk)); + + return new (memory) xml_stream_chunk(); + } + + static void destroy(void* ptr) + { + xml_stream_chunk* chunk = static_cast(ptr); + + // free chunk chain + while (chunk) + { + xml_stream_chunk* next_ = chunk->next; + + xml_memory::deallocate(chunk); + + chunk = next_; + } + } + + xml_stream_chunk(): next(0), size(0) + { + } + + xml_stream_chunk* next; + size_t size; + + T data[xml_memory_page_size / sizeof(T)]; + }; + + template PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream& stream, void** out_buffer, size_t* out_size) + { + buffer_holder chunks(0, xml_stream_chunk::destroy); + + // read file to a chunk list + size_t total = 0; + xml_stream_chunk* last = 0; + + while (!stream.eof()) + { + // allocate new chunk + xml_stream_chunk* chunk = xml_stream_chunk::create(); + if (!chunk) return status_out_of_memory; + + // append chunk to list + if (last) last = last->next = chunk; + else chunks.data = last = chunk; + + // read data to chunk + stream.read(chunk->data, static_cast(sizeof(chunk->data) / sizeof(T))); + chunk->size = static_cast(stream.gcount()) * sizeof(T); + + // read may set failbit | eofbit in case gcount() is less than read length, so check for other I/O errors + if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; + + // guard against huge files (chunk size is small enough to make this overflow check work) + if (total + chunk->size < total) return status_out_of_memory; + total += chunk->size; + } + + size_t max_suffix_size = sizeof(char_t); + + // copy chunk list to a contiguous buffer + char* buffer = static_cast(xml_memory::allocate(total + max_suffix_size)); + if (!buffer) return status_out_of_memory; + + char* write = buffer; + + for (xml_stream_chunk* chunk = static_cast*>(chunks.data); chunk; chunk = chunk->next) + { + assert(write + chunk->size <= buffer + total); + memcpy(write, chunk->data, chunk->size); + write += chunk->size; + } + + assert(write == buffer + total); + + // return buffer + *out_buffer = buffer; + *out_size = total; + + return status_ok; + } + + template PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream& stream, void** out_buffer, size_t* out_size) + { + // get length of remaining data in stream + typename std::basic_istream::pos_type pos = stream.tellg(); + stream.seekg(0, std::ios::end); + std::streamoff length = stream.tellg() - pos; + stream.seekg(pos); + + if (stream.fail() || pos < 0) return status_io_error; + + // guard against huge files + size_t read_length = static_cast(length); + + if (static_cast(read_length) != length || length < 0) return status_out_of_memory; + + size_t max_suffix_size = sizeof(char_t); + + // read stream data into memory (guard against stream exceptions with buffer holder) + buffer_holder buffer(xml_memory::allocate(read_length * sizeof(T) + max_suffix_size), xml_memory::deallocate); + if (!buffer.data) return status_out_of_memory; + + stream.read(static_cast(buffer.data), static_cast(read_length)); + + // read may set failbit | eofbit in case gcount() is less than read_length (i.e. line ending conversion), so check for other I/O errors + if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; + + // return buffer + size_t actual_length = static_cast(stream.gcount()); + assert(actual_length <= read_length); + + *out_buffer = buffer.release(); + *out_size = actual_length * sizeof(T); + + return status_ok; + } + + template PUGI__FN xml_parse_result load_stream_impl(xml_document& doc, std::basic_istream& stream, unsigned int options, xml_encoding encoding) + { + void* buffer = 0; + size_t size = 0; + xml_parse_status status = status_ok; + + // if stream has an error bit set, bail out (otherwise tellg() can fail and we'll clear error bits) + if (stream.fail()) return make_parse_result(status_io_error); + + // load stream to memory (using seek-based implementation if possible, since it's faster and takes less memory) + if (stream.tellg() < 0) + { + stream.clear(); // clear error flags that could be set by a failing tellg + status = load_stream_data_noseek(stream, &buffer, &size); + } + else + status = load_stream_data_seek(stream, &buffer, &size); + + if (status != status_ok) return make_parse_result(status); + + xml_encoding real_encoding = get_buffer_encoding(encoding, buffer, size); + + return doc.load_buffer_inplace_own(buffer, zero_terminate_buffer(buffer, size, real_encoding), options, real_encoding); + } +#endif + +#if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) || (defined(__MINGW32__) && (!defined(__STRICT_ANSI__) || defined(__MINGW64_VERSION_MAJOR))) + PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) + { + return _wfopen(path, mode); + } +#else + PUGI__FN char* convert_path_heap(const wchar_t* str) + { + assert(str); + + // first pass: get length in utf8 characters + size_t length = strlength_wide(str); + size_t size = as_utf8_begin(str, length); + + // allocate resulting string + char* result = static_cast(xml_memory::allocate(size + 1)); + if (!result) return 0; + + // second pass: convert to utf8 + as_utf8_end(result, size, str, length); + + return result; + } + + PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) + { + // there is no standard function to open wide paths, so our best bet is to try utf8 path + char* path_utf8 = convert_path_heap(path); + if (!path_utf8) return 0; + + // convert mode to ASCII (we mirror _wfopen interface) + char mode_ascii[4] = {0}; + for (size_t i = 0; mode[i]; ++i) mode_ascii[i] = static_cast(mode[i]); + + // try to open the utf8 path + FILE* result = fopen(path_utf8, mode_ascii); + + // free dummy buffer + xml_memory::deallocate(path_utf8); + + return result; + } +#endif + + PUGI__FN bool save_file_impl(const xml_document& doc, FILE* file, const char_t* indent, unsigned int flags, xml_encoding encoding) + { + if (!file) return false; + + xml_writer_file writer(file); + doc.save(writer, indent, flags, encoding); + + int result = ferror(file); + + fclose(file); + + return result == 0; + } + + PUGI__FN xml_parse_result load_buffer_impl(xml_document_struct* doc, xml_node_struct* root, void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own, char_t** out_buffer) + { + // check input buffer + if (!contents && size) return make_parse_result(status_io_error); + + // get actual encoding + xml_encoding buffer_encoding = impl::get_buffer_encoding(encoding, contents, size); + + // get private buffer + char_t* buffer = 0; + size_t length = 0; + + if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable)) return impl::make_parse_result(status_out_of_memory); + + // delete original buffer if we performed a conversion + if (own && buffer != contents && contents) impl::xml_memory::deallocate(contents); + + // store buffer for offset_debug + doc->buffer = buffer; + + // parse + xml_parse_result res = impl::xml_parser::parse(buffer, length, doc, root, options); + + // remember encoding + res.encoding = buffer_encoding; + + // grab onto buffer if it's our buffer, user is responsible for deallocating contents himself + if (own || buffer != contents) *out_buffer = buffer; + + return res; + } +PUGI__NS_END + +namespace pugi +{ + PUGI__FN xml_writer_file::xml_writer_file(void* file_): file(file_) + { + } + + PUGI__FN void xml_writer_file::write(const void* data, size_t size) + { + size_t result = fwrite(data, 1, size, static_cast(file)); + (void)!result; // unfortunately we can't do proper error handling here + } + +#ifndef PUGIXML_NO_STL + PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(&stream), wide_stream(0) + { + } + + PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(0), wide_stream(&stream) + { + } + + PUGI__FN void xml_writer_stream::write(const void* data, size_t size) + { + if (narrow_stream) + { + assert(!wide_stream); + narrow_stream->write(reinterpret_cast(data), static_cast(size)); + } + else + { + assert(wide_stream); + assert(size % sizeof(wchar_t) == 0); + + wide_stream->write(reinterpret_cast(data), static_cast(size / sizeof(wchar_t))); + } + } +#endif + + PUGI__FN xml_tree_walker::xml_tree_walker(): _depth(0) + { + } + + PUGI__FN xml_tree_walker::~xml_tree_walker() + { + } + + PUGI__FN int xml_tree_walker::depth() const + { + return _depth; + } + + PUGI__FN bool xml_tree_walker::begin(xml_node&) + { + return true; + } + + PUGI__FN bool xml_tree_walker::end(xml_node&) + { + return true; + } + + PUGI__FN xml_attribute::xml_attribute(): _attr(0) + { + } + + PUGI__FN xml_attribute::xml_attribute(xml_attribute_struct* attr): _attr(attr) + { + } + + PUGI__FN static void unspecified_bool_xml_attribute(xml_attribute***) + { + } + + PUGI__FN xml_attribute::operator xml_attribute::unspecified_bool_type() const + { + return _attr ? unspecified_bool_xml_attribute : 0; + } + + PUGI__FN bool xml_attribute::operator!() const + { + return !_attr; + } + + PUGI__FN bool xml_attribute::operator==(const xml_attribute& r) const + { + return (_attr == r._attr); + } + + PUGI__FN bool xml_attribute::operator!=(const xml_attribute& r) const + { + return (_attr != r._attr); + } + + PUGI__FN bool xml_attribute::operator<(const xml_attribute& r) const + { + return (_attr < r._attr); + } + + PUGI__FN bool xml_attribute::operator>(const xml_attribute& r) const + { + return (_attr > r._attr); + } + + PUGI__FN bool xml_attribute::operator<=(const xml_attribute& r) const + { + return (_attr <= r._attr); + } + + PUGI__FN bool xml_attribute::operator>=(const xml_attribute& r) const + { + return (_attr >= r._attr); + } + + PUGI__FN xml_attribute xml_attribute::next_attribute() const + { + return _attr ? xml_attribute(_attr->next_attribute) : xml_attribute(); + } + + PUGI__FN xml_attribute xml_attribute::previous_attribute() const + { + return _attr && _attr->prev_attribute_c->next_attribute ? xml_attribute(_attr->prev_attribute_c) : xml_attribute(); + } + + PUGI__FN const char_t* xml_attribute::as_string(const char_t* def) const + { + return (_attr && _attr->value) ? _attr->value : def; + } + + PUGI__FN int xml_attribute::as_int(int def) const + { + return impl::get_value_int(_attr ? _attr->value : 0, def); + } + + PUGI__FN unsigned int xml_attribute::as_uint(unsigned int def) const + { + return impl::get_value_uint(_attr ? _attr->value : 0, def); + } + + PUGI__FN double xml_attribute::as_double(double def) const + { + return impl::get_value_double(_attr ? _attr->value : 0, def); + } + + PUGI__FN float xml_attribute::as_float(float def) const + { + return impl::get_value_float(_attr ? _attr->value : 0, def); + } + + PUGI__FN bool xml_attribute::as_bool(bool def) const + { + return impl::get_value_bool(_attr ? _attr->value : 0, def); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN long long xml_attribute::as_llong(long long def) const + { + return impl::get_value_llong(_attr ? _attr->value : 0, def); + } + + PUGI__FN unsigned long long xml_attribute::as_ullong(unsigned long long def) const + { + return impl::get_value_ullong(_attr ? _attr->value : 0, def); + } +#endif + + PUGI__FN bool xml_attribute::empty() const + { + return !_attr; + } + + PUGI__FN const char_t* xml_attribute::name() const + { + return (_attr && _attr->name) ? _attr->name : PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* xml_attribute::value() const + { + return (_attr && _attr->value) ? _attr->value : PUGIXML_TEXT(""); + } + + PUGI__FN size_t xml_attribute::hash_value() const + { + return static_cast(reinterpret_cast(_attr) / sizeof(xml_attribute_struct)); + } + + PUGI__FN xml_attribute_struct* xml_attribute::internal_object() const + { + return _attr; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(const char_t* rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(int rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(unsigned int rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(double rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(float rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(bool rhs) + { + set_value(rhs); + return *this; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN xml_attribute& xml_attribute::operator=(long long rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(unsigned long long rhs) + { + set_value(rhs); + return *this; + } +#endif + + PUGI__FN bool xml_attribute::set_name(const char_t* rhs) + { + if (!_attr) return false; + + return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(const char_t* rhs) + { + if (!_attr) return false; + + return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(int rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(unsigned int rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(double rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(float rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(bool rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN bool xml_attribute::set_value(long long rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(unsigned long long rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } +#endif + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_attribute& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xml_attribute& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN xml_node::xml_node(): _root(0) + { + } + + PUGI__FN xml_node::xml_node(xml_node_struct* p): _root(p) + { + } + + PUGI__FN static void unspecified_bool_xml_node(xml_node***) + { + } + + PUGI__FN xml_node::operator xml_node::unspecified_bool_type() const + { + return _root ? unspecified_bool_xml_node : 0; + } + + PUGI__FN bool xml_node::operator!() const + { + return !_root; + } + + PUGI__FN xml_node::iterator xml_node::begin() const + { + return iterator(_root ? _root->first_child : 0, _root); + } + + PUGI__FN xml_node::iterator xml_node::end() const + { + return iterator(0, _root); + } + + PUGI__FN xml_node::attribute_iterator xml_node::attributes_begin() const + { + return attribute_iterator(_root ? _root->first_attribute : 0, _root); + } + + PUGI__FN xml_node::attribute_iterator xml_node::attributes_end() const + { + return attribute_iterator(0, _root); + } + + PUGI__FN xml_object_range xml_node::children() const + { + return xml_object_range(begin(), end()); + } + + PUGI__FN xml_object_range xml_node::children(const char_t* name_) const + { + return xml_object_range(xml_named_node_iterator(child(name_)._root, _root, name_), xml_named_node_iterator(0, _root, name_)); + } + + PUGI__FN xml_object_range xml_node::attributes() const + { + return xml_object_range(attributes_begin(), attributes_end()); + } + + PUGI__FN bool xml_node::operator==(const xml_node& r) const + { + return (_root == r._root); + } + + PUGI__FN bool xml_node::operator!=(const xml_node& r) const + { + return (_root != r._root); + } + + PUGI__FN bool xml_node::operator<(const xml_node& r) const + { + return (_root < r._root); + } + + PUGI__FN bool xml_node::operator>(const xml_node& r) const + { + return (_root > r._root); + } + + PUGI__FN bool xml_node::operator<=(const xml_node& r) const + { + return (_root <= r._root); + } + + PUGI__FN bool xml_node::operator>=(const xml_node& r) const + { + return (_root >= r._root); + } + + PUGI__FN bool xml_node::empty() const + { + return !_root; + } + + PUGI__FN const char_t* xml_node::name() const + { + return (_root && _root->name) ? _root->name : PUGIXML_TEXT(""); + } + + PUGI__FN xml_node_type xml_node::type() const + { + return _root ? PUGI__NODETYPE(_root) : node_null; + } + + PUGI__FN const char_t* xml_node::value() const + { + return (_root && _root->value) ? _root->value : PUGIXML_TEXT(""); + } + + PUGI__FN xml_node xml_node::child(const char_t* name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + + return xml_node(); + } + + PUGI__FN xml_attribute xml_node::attribute(const char_t* name_) const + { + if (!_root) return xml_attribute(); + + for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) + if (i->name && impl::strequal(name_, i->name)) + return xml_attribute(i); + + return xml_attribute(); + } + + PUGI__FN xml_node xml_node::next_sibling(const char_t* name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling) + if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + + return xml_node(); + } + + PUGI__FN xml_node xml_node::next_sibling() const + { + return _root ? xml_node(_root->next_sibling) : xml_node(); + } + + PUGI__FN xml_node xml_node::previous_sibling(const char_t* name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c) + if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + + return xml_node(); + } + + PUGI__FN xml_node xml_node::previous_sibling() const + { + if (!_root) return xml_node(); + + if (_root->prev_sibling_c->next_sibling) return xml_node(_root->prev_sibling_c); + else return xml_node(); + } + + PUGI__FN xml_node xml_node::parent() const + { + return _root ? xml_node(_root->parent) : xml_node(); + } + + PUGI__FN xml_node xml_node::root() const + { + return _root ? xml_node(&impl::get_document(_root)) : xml_node(); + } + + PUGI__FN xml_text xml_node::text() const + { + return xml_text(_root); + } + + PUGI__FN const char_t* xml_node::child_value() const + { + if (!_root) return PUGIXML_TEXT(""); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->value && impl::is_text_node(i)) + return i->value; + + return PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* xml_node::child_value(const char_t* name_) const + { + return child(name_).child_value(); + } + + PUGI__FN xml_attribute xml_node::first_attribute() const + { + return _root ? xml_attribute(_root->first_attribute) : xml_attribute(); + } + + PUGI__FN xml_attribute xml_node::last_attribute() const + { + return _root && _root->first_attribute ? xml_attribute(_root->first_attribute->prev_attribute_c) : xml_attribute(); + } + + PUGI__FN xml_node xml_node::first_child() const + { + return _root ? xml_node(_root->first_child) : xml_node(); + } + + PUGI__FN xml_node xml_node::last_child() const + { + return _root && _root->first_child ? xml_node(_root->first_child->prev_sibling_c) : xml_node(); + } + + PUGI__FN bool xml_node::set_name(const char_t* rhs) + { + switch (type()) + { + case node_pi: + case node_declaration: + case node_element: + return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs); + + default: + return false; + } + } + + PUGI__FN bool xml_node::set_value(const char_t* rhs) + { + switch (type()) + { + case node_pi: + case node_cdata: + case node_pcdata: + case node_comment: + case node_doctype: + return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs); + + default: + return false; + } + } + + PUGI__FN xml_attribute xml_node::append_attribute(const char_t* name_) + { + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); + if (!a) return xml_attribute(); + + impl::append_attribute(a._attr, _root); + + a.set_name(name_); + + return a; + } + + PUGI__FN xml_attribute xml_node::prepend_attribute(const char_t* name_) + { + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); + if (!a) return xml_attribute(); + + impl::prepend_attribute(a._attr, _root); + + a.set_name(name_); + + return a; + } + + PUGI__FN xml_attribute xml_node::insert_attribute_after(const char_t* name_, const xml_attribute& attr) + { + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); + if (!a) return xml_attribute(); + + impl::insert_attribute_after(a._attr, attr._attr, _root); + + a.set_name(name_); + + return a; + } + + PUGI__FN xml_attribute xml_node::insert_attribute_before(const char_t* name_, const xml_attribute& attr) + { + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); + if (!a) return xml_attribute(); + + impl::insert_attribute_before(a._attr, attr._attr, _root); + + a.set_name(name_); + + return a; + } + + PUGI__FN xml_attribute xml_node::append_copy(const xml_attribute& proto) + { + if (!proto) return xml_attribute(); + + xml_attribute result = append_attribute(proto.name()); + result.set_value(proto.value()); + + return result; + } + + PUGI__FN xml_attribute xml_node::prepend_copy(const xml_attribute& proto) + { + if (!proto) return xml_attribute(); + + xml_attribute result = prepend_attribute(proto.name()); + result.set_value(proto.value()); + + return result; + } + + PUGI__FN xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr) + { + if (!proto) return xml_attribute(); + + xml_attribute result = insert_attribute_after(proto.name(), attr); + result.set_value(proto.value()); + + return result; + } + + PUGI__FN xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr) + { + if (!proto) return xml_attribute(); + + xml_attribute result = insert_attribute_before(proto.name(), attr); + result.set_value(proto.value()); + + return result; + } + + PUGI__FN xml_node xml_node::append_child(xml_node_type type_) + { + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + impl::append_node(n._root, _root); + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::prepend_child(xml_node_type type_) + { + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + impl::prepend_node(n._root, _root); + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::insert_child_before(xml_node_type type_, const xml_node& node) + { + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + impl::insert_node_before(n._root, node._root); + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::insert_child_after(xml_node_type type_, const xml_node& node) + { + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + impl::insert_node_after(n._root, node._root); + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::append_child(const char_t* name_) + { + xml_node result = append_child(node_element); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::prepend_child(const char_t* name_) + { + xml_node result = prepend_child(node_element); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::insert_child_after(const char_t* name_, const xml_node& node) + { + xml_node result = insert_child_after(node_element, node); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::insert_child_before(const char_t* name_, const xml_node& node) + { + xml_node result = insert_child_before(node_element, node); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::append_copy(const xml_node& proto) + { + xml_node_type type_ = proto.type(); + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + impl::append_node(n._root, _root); + impl::node_copy_tree(n._root, proto._root); + + return n; + } + + PUGI__FN xml_node xml_node::prepend_copy(const xml_node& proto) + { + xml_node_type type_ = proto.type(); + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + impl::prepend_node(n._root, _root); + impl::node_copy_tree(n._root, proto._root); + + return n; + } + + PUGI__FN xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node) + { + xml_node_type type_ = proto.type(); + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + impl::insert_node_after(n._root, node._root); + impl::node_copy_tree(n._root, proto._root); + + return n; + } + + PUGI__FN xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node) + { + xml_node_type type_ = proto.type(); + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + impl::insert_node_before(n._root, node._root); + impl::node_copy_tree(n._root, proto._root); + + return n; + } + + PUGI__FN xml_node xml_node::append_move(const xml_node& moved) + { + if (!impl::allow_move(*this, moved)) return xml_node(); + + // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers + impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; + + impl::remove_node(moved._root); + impl::append_node(moved._root, _root); + + return moved; + } + + PUGI__FN xml_node xml_node::prepend_move(const xml_node& moved) + { + if (!impl::allow_move(*this, moved)) return xml_node(); + + // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers + impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; + + impl::remove_node(moved._root); + impl::prepend_node(moved._root, _root); + + return moved; + } + + PUGI__FN xml_node xml_node::insert_move_after(const xml_node& moved, const xml_node& node) + { + if (!impl::allow_move(*this, moved)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + if (moved._root == node._root) return xml_node(); + + // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers + impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; + + impl::remove_node(moved._root); + impl::insert_node_after(moved._root, node._root); + + return moved; + } + + PUGI__FN xml_node xml_node::insert_move_before(const xml_node& moved, const xml_node& node) + { + if (!impl::allow_move(*this, moved)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + if (moved._root == node._root) return xml_node(); + + // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers + impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; + + impl::remove_node(moved._root); + impl::insert_node_before(moved._root, node._root); + + return moved; + } + + PUGI__FN bool xml_node::remove_attribute(const char_t* name_) + { + return remove_attribute(attribute(name_)); + } + + PUGI__FN bool xml_node::remove_attribute(const xml_attribute& a) + { + if (!_root || !a._attr) return false; + if (!impl::is_attribute_of(a._attr, _root)) return false; + + impl::remove_attribute(a._attr, _root); + impl::destroy_attribute(a._attr, impl::get_allocator(_root)); + + return true; + } + + PUGI__FN bool xml_node::remove_child(const char_t* name_) + { + return remove_child(child(name_)); + } + + PUGI__FN bool xml_node::remove_child(const xml_node& n) + { + if (!_root || !n._root || n._root->parent != _root) return false; + + impl::remove_node(n._root); + impl::destroy_node(n._root, impl::get_allocator(_root)); + + return true; + } + + PUGI__FN xml_parse_result xml_node::append_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + // append_buffer is only valid for elements/documents + if (!impl::allow_insert_child(type(), node_element)) return impl::make_parse_result(status_append_invalid_root); + + // get document node + impl::xml_document_struct* doc = &impl::get_document(_root); + + // disable document_buffer_order optimization since in a document with multiple buffers comparing buffer pointers does not make sense + doc->header |= impl::xml_memory_page_contents_shared_mask; + + // get extra buffer element (we'll store the document fragment buffer there so that we can deallocate it later) + impl::xml_memory_page* page = 0; + impl::xml_extra_buffer* extra = static_cast(doc->allocate_memory(sizeof(impl::xml_extra_buffer), page)); + (void)page; + + if (!extra) return impl::make_parse_result(status_out_of_memory); + + // save name; name of the root has to be NULL before parsing - otherwise closing node mismatches will not be detected at the top level + char_t* rootname = _root->name; + _root->name = 0; + + // parse + char_t* buffer = 0; + xml_parse_result res = impl::load_buffer_impl(doc, _root, const_cast(contents), size, options, encoding, false, false, &buffer); + + // restore name + _root->name = rootname; + + // add extra buffer to the list + extra->buffer = buffer; + extra->next = doc->extra_buffers; + doc->extra_buffers = extra; + + return res; + } + + PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->name && impl::strequal(name_, i->name)) + { + for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) + if (a->name && impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value ? a->value : PUGIXML_TEXT(""))) + return xml_node(i); + } + + return xml_node(); + } + + PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) + if (a->name && impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value ? a->value : PUGIXML_TEXT(""))) + return xml_node(i); + + return xml_node(); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN string_t xml_node::path(char_t delimiter) const + { + xml_node cursor = *this; // Make a copy. + + string_t result = cursor.name(); + + while (cursor.parent()) + { + cursor = cursor.parent(); + + string_t temp = cursor.name(); + temp += delimiter; + temp += result; + result.swap(temp); + } + + return result; + } +#endif + + PUGI__FN xml_node xml_node::first_element_by_path(const char_t* path_, char_t delimiter) const + { + xml_node found = *this; // Current search context. + + if (!_root || !path_ || !path_[0]) return found; + + if (path_[0] == delimiter) + { + // Absolute path; e.g. '/foo/bar' + found = found.root(); + ++path_; + } + + const char_t* path_segment = path_; + + while (*path_segment == delimiter) ++path_segment; + + const char_t* path_segment_end = path_segment; + + while (*path_segment_end && *path_segment_end != delimiter) ++path_segment_end; + + if (path_segment == path_segment_end) return found; + + const char_t* next_segment = path_segment_end; + + while (*next_segment == delimiter) ++next_segment; + + if (*path_segment == '.' && path_segment + 1 == path_segment_end) + return found.first_element_by_path(next_segment, delimiter); + else if (*path_segment == '.' && *(path_segment+1) == '.' && path_segment + 2 == path_segment_end) + return found.parent().first_element_by_path(next_segment, delimiter); + else + { + for (xml_node_struct* j = found._root->first_child; j; j = j->next_sibling) + { + if (j->name && impl::strequalrange(j->name, path_segment, static_cast(path_segment_end - path_segment))) + { + xml_node subsearch = xml_node(j).first_element_by_path(next_segment, delimiter); + + if (subsearch) return subsearch; + } + } + + return xml_node(); + } + } + + PUGI__FN bool xml_node::traverse(xml_tree_walker& walker) + { + walker._depth = -1; + + xml_node arg_begin = *this; + if (!walker.begin(arg_begin)) return false; + + xml_node cur = first_child(); + + if (cur) + { + ++walker._depth; + + do + { + xml_node arg_for_each = cur; + if (!walker.for_each(arg_for_each)) + return false; + + if (cur.first_child()) + { + ++walker._depth; + cur = cur.first_child(); + } + else if (cur.next_sibling()) + cur = cur.next_sibling(); + else + { + // Borland C++ workaround + while (!cur.next_sibling() && cur != *this && !cur.parent().empty()) + { + --walker._depth; + cur = cur.parent(); + } + + if (cur != *this) + cur = cur.next_sibling(); + } + } + while (cur && cur != *this); + } + + assert(walker._depth == -1); + + xml_node arg_end = *this; + return walker.end(arg_end); + } + + PUGI__FN size_t xml_node::hash_value() const + { + return static_cast(reinterpret_cast(_root) / sizeof(xml_node_struct)); + } + + PUGI__FN xml_node_struct* xml_node::internal_object() const + { + return _root; + } + + PUGI__FN void xml_node::print(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const + { + if (!_root) return; + + impl::xml_buffered_writer buffered_writer(writer, encoding); + + impl::node_output(buffered_writer, _root, indent, flags, depth); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const + { + xml_writer_stream writer(stream); + + print(writer, indent, flags, encoding, depth); + } + + PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, unsigned int depth) const + { + xml_writer_stream writer(stream); + + print(writer, indent, flags, encoding_wchar, depth); + } +#endif + + PUGI__FN ptrdiff_t xml_node::offset_debug() const + { + if (!_root) return -1; + + impl::xml_document_struct& doc = impl::get_document(_root); + + // we can determine the offset reliably only if there is exactly once parse buffer + if (!doc.buffer || doc.extra_buffers) return -1; + + switch (type()) + { + case node_document: + return 0; + + case node_element: + case node_declaration: + case node_pi: + return _root->name && (_root->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0 ? _root->name - doc.buffer : -1; + + case node_pcdata: + case node_cdata: + case node_comment: + case node_doctype: + return _root->value && (_root->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0 ? _root->value - doc.buffer : -1; + + default: + return -1; + } + } + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_node& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xml_node& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN xml_text::xml_text(xml_node_struct* root): _root(root) + { + } + + PUGI__FN xml_node_struct* xml_text::_data() const + { + if (!_root || impl::is_text_node(_root)) return _root; + + for (xml_node_struct* node = _root->first_child; node; node = node->next_sibling) + if (impl::is_text_node(node)) + return node; + + return 0; + } + + PUGI__FN xml_node_struct* xml_text::_data_new() + { + xml_node_struct* d = _data(); + if (d) return d; + + return xml_node(_root).append_child(node_pcdata).internal_object(); + } + + PUGI__FN xml_text::xml_text(): _root(0) + { + } + + PUGI__FN static void unspecified_bool_xml_text(xml_text***) + { + } + + PUGI__FN xml_text::operator xml_text::unspecified_bool_type() const + { + return _data() ? unspecified_bool_xml_text : 0; + } + + PUGI__FN bool xml_text::operator!() const + { + return !_data(); + } + + PUGI__FN bool xml_text::empty() const + { + return _data() == 0; + } + + PUGI__FN const char_t* xml_text::get() const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? d->value : PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* xml_text::as_string(const char_t* def) const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? d->value : def; + } + + PUGI__FN int xml_text::as_int(int def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_int(d ? d->value : 0, def); + } + + PUGI__FN unsigned int xml_text::as_uint(unsigned int def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_uint(d ? d->value : 0, def); + } + + PUGI__FN double xml_text::as_double(double def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_double(d ? d->value : 0, def); + } + + PUGI__FN float xml_text::as_float(float def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_float(d ? d->value : 0, def); + } + + PUGI__FN bool xml_text::as_bool(bool def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_bool(d ? d->value : 0, def); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN long long xml_text::as_llong(long long def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_llong(d ? d->value : 0, def); + } + + PUGI__FN unsigned long long xml_text::as_ullong(unsigned long long def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_ullong(d ? d->value : 0, def); + } +#endif + + PUGI__FN bool xml_text::set(const char_t* rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(int rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(unsigned int rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(float rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(double rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(bool rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN bool xml_text::set(long long rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(unsigned long long rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } +#endif + + PUGI__FN xml_text& xml_text::operator=(const char_t* rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(int rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(unsigned int rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(double rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(float rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(bool rhs) + { + set(rhs); + return *this; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN xml_text& xml_text::operator=(long long rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(unsigned long long rhs) + { + set(rhs); + return *this; + } +#endif + + PUGI__FN xml_node xml_text::data() const + { + return xml_node(_data()); + } + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_text& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xml_text& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN xml_node_iterator::xml_node_iterator() + { + } + + PUGI__FN xml_node_iterator::xml_node_iterator(const xml_node& node): _wrap(node), _parent(node.parent()) + { + } + + PUGI__FN xml_node_iterator::xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) + { + } + + PUGI__FN bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const + { + return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; + } + + PUGI__FN bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const + { + return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; + } + + PUGI__FN xml_node& xml_node_iterator::operator*() const + { + assert(_wrap._root); + return _wrap; + } + + PUGI__FN xml_node* xml_node_iterator::operator->() const + { + assert(_wrap._root); + return const_cast(&_wrap); // BCC32 workaround + } + + PUGI__FN const xml_node_iterator& xml_node_iterator::operator++() + { + assert(_wrap._root); + _wrap._root = _wrap._root->next_sibling; + return *this; + } + + PUGI__FN xml_node_iterator xml_node_iterator::operator++(int) + { + xml_node_iterator temp = *this; + ++*this; + return temp; + } + + PUGI__FN const xml_node_iterator& xml_node_iterator::operator--() + { + _wrap = _wrap._root ? _wrap.previous_sibling() : _parent.last_child(); + return *this; + } + + PUGI__FN xml_node_iterator xml_node_iterator::operator--(int) + { + xml_node_iterator temp = *this; + --*this; + return temp; + } + + PUGI__FN xml_attribute_iterator::xml_attribute_iterator() + { + } + + PUGI__FN xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent): _wrap(attr), _parent(parent) + { + } + + PUGI__FN xml_attribute_iterator::xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) + { + } + + PUGI__FN bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const + { + return _wrap._attr == rhs._wrap._attr && _parent._root == rhs._parent._root; + } + + PUGI__FN bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const + { + return _wrap._attr != rhs._wrap._attr || _parent._root != rhs._parent._root; + } + + PUGI__FN xml_attribute& xml_attribute_iterator::operator*() const + { + assert(_wrap._attr); + return _wrap; + } + + PUGI__FN xml_attribute* xml_attribute_iterator::operator->() const + { + assert(_wrap._attr); + return const_cast(&_wrap); // BCC32 workaround + } + + PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator++() + { + assert(_wrap._attr); + _wrap._attr = _wrap._attr->next_attribute; + return *this; + } + + PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator++(int) + { + xml_attribute_iterator temp = *this; + ++*this; + return temp; + } + + PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator--() + { + _wrap = _wrap._attr ? _wrap.previous_attribute() : _parent.last_attribute(); + return *this; + } + + PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator--(int) + { + xml_attribute_iterator temp = *this; + --*this; + return temp; + } + + PUGI__FN xml_named_node_iterator::xml_named_node_iterator(): _name(0) + { + } + + PUGI__FN xml_named_node_iterator::xml_named_node_iterator(const xml_node& node, const char_t* name): _wrap(node), _parent(node.parent()), _name(name) + { + } + + PUGI__FN xml_named_node_iterator::xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name): _wrap(ref), _parent(parent), _name(name) + { + } + + PUGI__FN bool xml_named_node_iterator::operator==(const xml_named_node_iterator& rhs) const + { + return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; + } + + PUGI__FN bool xml_named_node_iterator::operator!=(const xml_named_node_iterator& rhs) const + { + return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; + } + + PUGI__FN xml_node& xml_named_node_iterator::operator*() const + { + assert(_wrap._root); + return _wrap; + } + + PUGI__FN xml_node* xml_named_node_iterator::operator->() const + { + assert(_wrap._root); + return const_cast(&_wrap); // BCC32 workaround + } + + PUGI__FN const xml_named_node_iterator& xml_named_node_iterator::operator++() + { + assert(_wrap._root); + _wrap = _wrap.next_sibling(_name); + return *this; + } + + PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator++(int) + { + xml_named_node_iterator temp = *this; + ++*this; + return temp; + } + + PUGI__FN const xml_named_node_iterator& xml_named_node_iterator::operator--() + { + if (_wrap._root) + _wrap = _wrap.previous_sibling(_name); + else + { + _wrap = _parent.last_child(); + + if (!impl::strequal(_wrap.name(), _name)) + _wrap = _wrap.previous_sibling(_name); + } + + return *this; + } + + PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator--(int) + { + xml_named_node_iterator temp = *this; + --*this; + return temp; + } + + PUGI__FN xml_parse_result::xml_parse_result(): status(status_internal_error), offset(0), encoding(encoding_auto) + { + } + + PUGI__FN xml_parse_result::operator bool() const + { + return status == status_ok; + } + + PUGI__FN const char* xml_parse_result::description() const + { + switch (status) + { + case status_ok: return "No error"; + + case status_file_not_found: return "File was not found"; + case status_io_error: return "Error reading from file/stream"; + case status_out_of_memory: return "Could not allocate memory"; + case status_internal_error: return "Internal error occurred"; + + case status_unrecognized_tag: return "Could not determine tag type"; + + case status_bad_pi: return "Error parsing document declaration/processing instruction"; + case status_bad_comment: return "Error parsing comment"; + case status_bad_cdata: return "Error parsing CDATA section"; + case status_bad_doctype: return "Error parsing document type declaration"; + case status_bad_pcdata: return "Error parsing PCDATA section"; + case status_bad_start_element: return "Error parsing start element tag"; + case status_bad_attribute: return "Error parsing element attribute"; + case status_bad_end_element: return "Error parsing end element tag"; + case status_end_element_mismatch: return "Start-end tags mismatch"; + + case status_append_invalid_root: return "Unable to append nodes: root is not an element or document"; + + case status_no_document_element: return "No document element found"; + + default: return "Unknown error"; + } + } + + PUGI__FN xml_document::xml_document(): _buffer(0) + { + create(); + } + + PUGI__FN xml_document::~xml_document() + { + destroy(); + } + + PUGI__FN void xml_document::reset() + { + destroy(); + create(); + } + + PUGI__FN void xml_document::reset(const xml_document& proto) + { + reset(); + + for (xml_node cur = proto.first_child(); cur; cur = cur.next_sibling()) + append_copy(cur); + } + + PUGI__FN void xml_document::create() + { + assert(!_root); + + // initialize sentinel page + PUGI__STATIC_ASSERT(sizeof(impl::xml_memory_page) + sizeof(impl::xml_document_struct) + impl::xml_memory_page_alignment - sizeof(void*) <= sizeof(_memory)); + + // align upwards to page boundary + void* page_memory = reinterpret_cast((reinterpret_cast(_memory) + (impl::xml_memory_page_alignment - 1)) & ~(impl::xml_memory_page_alignment - 1)); + + // prepare page structure + impl::xml_memory_page* page = impl::xml_memory_page::construct(page_memory); + assert(page); + + page->busy_size = impl::xml_memory_page_size; + + // allocate new root + _root = new (reinterpret_cast(page) + sizeof(impl::xml_memory_page)) impl::xml_document_struct(page); + _root->prev_sibling_c = _root; + + // setup sentinel page + page->allocator = static_cast(_root); + + // verify the document allocation + assert(reinterpret_cast(_root) + sizeof(impl::xml_document_struct) <= _memory + sizeof(_memory)); + } + + PUGI__FN void xml_document::destroy() + { + assert(_root); + + // destroy static storage + if (_buffer) + { + impl::xml_memory::deallocate(_buffer); + _buffer = 0; + } + + // destroy extra buffers (note: no need to destroy linked list nodes, they're allocated using document allocator) + for (impl::xml_extra_buffer* extra = static_cast(_root)->extra_buffers; extra; extra = extra->next) + { + if (extra->buffer) impl::xml_memory::deallocate(extra->buffer); + } + + // destroy dynamic storage, leave sentinel page (it's in static memory) + impl::xml_memory_page* root_page = reinterpret_cast(_root->header & impl::xml_memory_page_pointer_mask); + assert(root_page && !root_page->prev); + assert(reinterpret_cast(root_page) >= _memory && reinterpret_cast(root_page) < _memory + sizeof(_memory)); + + for (impl::xml_memory_page* page = root_page->next; page; ) + { + impl::xml_memory_page* next = page->next; + + impl::xml_allocator::deallocate_page(page); + + page = next; + } + + _root = 0; + } + +#ifndef PUGIXML_NO_STL + PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_stream_impl(*this, stream, options, encoding); + } + + PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options) + { + reset(); + + return impl::load_stream_impl(*this, stream, options, encoding_wchar); + } +#endif + + PUGI__FN xml_parse_result xml_document::load_string(const char_t* contents, unsigned int options) + { + // Force native encoding (skip autodetection) + #ifdef PUGIXML_WCHAR_MODE + xml_encoding encoding = encoding_wchar; + #else + xml_encoding encoding = encoding_utf8; + #endif + + return load_buffer(contents, impl::strlength(contents) * sizeof(char_t), options, encoding); + } + + PUGI__FN xml_parse_result xml_document::load(const char_t* contents, unsigned int options) + { + return load_string(contents, options); + } + + PUGI__FN xml_parse_result xml_document::load_file(const char* path_, unsigned int options, xml_encoding encoding) + { + reset(); + + FILE* file = fopen(path_, "rb"); + + return impl::load_file_impl(*this, file, options, encoding); + } + + PUGI__FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding) + { + reset(); + + FILE* file = impl::open_file_wide(path_, L"rb"); + + return impl::load_file_impl(*this, file, options, encoding); + } + + PUGI__FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_buffer_impl(static_cast(_root), _root, const_cast(contents), size, options, encoding, false, false, &_buffer); + } + + PUGI__FN xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_buffer_impl(static_cast(_root), _root, contents, size, options, encoding, true, false, &_buffer); + } + + PUGI__FN xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_buffer_impl(static_cast(_root), _root, contents, size, options, encoding, true, true, &_buffer); + } + + PUGI__FN void xml_document::save(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + impl::xml_buffered_writer buffered_writer(writer, encoding); + + if ((flags & format_write_bom) && encoding != encoding_latin1) + { + // BOM always represents the codepoint U+FEFF, so just write it in native encoding + #ifdef PUGIXML_WCHAR_MODE + unsigned int bom = 0xfeff; + buffered_writer.write(static_cast(bom)); + #else + buffered_writer.write('\xef', '\xbb', '\xbf'); + #endif + } + + if (!(flags & format_no_declaration) && !impl::has_declaration(_root)) + { + buffered_writer.write_string(PUGIXML_TEXT("'); + if (!(flags & format_raw)) buffered_writer.write('\n'); + } + + impl::node_output(buffered_writer, _root, indent, flags, 0); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + xml_writer_stream writer(stream); + + save(writer, indent, flags, encoding); + } + + PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags) const + { + xml_writer_stream writer(stream); + + save(writer, indent, flags, encoding_wchar); + } +#endif + + PUGI__FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + FILE* file = fopen(path_, (flags & format_save_file_text) ? "w" : "wb"); + return impl::save_file_impl(*this, file, indent, flags, encoding); + } + + PUGI__FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + FILE* file = impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb"); + return impl::save_file_impl(*this, file, indent, flags, encoding); + } + + PUGI__FN xml_node xml_document::document_element() const + { + assert(_root); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (PUGI__NODETYPE(i) == node_element) + return xml_node(i); + + return xml_node(); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str) + { + assert(str); + + return impl::as_utf8_impl(str, impl::strlength_wide(str)); + } + + PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const std::basic_string& str) + { + return impl::as_utf8_impl(str.c_str(), str.size()); + } + + PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const char* str) + { + assert(str); + + return impl::as_wide_impl(str, strlen(str)); + } + + PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const std::string& str) + { + return impl::as_wide_impl(str.c_str(), str.size()); + } +#endif + + PUGI__FN void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate) + { + impl::xml_memory::allocate = allocate; + impl::xml_memory::deallocate = deallocate; + } + + PUGI__FN allocation_function PUGIXML_FUNCTION get_memory_allocation_function() + { + return impl::xml_memory::allocate; + } + + PUGI__FN deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function() + { + return impl::xml_memory::deallocate; + } +} + +#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) +namespace std +{ + // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_attribute_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_named_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } +} +#endif + +#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) +namespace std +{ + // Workarounds for (non-standard) iterator category detection + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_attribute_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_named_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } +} +#endif + +#ifndef PUGIXML_NO_XPATH +// STL replacements +PUGI__NS_BEGIN + struct equal_to + { + template bool operator()(const T& lhs, const T& rhs) const + { + return lhs == rhs; + } + }; + + struct not_equal_to + { + template bool operator()(const T& lhs, const T& rhs) const + { + return lhs != rhs; + } + }; + + struct less + { + template bool operator()(const T& lhs, const T& rhs) const + { + return lhs < rhs; + } + }; + + struct less_equal + { + template bool operator()(const T& lhs, const T& rhs) const + { + return lhs <= rhs; + } + }; + + template void swap(T& lhs, T& rhs) + { + T temp = lhs; + lhs = rhs; + rhs = temp; + } + + template I min_element(I begin, I end, const Pred& pred) + { + I result = begin; + + for (I it = begin + 1; it != end; ++it) + if (pred(*it, *result)) + result = it; + + return result; + } + + template void reverse(I begin, I end) + { + while (end - begin > 1) swap(*begin++, *--end); + } + + template I unique(I begin, I end) + { + // fast skip head + while (end - begin > 1 && *begin != *(begin + 1)) begin++; + + if (begin == end) return begin; + + // last written element + I write = begin++; + + // merge unique elements + while (begin != end) + { + if (*begin != *write) + *++write = *begin++; + else + begin++; + } + + // past-the-end (write points to live element) + return write + 1; + } + + template void copy_backwards(I begin, I end, I target) + { + while (begin != end) *--target = *--end; + } + + template void insertion_sort(I begin, I end, const Pred& pred, T*) + { + assert(begin != end); + + for (I it = begin + 1; it != end; ++it) + { + T val = *it; + + if (pred(val, *begin)) + { + // move to front + copy_backwards(begin, it, it + 1); + *begin = val; + } + else + { + I hole = it; + + // move hole backwards + while (pred(val, *(hole - 1))) + { + *hole = *(hole - 1); + hole--; + } + + // fill hole with element + *hole = val; + } + } + } + + // std variant for elements with == + template void partition(I begin, I middle, I end, const Pred& pred, I* out_eqbeg, I* out_eqend) + { + I eqbeg = middle, eqend = middle + 1; + + // expand equal range + while (eqbeg != begin && *(eqbeg - 1) == *eqbeg) --eqbeg; + while (eqend != end && *eqend == *eqbeg) ++eqend; + + // process outer elements + I ltend = eqbeg, gtbeg = eqend; + + for (;;) + { + // find the element from the right side that belongs to the left one + for (; gtbeg != end; ++gtbeg) + if (!pred(*eqbeg, *gtbeg)) + { + if (*gtbeg == *eqbeg) swap(*gtbeg, *eqend++); + else break; + } + + // find the element from the left side that belongs to the right one + for (; ltend != begin; --ltend) + if (!pred(*(ltend - 1), *eqbeg)) + { + if (*eqbeg == *(ltend - 1)) swap(*(ltend - 1), *--eqbeg); + else break; + } + + // scanned all elements + if (gtbeg == end && ltend == begin) + { + *out_eqbeg = eqbeg; + *out_eqend = eqend; + return; + } + + // make room for elements by moving equal area + if (gtbeg == end) + { + if (--ltend != --eqbeg) swap(*ltend, *eqbeg); + swap(*eqbeg, *--eqend); + } + else if (ltend == begin) + { + if (eqend != gtbeg) swap(*eqbeg, *eqend); + ++eqend; + swap(*gtbeg++, *eqbeg++); + } + else swap(*gtbeg++, *--ltend); + } + } + + template void median3(I first, I middle, I last, const Pred& pred) + { + if (pred(*middle, *first)) swap(*middle, *first); + if (pred(*last, *middle)) swap(*last, *middle); + if (pred(*middle, *first)) swap(*middle, *first); + } + + template void median(I first, I middle, I last, const Pred& pred) + { + if (last - first <= 40) + { + // median of three for small chunks + median3(first, middle, last, pred); + } + else + { + // median of nine + size_t step = (last - first + 1) / 8; + + median3(first, first + step, first + 2 * step, pred); + median3(middle - step, middle, middle + step, pred); + median3(last - 2 * step, last - step, last, pred); + median3(first + step, middle, last - step, pred); + } + } + + template void sort(I begin, I end, const Pred& pred) + { + // sort large chunks + while (end - begin > 32) + { + // find median element + I middle = begin + (end - begin) / 2; + median(begin, middle, end - 1, pred); + + // partition in three chunks (< = >) + I eqbeg, eqend; + partition(begin, middle, end, pred, &eqbeg, &eqend); + + // loop on larger half + if (eqbeg - begin > end - eqend) + { + sort(eqend, end, pred); + end = eqbeg; + } + else + { + sort(begin, eqbeg, pred); + begin = eqend; + } + } + + // insertion sort small chunk + if (begin != end) insertion_sort(begin, end, pred, &*begin); + } +PUGI__NS_END + +// Allocator used for AST and evaluation stacks +PUGI__NS_BEGIN + struct xpath_memory_block + { + xpath_memory_block* next; + size_t capacity; + + char data[ + #ifdef PUGIXML_MEMORY_XPATH_PAGE_SIZE + PUGIXML_MEMORY_XPATH_PAGE_SIZE + #else + 4096 + #endif + ]; + }; + + class xpath_allocator + { + xpath_memory_block* _root; + size_t _root_size; + + public: + #ifdef PUGIXML_NO_EXCEPTIONS + jmp_buf* error_handler; + #endif + + xpath_allocator(xpath_memory_block* root, size_t root_size = 0): _root(root), _root_size(root_size) + { + #ifdef PUGIXML_NO_EXCEPTIONS + error_handler = 0; + #endif + } + + void* allocate_nothrow(size_t size) + { + // align size so that we're able to store pointers in subsequent blocks + size = (size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); + + if (_root_size + size <= _root->capacity) + { + void* buf = _root->data + _root_size; + _root_size += size; + return buf; + } + else + { + // make sure we have at least 1/4th of the page free after allocation to satisfy subsequent allocation requests + size_t block_capacity_base = sizeof(_root->data); + size_t block_capacity_req = size + block_capacity_base / 4; + size_t block_capacity = (block_capacity_base > block_capacity_req) ? block_capacity_base : block_capacity_req; + + size_t block_size = block_capacity + offsetof(xpath_memory_block, data); + + xpath_memory_block* block = static_cast(xml_memory::allocate(block_size)); + if (!block) return 0; + + block->next = _root; + block->capacity = block_capacity; + + _root = block; + _root_size = size; + + return block->data; + } + } + + void* allocate(size_t size) + { + void* result = allocate_nothrow(size); + + if (!result) + { + #ifdef PUGIXML_NO_EXCEPTIONS + assert(error_handler); + longjmp(*error_handler, 1); + #else + throw std::bad_alloc(); + #endif + } + + return result; + } + + void* reallocate(void* ptr, size_t old_size, size_t new_size) + { + // align size so that we're able to store pointers in subsequent blocks + old_size = (old_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); + new_size = (new_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); + + // we can only reallocate the last object + assert(ptr == 0 || static_cast(ptr) + old_size == _root->data + _root_size); + + // adjust root size so that we have not allocated the object at all + bool only_object = (_root_size == old_size); + + if (ptr) _root_size -= old_size; + + // allocate a new version (this will obviously reuse the memory if possible) + void* result = allocate(new_size); + assert(result); + + // we have a new block + if (result != ptr && ptr) + { + // copy old data + assert(new_size >= old_size); + memcpy(result, ptr, old_size); + + // free the previous page if it had no other objects + if (only_object) + { + assert(_root->data == result); + assert(_root->next); + + xpath_memory_block* next = _root->next->next; + + if (next) + { + // deallocate the whole page, unless it was the first one + xml_memory::deallocate(_root->next); + _root->next = next; + } + } + } + + return result; + } + + void revert(const xpath_allocator& state) + { + // free all new pages + xpath_memory_block* cur = _root; + + while (cur != state._root) + { + xpath_memory_block* next = cur->next; + + xml_memory::deallocate(cur); + + cur = next; + } + + // restore state + _root = state._root; + _root_size = state._root_size; + } + + void release() + { + xpath_memory_block* cur = _root; + assert(cur); + + while (cur->next) + { + xpath_memory_block* next = cur->next; + + xml_memory::deallocate(cur); + + cur = next; + } + } + }; + + struct xpath_allocator_capture + { + xpath_allocator_capture(xpath_allocator* alloc): _target(alloc), _state(*alloc) + { + } + + ~xpath_allocator_capture() + { + _target->revert(_state); + } + + xpath_allocator* _target; + xpath_allocator _state; + }; + + struct xpath_stack + { + xpath_allocator* result; + xpath_allocator* temp; + }; + + struct xpath_stack_data + { + xpath_memory_block blocks[2]; + xpath_allocator result; + xpath_allocator temp; + xpath_stack stack; + + #ifdef PUGIXML_NO_EXCEPTIONS + jmp_buf error_handler; + #endif + + xpath_stack_data(): result(blocks + 0), temp(blocks + 1) + { + blocks[0].next = blocks[1].next = 0; + blocks[0].capacity = blocks[1].capacity = sizeof(blocks[0].data); + + stack.result = &result; + stack.temp = &temp; + + #ifdef PUGIXML_NO_EXCEPTIONS + result.error_handler = temp.error_handler = &error_handler; + #endif + } + + ~xpath_stack_data() + { + result.release(); + temp.release(); + } + }; +PUGI__NS_END + +// String class +PUGI__NS_BEGIN + class xpath_string + { + const char_t* _buffer; + bool _uses_heap; + size_t _length_heap; + + static char_t* duplicate_string(const char_t* string, size_t length, xpath_allocator* alloc) + { + char_t* result = static_cast(alloc->allocate((length + 1) * sizeof(char_t))); + assert(result); + + memcpy(result, string, length * sizeof(char_t)); + result[length] = 0; + + return result; + } + + xpath_string(const char_t* buffer, bool uses_heap_, size_t length_heap): _buffer(buffer), _uses_heap(uses_heap_), _length_heap(length_heap) + { + } + + public: + static xpath_string from_const(const char_t* str) + { + return xpath_string(str, false, 0); + } + + static xpath_string from_heap_preallocated(const char_t* begin, const char_t* end) + { + assert(begin <= end && *end == 0); + + return xpath_string(begin, true, static_cast(end - begin)); + } + + static xpath_string from_heap(const char_t* begin, const char_t* end, xpath_allocator* alloc) + { + assert(begin <= end); + + size_t length = static_cast(end - begin); + + return length == 0 ? xpath_string() : xpath_string(duplicate_string(begin, length, alloc), true, length); + } + + xpath_string(): _buffer(PUGIXML_TEXT("")), _uses_heap(false), _length_heap(0) + { + } + + void append(const xpath_string& o, xpath_allocator* alloc) + { + // skip empty sources + if (!*o._buffer) return; + + // fast append for constant empty target and constant source + if (!*_buffer && !_uses_heap && !o._uses_heap) + { + _buffer = o._buffer; + } + else + { + // need to make heap copy + size_t target_length = length(); + size_t source_length = o.length(); + size_t result_length = target_length + source_length; + + // allocate new buffer + char_t* result = static_cast(alloc->reallocate(_uses_heap ? const_cast(_buffer) : 0, (target_length + 1) * sizeof(char_t), (result_length + 1) * sizeof(char_t))); + assert(result); + + // append first string to the new buffer in case there was no reallocation + if (!_uses_heap) memcpy(result, _buffer, target_length * sizeof(char_t)); + + // append second string to the new buffer + memcpy(result + target_length, o._buffer, source_length * sizeof(char_t)); + result[result_length] = 0; + + // finalize + _buffer = result; + _uses_heap = true; + _length_heap = result_length; + } + } + + const char_t* c_str() const + { + return _buffer; + } + + size_t length() const + { + return _uses_heap ? _length_heap : strlength(_buffer); + } + + char_t* data(xpath_allocator* alloc) + { + // make private heap copy + if (!_uses_heap) + { + size_t length_ = strlength(_buffer); + + _buffer = duplicate_string(_buffer, length_, alloc); + _uses_heap = true; + _length_heap = length_; + } + + return const_cast(_buffer); + } + + bool empty() const + { + return *_buffer == 0; + } + + bool operator==(const xpath_string& o) const + { + return strequal(_buffer, o._buffer); + } + + bool operator!=(const xpath_string& o) const + { + return !strequal(_buffer, o._buffer); + } + + bool uses_heap() const + { + return _uses_heap; + } + }; +PUGI__NS_END + +PUGI__NS_BEGIN + PUGI__FN bool starts_with(const char_t* string, const char_t* pattern) + { + while (*pattern && *string == *pattern) + { + string++; + pattern++; + } + + return *pattern == 0; + } + + PUGI__FN const char_t* find_char(const char_t* s, char_t c) + { + #ifdef PUGIXML_WCHAR_MODE + return wcschr(s, c); + #else + return strchr(s, c); + #endif + } + + PUGI__FN const char_t* find_substring(const char_t* s, const char_t* p) + { + #ifdef PUGIXML_WCHAR_MODE + // MSVC6 wcsstr bug workaround (if s is empty it always returns 0) + return (*p == 0) ? s : wcsstr(s, p); + #else + return strstr(s, p); + #endif + } + + // Converts symbol to lower case, if it is an ASCII one + PUGI__FN char_t tolower_ascii(char_t ch) + { + return static_cast(ch - 'A') < 26 ? static_cast(ch | ' ') : ch; + } + + PUGI__FN xpath_string string_value(const xpath_node& na, xpath_allocator* alloc) + { + if (na.attribute()) + return xpath_string::from_const(na.attribute().value()); + else + { + xml_node n = na.node(); + + switch (n.type()) + { + case node_pcdata: + case node_cdata: + case node_comment: + case node_pi: + return xpath_string::from_const(n.value()); + + case node_document: + case node_element: + { + xpath_string result; + + xml_node cur = n.first_child(); + + while (cur && cur != n) + { + if (cur.type() == node_pcdata || cur.type() == node_cdata) + result.append(xpath_string::from_const(cur.value()), alloc); + + if (cur.first_child()) + cur = cur.first_child(); + else if (cur.next_sibling()) + cur = cur.next_sibling(); + else + { + while (!cur.next_sibling() && cur != n) + cur = cur.parent(); + + if (cur != n) cur = cur.next_sibling(); + } + } + + return result; + } + + default: + return xpath_string(); + } + } + } + + PUGI__FN bool node_is_before_sibling(xml_node_struct* ln, xml_node_struct* rn) + { + assert(ln->parent == rn->parent); + + // there is no common ancestor (the shared parent is null), nodes are from different documents + if (!ln->parent) return ln < rn; + + // determine sibling order + xml_node_struct* ls = ln; + xml_node_struct* rs = rn; + + while (ls && rs) + { + if (ls == rn) return true; + if (rs == ln) return false; + + ls = ls->next_sibling; + rs = rs->next_sibling; + } + + // if rn sibling chain ended ln must be before rn + return !rs; + } + + PUGI__FN bool node_is_before(xml_node_struct* ln, xml_node_struct* rn) + { + // find common ancestor at the same depth, if any + xml_node_struct* lp = ln; + xml_node_struct* rp = rn; + + while (lp && rp && lp->parent != rp->parent) + { + lp = lp->parent; + rp = rp->parent; + } + + // parents are the same! + if (lp && rp) return node_is_before_sibling(lp, rp); + + // nodes are at different depths, need to normalize heights + bool left_higher = !lp; + + while (lp) + { + lp = lp->parent; + ln = ln->parent; + } + + while (rp) + { + rp = rp->parent; + rn = rn->parent; + } + + // one node is the ancestor of the other + if (ln == rn) return left_higher; + + // find common ancestor... again + while (ln->parent != rn->parent) + { + ln = ln->parent; + rn = rn->parent; + } + + return node_is_before_sibling(ln, rn); + } + + PUGI__FN bool node_is_ancestor(xml_node_struct* parent, xml_node_struct* node) + { + while (node && node != parent) node = node->parent; + + return parent && node == parent; + } + + PUGI__FN const void* document_buffer_order(const xpath_node& xnode) + { + xml_node_struct* node = xnode.node().internal_object(); + + if (node) + { + if ((get_document(node).header & xml_memory_page_contents_shared_mask) == 0) + { + if (node->name && (node->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0) return node->name; + if (node->value && (node->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0) return node->value; + } + + return 0; + } + + xml_attribute_struct* attr = xnode.attribute().internal_object(); + + if (attr) + { + if ((get_document(attr).header & xml_memory_page_contents_shared_mask) == 0) + { + if ((attr->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0) return attr->name; + if ((attr->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0) return attr->value; + } + + return 0; + } + + return 0; + } + + struct document_order_comparator + { + bool operator()(const xpath_node& lhs, const xpath_node& rhs) const + { + // optimized document order based check + const void* lo = document_buffer_order(lhs); + const void* ro = document_buffer_order(rhs); + + if (lo && ro) return lo < ro; + + // slow comparison + xml_node ln = lhs.node(), rn = rhs.node(); + + // compare attributes + if (lhs.attribute() && rhs.attribute()) + { + // shared parent + if (lhs.parent() == rhs.parent()) + { + // determine sibling order + for (xml_attribute a = lhs.attribute(); a; a = a.next_attribute()) + if (a == rhs.attribute()) + return true; + + return false; + } + + // compare attribute parents + ln = lhs.parent(); + rn = rhs.parent(); + } + else if (lhs.attribute()) + { + // attributes go after the parent element + if (lhs.parent() == rhs.node()) return false; + + ln = lhs.parent(); + } + else if (rhs.attribute()) + { + // attributes go after the parent element + if (rhs.parent() == lhs.node()) return true; + + rn = rhs.parent(); + } + + if (ln == rn) return false; + + if (!ln || !rn) return ln < rn; + + return node_is_before(ln.internal_object(), rn.internal_object()); + } + }; + + struct duplicate_comparator + { + bool operator()(const xpath_node& lhs, const xpath_node& rhs) const + { + if (lhs.attribute()) return rhs.attribute() ? lhs.attribute() < rhs.attribute() : true; + else return rhs.attribute() ? false : lhs.node() < rhs.node(); + } + }; + + PUGI__FN double gen_nan() + { + #if defined(__STDC_IEC_559__) || ((FLT_RADIX - 0 == 2) && (FLT_MAX_EXP - 0 == 128) && (FLT_MANT_DIG - 0 == 24)) + union { float f; uint32_t i; } u[sizeof(float) == sizeof(uint32_t) ? 1 : -1]; + u[0].i = 0x7fc00000; + return u[0].f; + #else + // fallback + const volatile double zero = 0.0; + return zero / zero; + #endif + } + + PUGI__FN bool is_nan(double value) + { + #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) + return !!_isnan(value); + #elif defined(fpclassify) && defined(FP_NAN) + return fpclassify(value) == FP_NAN; + #else + // fallback + const volatile double v = value; + return v != v; + #endif + } + + PUGI__FN const char_t* convert_number_to_string_special(double value) + { + #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) + if (_finite(value)) return (value == 0) ? PUGIXML_TEXT("0") : 0; + if (_isnan(value)) return PUGIXML_TEXT("NaN"); + return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + #elif defined(fpclassify) && defined(FP_NAN) && defined(FP_INFINITE) && defined(FP_ZERO) + switch (fpclassify(value)) + { + case FP_NAN: + return PUGIXML_TEXT("NaN"); + + case FP_INFINITE: + return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + + case FP_ZERO: + return PUGIXML_TEXT("0"); + + default: + return 0; + } + #else + // fallback + const volatile double v = value; + + if (v == 0) return PUGIXML_TEXT("0"); + if (v != v) return PUGIXML_TEXT("NaN"); + if (v * 2 == v) return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + return 0; + #endif + } + + PUGI__FN bool convert_number_to_boolean(double value) + { + return (value != 0 && !is_nan(value)); + } + + PUGI__FN void truncate_zeros(char* begin, char* end) + { + while (begin != end && end[-1] == '0') end--; + + *end = 0; + } + + // gets mantissa digits in the form of 0.xxxxx with 0. implied and the exponent +#if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE) + PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) + { + // get base values + int sign, exponent; + _ecvt_s(buffer, buffer_size, value, DBL_DIG + 1, &exponent, &sign); + + // truncate redundant zeros + truncate_zeros(buffer, buffer + strlen(buffer)); + + // fill results + *out_mantissa = buffer; + *out_exponent = exponent; + } +#else + PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) + { + // get a scientific notation value with IEEE DBL_DIG decimals + sprintf(buffer, "%.*e", DBL_DIG, value); + assert(strlen(buffer) < buffer_size); + (void)!buffer_size; + + // get the exponent (possibly negative) + char* exponent_string = strchr(buffer, 'e'); + assert(exponent_string); + + int exponent = atoi(exponent_string + 1); + + // extract mantissa string: skip sign + char* mantissa = buffer[0] == '-' ? buffer + 1 : buffer; + assert(mantissa[0] != '0' && mantissa[1] == '.'); + + // divide mantissa by 10 to eliminate integer part + mantissa[1] = mantissa[0]; + mantissa++; + exponent++; + + // remove extra mantissa digits and zero-terminate mantissa + truncate_zeros(mantissa, exponent_string); + + // fill results + *out_mantissa = mantissa; + *out_exponent = exponent; + } +#endif + + PUGI__FN xpath_string convert_number_to_string(double value, xpath_allocator* alloc) + { + // try special number conversion + const char_t* special = convert_number_to_string_special(value); + if (special) return xpath_string::from_const(special); + + // get mantissa + exponent form + char mantissa_buffer[32]; + + char* mantissa; + int exponent; + convert_number_to_mantissa_exponent(value, mantissa_buffer, sizeof(mantissa_buffer), &mantissa, &exponent); + + // allocate a buffer of suitable length for the number + size_t result_size = strlen(mantissa_buffer) + (exponent > 0 ? exponent : -exponent) + 4; + char_t* result = static_cast(alloc->allocate(sizeof(char_t) * result_size)); + assert(result); + + // make the number! + char_t* s = result; + + // sign + if (value < 0) *s++ = '-'; + + // integer part + if (exponent <= 0) + { + *s++ = '0'; + } + else + { + while (exponent > 0) + { + assert(*mantissa == 0 || static_cast(static_cast(*mantissa) - '0') <= 9); + *s++ = *mantissa ? *mantissa++ : '0'; + exponent--; + } + } + + // fractional part + if (*mantissa) + { + // decimal point + *s++ = '.'; + + // extra zeroes from negative exponent + while (exponent < 0) + { + *s++ = '0'; + exponent++; + } + + // extra mantissa digits + while (*mantissa) + { + assert(static_cast(*mantissa - '0') <= 9); + *s++ = *mantissa++; + } + } + + // zero-terminate + assert(s < result + result_size); + *s = 0; + + return xpath_string::from_heap_preallocated(result, s); + } + + PUGI__FN bool check_string_to_number_format(const char_t* string) + { + // parse leading whitespace + while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; + + // parse sign + if (*string == '-') ++string; + + if (!*string) return false; + + // if there is no integer part, there should be a decimal part with at least one digit + if (!PUGI__IS_CHARTYPEX(string[0], ctx_digit) && (string[0] != '.' || !PUGI__IS_CHARTYPEX(string[1], ctx_digit))) return false; + + // parse integer part + while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; + + // parse decimal part + if (*string == '.') + { + ++string; + + while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; + } + + // parse trailing whitespace + while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; + + return *string == 0; + } + + PUGI__FN double convert_string_to_number(const char_t* string) + { + // check string format + if (!check_string_to_number_format(string)) return gen_nan(); + + // parse string + #ifdef PUGIXML_WCHAR_MODE + return wcstod(string, 0); + #else + return atof(string); + #endif + } + + PUGI__FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result) + { + size_t length = static_cast(end - begin); + char_t* scratch = buffer; + + if (length >= sizeof(buffer) / sizeof(buffer[0])) + { + // need to make dummy on-heap copy + scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!scratch) return false; + } + + // copy string to zero-terminated buffer and perform conversion + memcpy(scratch, begin, length * sizeof(char_t)); + scratch[length] = 0; + + *out_result = convert_string_to_number(scratch); + + // free dummy buffer + if (scratch != buffer) xml_memory::deallocate(scratch); + + return true; + } + + PUGI__FN double round_nearest(double value) + { + return floor(value + 0.5); + } + + PUGI__FN double round_nearest_nzero(double value) + { + // same as round_nearest, but returns -0 for [-0.5, -0] + // ceil is used to differentiate between +0 and -0 (we return -0 for [-0.5, -0] and +0 for +0) + return (value >= -0.5 && value <= 0) ? ceil(value) : floor(value + 0.5); + } + + PUGI__FN const char_t* qualified_name(const xpath_node& node) + { + return node.attribute() ? node.attribute().name() : node.node().name(); + } + + PUGI__FN const char_t* local_name(const xpath_node& node) + { + const char_t* name = qualified_name(node); + const char_t* p = find_char(name, ':'); + + return p ? p + 1 : name; + } + + struct namespace_uri_predicate + { + const char_t* prefix; + size_t prefix_length; + + namespace_uri_predicate(const char_t* name) + { + const char_t* pos = find_char(name, ':'); + + prefix = pos ? name : 0; + prefix_length = pos ? static_cast(pos - name) : 0; + } + + bool operator()(xml_attribute a) const + { + const char_t* name = a.name(); + + if (!starts_with(name, PUGIXML_TEXT("xmlns"))) return false; + + return prefix ? name[5] == ':' && strequalrange(name + 6, prefix, prefix_length) : name[5] == 0; + } + }; + + PUGI__FN const char_t* namespace_uri(xml_node node) + { + namespace_uri_predicate pred = node.name(); + + xml_node p = node; + + while (p) + { + xml_attribute a = p.find_attribute(pred); + + if (a) return a.value(); + + p = p.parent(); + } + + return PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* namespace_uri(xml_attribute attr, xml_node parent) + { + namespace_uri_predicate pred = attr.name(); + + // Default namespace does not apply to attributes + if (!pred.prefix) return PUGIXML_TEXT(""); + + xml_node p = parent; + + while (p) + { + xml_attribute a = p.find_attribute(pred); + + if (a) return a.value(); + + p = p.parent(); + } + + return PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* namespace_uri(const xpath_node& node) + { + return node.attribute() ? namespace_uri(node.attribute(), node.parent()) : namespace_uri(node.node()); + } + + PUGI__FN char_t* normalize_space(char_t* buffer) + { + char_t* write = buffer; + + for (char_t* it = buffer; *it; ) + { + char_t ch = *it++; + + if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + // replace whitespace sequence with single space + while (PUGI__IS_CHARTYPE(*it, ct_space)) it++; + + // avoid leading spaces + if (write != buffer) *write++ = ' '; + } + else *write++ = ch; + } + + // remove trailing space + if (write != buffer && PUGI__IS_CHARTYPE(write[-1], ct_space)) write--; + + // zero-terminate + *write = 0; + + return write; + } + + PUGI__FN char_t* translate(char_t* buffer, const char_t* from, const char_t* to, size_t to_length) + { + char_t* write = buffer; + + while (*buffer) + { + PUGI__DMC_VOLATILE char_t ch = *buffer++; + + const char_t* pos = find_char(from, ch); + + if (!pos) + *write++ = ch; // do not process + else if (static_cast(pos - from) < to_length) + *write++ = to[pos - from]; // replace + } + + // zero-terminate + *write = 0; + + return write; + } + + PUGI__FN unsigned char* translate_table_generate(xpath_allocator* alloc, const char_t* from, const char_t* to) + { + unsigned char table[128] = {0}; + + while (*from) + { + unsigned int fc = static_cast(*from); + unsigned int tc = static_cast(*to); + + if (fc >= 128 || tc >= 128) + return 0; + + // code=128 means "skip character" + if (!table[fc]) + table[fc] = static_cast(tc ? tc : 128); + + from++; + if (tc) to++; + } + + for (int i = 0; i < 128; ++i) + if (!table[i]) + table[i] = static_cast(i); + + void* result = alloc->allocate_nothrow(sizeof(table)); + + if (result) + { + memcpy(result, table, sizeof(table)); + } + + return static_cast(result); + } + + PUGI__FN char_t* translate_table(char_t* buffer, const unsigned char* table) + { + char_t* write = buffer; + + while (*buffer) + { + char_t ch = *buffer++; + unsigned int index = static_cast(ch); + + if (index < 128) + { + unsigned char code = table[index]; + + // code=128 means "skip character" (table size is 128 so 128 can be a special value) + // this code skips these characters without extra branches + *write = static_cast(code); + write += 1 - (code >> 7); + } + else + { + *write++ = ch; + } + } + + // zero-terminate + *write = 0; + + return write; + } + + inline bool is_xpath_attribute(const char_t* name) + { + return !(starts_with(name, PUGIXML_TEXT("xmlns")) && (name[5] == 0 || name[5] == ':')); + } + + struct xpath_variable_boolean: xpath_variable + { + xpath_variable_boolean(): value(false) + { + } + + bool value; + char_t name[1]; + }; + + struct xpath_variable_number: xpath_variable + { + xpath_variable_number(): value(0) + { + } + + double value; + char_t name[1]; + }; + + struct xpath_variable_string: xpath_variable + { + xpath_variable_string(): value(0) + { + } + + ~xpath_variable_string() + { + if (value) xml_memory::deallocate(value); + } + + char_t* value; + char_t name[1]; + }; + + struct xpath_variable_node_set: xpath_variable + { + xpath_node_set value; + char_t name[1]; + }; + + static const xpath_node_set dummy_node_set; + + PUGI__FN unsigned int hash_string(const char_t* str) + { + // Jenkins one-at-a-time hash (http://en.wikipedia.org/wiki/Jenkins_hash_function#one-at-a-time) + unsigned int result = 0; + + while (*str) + { + result += static_cast(*str++); + result += result << 10; + result ^= result >> 6; + } + + result += result << 3; + result ^= result >> 11; + result += result << 15; + + return result; + } + + template PUGI__FN T* new_xpath_variable(const char_t* name) + { + size_t length = strlength(name); + if (length == 0) return 0; // empty variable names are invalid + + // $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters + void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t)); + if (!memory) return 0; + + T* result = new (memory) T(); + + memcpy(result->name, name, (length + 1) * sizeof(char_t)); + + return result; + } + + PUGI__FN xpath_variable* new_xpath_variable(xpath_value_type type, const char_t* name) + { + switch (type) + { + case xpath_type_node_set: + return new_xpath_variable(name); + + case xpath_type_number: + return new_xpath_variable(name); + + case xpath_type_string: + return new_xpath_variable(name); + + case xpath_type_boolean: + return new_xpath_variable(name); + + default: + return 0; + } + } + + template PUGI__FN void delete_xpath_variable(T* var) + { + var->~T(); + xml_memory::deallocate(var); + } + + PUGI__FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var) + { + switch (type) + { + case xpath_type_node_set: + delete_xpath_variable(static_cast(var)); + break; + + case xpath_type_number: + delete_xpath_variable(static_cast(var)); + break; + + case xpath_type_string: + delete_xpath_variable(static_cast(var)); + break; + + case xpath_type_boolean: + delete_xpath_variable(static_cast(var)); + break; + + default: + assert(!"Invalid variable type"); + } + } + + PUGI__FN xpath_variable* get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end) + { + size_t length = static_cast(end - begin); + char_t* scratch = buffer; + + if (length >= sizeof(buffer) / sizeof(buffer[0])) + { + // need to make dummy on-heap copy + scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!scratch) return 0; + } + + // copy string to zero-terminated buffer and perform lookup + memcpy(scratch, begin, length * sizeof(char_t)); + scratch[length] = 0; + + xpath_variable* result = set->get(scratch); + + // free dummy buffer + if (scratch != buffer) xml_memory::deallocate(scratch); + + return result; + } +PUGI__NS_END + +// Internal node set class +PUGI__NS_BEGIN + PUGI__FN xpath_node_set::type_t xpath_get_order(const xpath_node* begin, const xpath_node* end) + { + if (end - begin < 2) + return xpath_node_set::type_sorted; + + document_order_comparator cmp; + + bool first = cmp(begin[0], begin[1]); + + for (const xpath_node* it = begin + 1; it + 1 < end; ++it) + if (cmp(it[0], it[1]) != first) + return xpath_node_set::type_unsorted; + + return first ? xpath_node_set::type_sorted : xpath_node_set::type_sorted_reverse; + } + + PUGI__FN xpath_node_set::type_t xpath_sort(xpath_node* begin, xpath_node* end, xpath_node_set::type_t type, bool rev) + { + xpath_node_set::type_t order = rev ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; + + if (type == xpath_node_set::type_unsorted) + { + xpath_node_set::type_t sorted = xpath_get_order(begin, end); + + if (sorted == xpath_node_set::type_unsorted) + { + sort(begin, end, document_order_comparator()); + + type = xpath_node_set::type_sorted; + } + else + type = sorted; + } + + if (type != order) reverse(begin, end); + + return order; + } + + PUGI__FN xpath_node xpath_first(const xpath_node* begin, const xpath_node* end, xpath_node_set::type_t type) + { + if (begin == end) return xpath_node(); + + switch (type) + { + case xpath_node_set::type_sorted: + return *begin; + + case xpath_node_set::type_sorted_reverse: + return *(end - 1); + + case xpath_node_set::type_unsorted: + return *min_element(begin, end, document_order_comparator()); + + default: + assert(!"Invalid node set type"); + return xpath_node(); + } + } + + class xpath_node_set_raw + { + xpath_node_set::type_t _type; + + xpath_node* _begin; + xpath_node* _end; + xpath_node* _eos; + + public: + xpath_node_set_raw(): _type(xpath_node_set::type_unsorted), _begin(0), _end(0), _eos(0) + { + } + + xpath_node* begin() const + { + return _begin; + } + + xpath_node* end() const + { + return _end; + } + + bool empty() const + { + return _begin == _end; + } + + size_t size() const + { + return static_cast(_end - _begin); + } + + xpath_node first() const + { + return xpath_first(_begin, _end, _type); + } + + void push_back_grow(const xpath_node& node, xpath_allocator* alloc); + + void push_back(const xpath_node& node, xpath_allocator* alloc) + { + if (_end != _eos) + *_end++ = node; + else + push_back_grow(node, alloc); + } + + void append(const xpath_node* begin_, const xpath_node* end_, xpath_allocator* alloc) + { + if (begin_ == end_) return; + + size_t size_ = static_cast(_end - _begin); + size_t capacity = static_cast(_eos - _begin); + size_t count = static_cast(end_ - begin_); + + if (size_ + count > capacity) + { + // reallocate the old array or allocate a new one + xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), (size_ + count) * sizeof(xpath_node))); + assert(data); + + // finalize + _begin = data; + _end = data + size_; + _eos = data + size_ + count; + } + + memcpy(_end, begin_, count * sizeof(xpath_node)); + _end += count; + } + + void sort_do() + { + _type = xpath_sort(_begin, _end, _type, false); + } + + void truncate(xpath_node* pos) + { + assert(_begin <= pos && pos <= _end); + + _end = pos; + } + + void remove_duplicates() + { + if (_type == xpath_node_set::type_unsorted) + sort(_begin, _end, duplicate_comparator()); + + _end = unique(_begin, _end); + } + + xpath_node_set::type_t type() const + { + return _type; + } + + void set_type(xpath_node_set::type_t value) + { + _type = value; + } + }; + + PUGI__FN_NO_INLINE void xpath_node_set_raw::push_back_grow(const xpath_node& node, xpath_allocator* alloc) + { + size_t capacity = static_cast(_eos - _begin); + + // get new capacity (1.5x rule) + size_t new_capacity = capacity + capacity / 2 + 1; + + // reallocate the old array or allocate a new one + xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), new_capacity * sizeof(xpath_node))); + assert(data); + + // finalize + _begin = data; + _end = data + capacity; + _eos = data + new_capacity; + + // push + *_end++ = node; + } +PUGI__NS_END + +PUGI__NS_BEGIN + struct xpath_context + { + xpath_node n; + size_t position, size; + + xpath_context(const xpath_node& n_, size_t position_, size_t size_): n(n_), position(position_), size(size_) + { + } + }; + + enum lexeme_t + { + lex_none = 0, + lex_equal, + lex_not_equal, + lex_less, + lex_greater, + lex_less_or_equal, + lex_greater_or_equal, + lex_plus, + lex_minus, + lex_multiply, + lex_union, + lex_var_ref, + lex_open_brace, + lex_close_brace, + lex_quoted_string, + lex_number, + lex_slash, + lex_double_slash, + lex_open_square_brace, + lex_close_square_brace, + lex_string, + lex_comma, + lex_axis_attribute, + lex_dot, + lex_double_dot, + lex_double_colon, + lex_eof + }; + + struct xpath_lexer_string + { + const char_t* begin; + const char_t* end; + + xpath_lexer_string(): begin(0), end(0) + { + } + + bool operator==(const char_t* other) const + { + size_t length = static_cast(end - begin); + + return strequalrange(other, begin, length); + } + }; + + class xpath_lexer + { + const char_t* _cur; + const char_t* _cur_lexeme_pos; + xpath_lexer_string _cur_lexeme_contents; + + lexeme_t _cur_lexeme; + + public: + explicit xpath_lexer(const char_t* query): _cur(query) + { + next(); + } + + const char_t* state() const + { + return _cur; + } + + void next() + { + const char_t* cur = _cur; + + while (PUGI__IS_CHARTYPE(*cur, ct_space)) ++cur; + + // save lexeme position for error reporting + _cur_lexeme_pos = cur; + + switch (*cur) + { + case 0: + _cur_lexeme = lex_eof; + break; + + case '>': + if (*(cur+1) == '=') + { + cur += 2; + _cur_lexeme = lex_greater_or_equal; + } + else + { + cur += 1; + _cur_lexeme = lex_greater; + } + break; + + case '<': + if (*(cur+1) == '=') + { + cur += 2; + _cur_lexeme = lex_less_or_equal; + } + else + { + cur += 1; + _cur_lexeme = lex_less; + } + break; + + case '!': + if (*(cur+1) == '=') + { + cur += 2; + _cur_lexeme = lex_not_equal; + } + else + { + _cur_lexeme = lex_none; + } + break; + + case '=': + cur += 1; + _cur_lexeme = lex_equal; + + break; + + case '+': + cur += 1; + _cur_lexeme = lex_plus; + + break; + + case '-': + cur += 1; + _cur_lexeme = lex_minus; + + break; + + case '*': + cur += 1; + _cur_lexeme = lex_multiply; + + break; + + case '|': + cur += 1; + _cur_lexeme = lex_union; + + break; + + case '$': + cur += 1; + + if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) + { + _cur_lexeme_contents.begin = cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + + if (cur[0] == ':' && PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // qname + { + cur++; // : + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + } + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_var_ref; + } + else + { + _cur_lexeme = lex_none; + } + + break; + + case '(': + cur += 1; + _cur_lexeme = lex_open_brace; + + break; + + case ')': + cur += 1; + _cur_lexeme = lex_close_brace; + + break; + + case '[': + cur += 1; + _cur_lexeme = lex_open_square_brace; + + break; + + case ']': + cur += 1; + _cur_lexeme = lex_close_square_brace; + + break; + + case ',': + cur += 1; + _cur_lexeme = lex_comma; + + break; + + case '/': + if (*(cur+1) == '/') + { + cur += 2; + _cur_lexeme = lex_double_slash; + } + else + { + cur += 1; + _cur_lexeme = lex_slash; + } + break; + + case '.': + if (*(cur+1) == '.') + { + cur += 2; + _cur_lexeme = lex_double_dot; + } + else if (PUGI__IS_CHARTYPEX(*(cur+1), ctx_digit)) + { + _cur_lexeme_contents.begin = cur; // . + + ++cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_number; + } + else + { + cur += 1; + _cur_lexeme = lex_dot; + } + break; + + case '@': + cur += 1; + _cur_lexeme = lex_axis_attribute; + + break; + + case '"': + case '\'': + { + char_t terminator = *cur; + + ++cur; + + _cur_lexeme_contents.begin = cur; + while (*cur && *cur != terminator) cur++; + _cur_lexeme_contents.end = cur; + + if (!*cur) + _cur_lexeme = lex_none; + else + { + cur += 1; + _cur_lexeme = lex_quoted_string; + } + + break; + } + + case ':': + if (*(cur+1) == ':') + { + cur += 2; + _cur_lexeme = lex_double_colon; + } + else + { + _cur_lexeme = lex_none; + } + break; + + default: + if (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) + { + _cur_lexeme_contents.begin = cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + + if (*cur == '.') + { + cur++; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + } + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_number; + } + else if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) + { + _cur_lexeme_contents.begin = cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + + if (cur[0] == ':') + { + if (cur[1] == '*') // namespace test ncname:* + { + cur += 2; // :* + } + else if (PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // namespace test qname + { + cur++; // : + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + } + } + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_string; + } + else + { + _cur_lexeme = lex_none; + } + } + + _cur = cur; + } + + lexeme_t current() const + { + return _cur_lexeme; + } + + const char_t* current_pos() const + { + return _cur_lexeme_pos; + } + + const xpath_lexer_string& contents() const + { + assert(_cur_lexeme == lex_var_ref || _cur_lexeme == lex_number || _cur_lexeme == lex_string || _cur_lexeme == lex_quoted_string); + + return _cur_lexeme_contents; + } + }; + + enum ast_type_t + { + ast_unknown, + ast_op_or, // left or right + ast_op_and, // left and right + ast_op_equal, // left = right + ast_op_not_equal, // left != right + ast_op_less, // left < right + ast_op_greater, // left > right + ast_op_less_or_equal, // left <= right + ast_op_greater_or_equal, // left >= right + ast_op_add, // left + right + ast_op_subtract, // left - right + ast_op_multiply, // left * right + ast_op_divide, // left / right + ast_op_mod, // left % right + ast_op_negate, // left - right + ast_op_union, // left | right + ast_predicate, // apply predicate to set; next points to next predicate + ast_filter, // select * from left where right + ast_string_constant, // string constant + ast_number_constant, // number constant + ast_variable, // variable + ast_func_last, // last() + ast_func_position, // position() + ast_func_count, // count(left) + ast_func_id, // id(left) + ast_func_local_name_0, // local-name() + ast_func_local_name_1, // local-name(left) + ast_func_namespace_uri_0, // namespace-uri() + ast_func_namespace_uri_1, // namespace-uri(left) + ast_func_name_0, // name() + ast_func_name_1, // name(left) + ast_func_string_0, // string() + ast_func_string_1, // string(left) + ast_func_concat, // concat(left, right, siblings) + ast_func_starts_with, // starts_with(left, right) + ast_func_contains, // contains(left, right) + ast_func_substring_before, // substring-before(left, right) + ast_func_substring_after, // substring-after(left, right) + ast_func_substring_2, // substring(left, right) + ast_func_substring_3, // substring(left, right, third) + ast_func_string_length_0, // string-length() + ast_func_string_length_1, // string-length(left) + ast_func_normalize_space_0, // normalize-space() + ast_func_normalize_space_1, // normalize-space(left) + ast_func_translate, // translate(left, right, third) + ast_func_boolean, // boolean(left) + ast_func_not, // not(left) + ast_func_true, // true() + ast_func_false, // false() + ast_func_lang, // lang(left) + ast_func_number_0, // number() + ast_func_number_1, // number(left) + ast_func_sum, // sum(left) + ast_func_floor, // floor(left) + ast_func_ceiling, // ceiling(left) + ast_func_round, // round(left) + ast_step, // process set left with step + ast_step_root, // select root node + + ast_opt_translate_table, // translate(left, right, third) where right/third are constants + ast_opt_compare_attribute // @name = 'string' + }; + + enum axis_t + { + axis_ancestor, + axis_ancestor_or_self, + axis_attribute, + axis_child, + axis_descendant, + axis_descendant_or_self, + axis_following, + axis_following_sibling, + axis_namespace, + axis_parent, + axis_preceding, + axis_preceding_sibling, + axis_self + }; + + enum nodetest_t + { + nodetest_none, + nodetest_name, + nodetest_type_node, + nodetest_type_comment, + nodetest_type_pi, + nodetest_type_text, + nodetest_pi, + nodetest_all, + nodetest_all_in_namespace + }; + + enum predicate_t + { + predicate_default, + predicate_posinv, + predicate_constant, + predicate_constant_one + }; + + enum nodeset_eval_t + { + nodeset_eval_all, + nodeset_eval_any, + nodeset_eval_first + }; + + template struct axis_to_type + { + static const axis_t axis; + }; + + template const axis_t axis_to_type::axis = N; + + class xpath_ast_node + { + private: + // node type + char _type; + char _rettype; + + // for ast_step + char _axis; + + // for ast_step/ast_predicate/ast_filter + char _test; + + // tree node structure + xpath_ast_node* _left; + xpath_ast_node* _right; + xpath_ast_node* _next; + + union + { + // value for ast_string_constant + const char_t* string; + // value for ast_number_constant + double number; + // variable for ast_variable + xpath_variable* variable; + // node test for ast_step (node name/namespace/node type/pi target) + const char_t* nodetest; + // table for ast_opt_translate_table + const unsigned char* table; + } _data; + + xpath_ast_node(const xpath_ast_node&); + xpath_ast_node& operator=(const xpath_ast_node&); + + template static bool compare_eq(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) + { + xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); + + if (lt != xpath_type_node_set && rt != xpath_type_node_set) + { + if (lt == xpath_type_boolean || rt == xpath_type_boolean) + return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); + else if (lt == xpath_type_number || rt == xpath_type_number) + return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); + else if (lt == xpath_type_string || rt == xpath_type_string) + { + xpath_allocator_capture cr(stack.result); + + xpath_string ls = lhs->eval_string(c, stack); + xpath_string rs = rhs->eval_string(c, stack); + + return comp(ls, rs); + } + } + else if (lt == xpath_type_node_set && rt == xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(string_value(*li, stack.result), string_value(*ri, stack.result))) + return true; + } + + return false; + } + else + { + if (lt == xpath_type_node_set) + { + swap(lhs, rhs); + swap(lt, rt); + } + + if (lt == xpath_type_boolean) + return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); + else if (lt == xpath_type_number) + { + xpath_allocator_capture cr(stack.result); + + double l = lhs->eval_number(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) + return true; + } + + return false; + } + else if (lt == xpath_type_string) + { + xpath_allocator_capture cr(stack.result); + + xpath_string l = lhs->eval_string(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(l, string_value(*ri, stack.result))) + return true; + } + + return false; + } + } + + assert(!"Wrong types"); + return false; + } + + static bool eval_once(xpath_node_set::type_t type, nodeset_eval_t eval) + { + return type == xpath_node_set::type_sorted ? eval != nodeset_eval_all : eval == nodeset_eval_any; + } + + template static bool compare_rel(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) + { + xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); + + if (lt != xpath_type_node_set && rt != xpath_type_node_set) + return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); + else if (lt == xpath_type_node_set && rt == xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) + { + xpath_allocator_capture cri(stack.result); + + double l = convert_string_to_number(string_value(*li, stack.result).c_str()); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture crii(stack.result); + + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) + return true; + } + } + + return false; + } + else if (lt != xpath_type_node_set && rt == xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + double l = lhs->eval_number(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) + return true; + } + + return false; + } + else if (lt == xpath_type_node_set && rt != xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all); + double r = rhs->eval_number(c, stack); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) + { + xpath_allocator_capture cri(stack.result); + + if (comp(convert_string_to_number(string_value(*li, stack.result).c_str()), r)) + return true; + } + + return false; + } + else + { + assert(!"Wrong types"); + return false; + } + } + + static void apply_predicate_boolean(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack, bool once) + { + assert(ns.size() >= first); + assert(expr->rettype() != xpath_type_number); + + size_t i = 1; + size_t size = ns.size() - first; + + xpath_node* last = ns.begin() + first; + + // remove_if... or well, sort of + for (xpath_node* it = last; it != ns.end(); ++it, ++i) + { + xpath_context c(*it, i, size); + + if (expr->eval_boolean(c, stack)) + { + *last++ = *it; + + if (once) break; + } + } + + ns.truncate(last); + } + + static void apply_predicate_number(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack, bool once) + { + assert(ns.size() >= first); + assert(expr->rettype() == xpath_type_number); + + size_t i = 1; + size_t size = ns.size() - first; + + xpath_node* last = ns.begin() + first; + + // remove_if... or well, sort of + for (xpath_node* it = last; it != ns.end(); ++it, ++i) + { + xpath_context c(*it, i, size); + + if (expr->eval_number(c, stack) == i) + { + *last++ = *it; + + if (once) break; + } + } + + ns.truncate(last); + } + + static void apply_predicate_number_const(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack) + { + assert(ns.size() >= first); + assert(expr->rettype() == xpath_type_number); + + size_t size = ns.size() - first; + + xpath_node* last = ns.begin() + first; + + xpath_context c(xpath_node(), 1, size); + + double er = expr->eval_number(c, stack); + + if (er >= 1.0 && er <= size) + { + size_t eri = static_cast(er); + + if (er == eri) + { + xpath_node r = last[eri - 1]; + + *last++ = r; + } + } + + ns.truncate(last); + } + + void apply_predicate(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack, bool once) + { + if (ns.size() == first) return; + + assert(_type == ast_filter || _type == ast_predicate); + + if (_test == predicate_constant || _test == predicate_constant_one) + apply_predicate_number_const(ns, first, _right, stack); + else if (_right->rettype() == xpath_type_number) + apply_predicate_number(ns, first, _right, stack, once); + else + apply_predicate_boolean(ns, first, _right, stack, once); + } + + void apply_predicates(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack, nodeset_eval_t eval) + { + if (ns.size() == first) return; + + bool last_once = eval_once(ns.type(), eval); + + for (xpath_ast_node* pred = _right; pred; pred = pred->_next) + pred->apply_predicate(ns, first, stack, !pred->_next && last_once); + } + + bool step_push(xpath_node_set_raw& ns, xml_attribute_struct* a, xml_node_struct* parent, xpath_allocator* alloc) + { + assert(a); + + const char_t* name = a->name ? a->name : PUGIXML_TEXT(""); + + switch (_test) + { + case nodetest_name: + if (strequal(name, _data.nodetest) && is_xpath_attribute(name)) + { + ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc); + return true; + } + break; + + case nodetest_type_node: + case nodetest_all: + if (is_xpath_attribute(name)) + { + ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc); + return true; + } + break; + + case nodetest_all_in_namespace: + if (starts_with(name, _data.nodetest) && is_xpath_attribute(name)) + { + ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc); + return true; + } + break; + + default: + ; + } + + return false; + } + + bool step_push(xpath_node_set_raw& ns, xml_node_struct* n, xpath_allocator* alloc) + { + assert(n); + + xml_node_type type = PUGI__NODETYPE(n); + + switch (_test) + { + case nodetest_name: + if (type == node_element && n->name && strequal(n->name, _data.nodetest)) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + case nodetest_type_node: + ns.push_back(xml_node(n), alloc); + return true; + + case nodetest_type_comment: + if (type == node_comment) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + case nodetest_type_text: + if (type == node_pcdata || type == node_cdata) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + case nodetest_type_pi: + if (type == node_pi) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + case nodetest_pi: + if (type == node_pi && n->name && strequal(n->name, _data.nodetest)) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + case nodetest_all: + if (type == node_element) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + case nodetest_all_in_namespace: + if (type == node_element && n->name && starts_with(n->name, _data.nodetest)) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + default: + assert(!"Unknown axis"); + } + + return false; + } + + template void step_fill(xpath_node_set_raw& ns, xml_node_struct* n, xpath_allocator* alloc, bool once, T) + { + const axis_t axis = T::axis; + + switch (axis) + { + case axis_attribute: + { + for (xml_attribute_struct* a = n->first_attribute; a; a = a->next_attribute) + if (step_push(ns, a, n, alloc) & once) + return; + + break; + } + + case axis_child: + { + for (xml_node_struct* c = n->first_child; c; c = c->next_sibling) + if (step_push(ns, c, alloc) & once) + return; + + break; + } + + case axis_descendant: + case axis_descendant_or_self: + { + if (axis == axis_descendant_or_self) + if (step_push(ns, n, alloc) & once) + return; + + xml_node_struct* cur = n->first_child; + + while (cur) + { + if (step_push(ns, cur, alloc) & once) + return; + + if (cur->first_child) + cur = cur->first_child; + else + { + while (!cur->next_sibling) + { + cur = cur->parent; + + if (cur == n) return; + } + + cur = cur->next_sibling; + } + } + + break; + } + + case axis_following_sibling: + { + for (xml_node_struct* c = n->next_sibling; c; c = c->next_sibling) + if (step_push(ns, c, alloc) & once) + return; + + break; + } + + case axis_preceding_sibling: + { + for (xml_node_struct* c = n->prev_sibling_c; c->next_sibling; c = c->prev_sibling_c) + if (step_push(ns, c, alloc) & once) + return; + + break; + } + + case axis_following: + { + xml_node_struct* cur = n; + + // exit from this node so that we don't include descendants + while (!cur->next_sibling) + { + cur = cur->parent; + + if (!cur) return; + } + + cur = cur->next_sibling; + + while (cur) + { + if (step_push(ns, cur, alloc) & once) + return; + + if (cur->first_child) + cur = cur->first_child; + else + { + while (!cur->next_sibling) + { + cur = cur->parent; + + if (!cur) return; + } + + cur = cur->next_sibling; + } + } + + break; + } + + case axis_preceding: + { + xml_node_struct* cur = n; + + // exit from this node so that we don't include descendants + while (!cur->prev_sibling_c->next_sibling) + { + cur = cur->parent; + + if (!cur) return; + } + + cur = cur->prev_sibling_c; + + while (cur) + { + if (cur->first_child) + cur = cur->first_child->prev_sibling_c; + else + { + // leaf node, can't be ancestor + if (step_push(ns, cur, alloc) & once) + return; + + while (!cur->prev_sibling_c->next_sibling) + { + cur = cur->parent; + + if (!cur) return; + + if (!node_is_ancestor(cur, n)) + if (step_push(ns, cur, alloc) & once) + return; + } + + cur = cur->prev_sibling_c; + } + } + + break; + } + + case axis_ancestor: + case axis_ancestor_or_self: + { + if (axis == axis_ancestor_or_self) + if (step_push(ns, n, alloc) & once) + return; + + xml_node_struct* cur = n->parent; + + while (cur) + { + if (step_push(ns, cur, alloc) & once) + return; + + cur = cur->parent; + } + + break; + } + + case axis_self: + { + step_push(ns, n, alloc); + + break; + } + + case axis_parent: + { + if (n->parent) + step_push(ns, n->parent, alloc); + + break; + } + + default: + assert(!"Unimplemented axis"); + } + } + + template void step_fill(xpath_node_set_raw& ns, xml_attribute_struct* a, xml_node_struct* p, xpath_allocator* alloc, bool once, T v) + { + const axis_t axis = T::axis; + + switch (axis) + { + case axis_ancestor: + case axis_ancestor_or_self: + { + if (axis == axis_ancestor_or_self && _test == nodetest_type_node) // reject attributes based on principal node type test + if (step_push(ns, a, p, alloc) & once) + return; + + xml_node_struct* cur = p; + + while (cur) + { + if (step_push(ns, cur, alloc) & once) + return; + + cur = cur->parent; + } + + break; + } + + case axis_descendant_or_self: + case axis_self: + { + if (_test == nodetest_type_node) // reject attributes based on principal node type test + step_push(ns, a, p, alloc); + + break; + } + + case axis_following: + { + xml_node_struct* cur = p; + + while (cur) + { + if (cur->first_child) + cur = cur->first_child; + else + { + while (!cur->next_sibling) + { + cur = cur->parent; + + if (!cur) return; + } + + cur = cur->next_sibling; + } + + if (step_push(ns, cur, alloc) & once) + return; + } + + break; + } + + case axis_parent: + { + step_push(ns, p, alloc); + + break; + } + + case axis_preceding: + { + // preceding:: axis does not include attribute nodes and attribute ancestors (they are the same as parent's ancestors), so we can reuse node preceding + step_fill(ns, p, alloc, once, v); + break; + } + + default: + assert(!"Unimplemented axis"); + } + } + + template void step_fill(xpath_node_set_raw& ns, const xpath_node& xn, xpath_allocator* alloc, bool once, T v) + { + const axis_t axis = T::axis; + const bool axis_has_attributes = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_descendant_or_self || axis == axis_following || axis == axis_parent || axis == axis_preceding || axis == axis_self); + + if (xn.node()) + step_fill(ns, xn.node().internal_object(), alloc, once, v); + else if (axis_has_attributes && xn.attribute() && xn.parent()) + step_fill(ns, xn.attribute().internal_object(), xn.parent().internal_object(), alloc, once, v); + } + + template xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, nodeset_eval_t eval, T v) + { + const axis_t axis = T::axis; + const bool axis_reverse = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_preceding || axis == axis_preceding_sibling); + const xpath_node_set::type_t axis_type = axis_reverse ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; + + bool once = + (axis == axis_attribute && _test == nodetest_name) || + (!_right && eval_once(axis_type, eval)) || + (_right && !_right->_next && _right->_test == predicate_constant_one); + + xpath_node_set_raw ns; + ns.set_type(axis_type); + + if (_left) + { + xpath_node_set_raw s = _left->eval_node_set(c, stack, nodeset_eval_all); + + // self axis preserves the original order + if (axis == axis_self) ns.set_type(s.type()); + + for (const xpath_node* it = s.begin(); it != s.end(); ++it) + { + size_t size = ns.size(); + + // in general, all axes generate elements in a particular order, but there is no order guarantee if axis is applied to two nodes + if (axis != axis_self && size != 0) ns.set_type(xpath_node_set::type_unsorted); + + step_fill(ns, *it, stack.result, once, v); + if (_right) apply_predicates(ns, size, stack, eval); + } + } + else + { + step_fill(ns, c.n, stack.result, once, v); + if (_right) apply_predicates(ns, 0, stack, eval); + } + + // child, attribute and self axes always generate unique set of nodes + // for other axis, if the set stayed sorted, it stayed unique because the traversal algorithms do not visit the same node twice + if (axis != axis_child && axis != axis_attribute && axis != axis_self && ns.type() == xpath_node_set::type_unsorted) + ns.remove_duplicates(); + + return ns; + } + + public: + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, const char_t* value): + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + { + assert(type == ast_string_constant); + _data.string = value; + } + + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, double value): + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + { + assert(type == ast_number_constant); + _data.number = value; + } + + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_variable* value): + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + { + assert(type == ast_variable); + _data.variable = value; + } + + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_ast_node* left = 0, xpath_ast_node* right = 0): + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(left), _right(right), _next(0) + { + } + + xpath_ast_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents): + _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(static_cast(axis)), _test(static_cast(test)), _left(left), _right(0), _next(0) + { + assert(type == ast_step); + _data.nodetest = contents; + } + + xpath_ast_node(ast_type_t type, xpath_ast_node* left, xpath_ast_node* right, predicate_t test): + _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(0), _test(static_cast(test)), _left(left), _right(right), _next(0) + { + assert(type == ast_filter || type == ast_predicate); + } + + void set_next(xpath_ast_node* value) + { + _next = value; + } + + void set_right(xpath_ast_node* value) + { + _right = value; + } + + bool eval_boolean(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_op_or: + return _left->eval_boolean(c, stack) || _right->eval_boolean(c, stack); + + case ast_op_and: + return _left->eval_boolean(c, stack) && _right->eval_boolean(c, stack); + + case ast_op_equal: + return compare_eq(_left, _right, c, stack, equal_to()); + + case ast_op_not_equal: + return compare_eq(_left, _right, c, stack, not_equal_to()); + + case ast_op_less: + return compare_rel(_left, _right, c, stack, less()); + + case ast_op_greater: + return compare_rel(_right, _left, c, stack, less()); + + case ast_op_less_or_equal: + return compare_rel(_left, _right, c, stack, less_equal()); + + case ast_op_greater_or_equal: + return compare_rel(_right, _left, c, stack, less_equal()); + + case ast_func_starts_with: + { + xpath_allocator_capture cr(stack.result); + + xpath_string lr = _left->eval_string(c, stack); + xpath_string rr = _right->eval_string(c, stack); + + return starts_with(lr.c_str(), rr.c_str()); + } + + case ast_func_contains: + { + xpath_allocator_capture cr(stack.result); + + xpath_string lr = _left->eval_string(c, stack); + xpath_string rr = _right->eval_string(c, stack); + + return find_substring(lr.c_str(), rr.c_str()) != 0; + } + + case ast_func_boolean: + return _left->eval_boolean(c, stack); + + case ast_func_not: + return !_left->eval_boolean(c, stack); + + case ast_func_true: + return true; + + case ast_func_false: + return false; + + case ast_func_lang: + { + if (c.n.attribute()) return false; + + xpath_allocator_capture cr(stack.result); + + xpath_string lang = _left->eval_string(c, stack); + + for (xml_node n = c.n.node(); n; n = n.parent()) + { + xml_attribute a = n.attribute(PUGIXML_TEXT("xml:lang")); + + if (a) + { + const char_t* value = a.value(); + + // strnicmp / strncasecmp is not portable + for (const char_t* lit = lang.c_str(); *lit; ++lit) + { + if (tolower_ascii(*lit) != tolower_ascii(*value)) return false; + ++value; + } + + return *value == 0 || *value == '-'; + } + } + + return false; + } + + case ast_opt_compare_attribute: + { + const char_t* value = (_right->_type == ast_string_constant) ? _right->_data.string : _right->_data.variable->get_string(); + + xml_attribute attr = c.n.node().attribute(_left->_data.nodetest); + + return attr && strequal(attr.value(), value) && is_xpath_attribute(attr.name()); + } + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_boolean) + return _data.variable->get_boolean(); + + // fallthrough to type conversion + } + + default: + { + switch (_rettype) + { + case xpath_type_number: + return convert_number_to_boolean(eval_number(c, stack)); + + case xpath_type_string: + { + xpath_allocator_capture cr(stack.result); + + return !eval_string(c, stack).empty(); + } + + case xpath_type_node_set: + { + xpath_allocator_capture cr(stack.result); + + return !eval_node_set(c, stack, nodeset_eval_any).empty(); + } + + default: + assert(!"Wrong expression for return type boolean"); + return false; + } + } + } + } + + double eval_number(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_op_add: + return _left->eval_number(c, stack) + _right->eval_number(c, stack); + + case ast_op_subtract: + return _left->eval_number(c, stack) - _right->eval_number(c, stack); + + case ast_op_multiply: + return _left->eval_number(c, stack) * _right->eval_number(c, stack); + + case ast_op_divide: + return _left->eval_number(c, stack) / _right->eval_number(c, stack); + + case ast_op_mod: + return fmod(_left->eval_number(c, stack), _right->eval_number(c, stack)); + + case ast_op_negate: + return -_left->eval_number(c, stack); + + case ast_number_constant: + return _data.number; + + case ast_func_last: + return static_cast(c.size); + + case ast_func_position: + return static_cast(c.position); + + case ast_func_count: + { + xpath_allocator_capture cr(stack.result); + + return static_cast(_left->eval_node_set(c, stack, nodeset_eval_all).size()); + } + + case ast_func_string_length_0: + { + xpath_allocator_capture cr(stack.result); + + return static_cast(string_value(c.n, stack.result).length()); + } + + case ast_func_string_length_1: + { + xpath_allocator_capture cr(stack.result); + + return static_cast(_left->eval_string(c, stack).length()); + } + + case ast_func_number_0: + { + xpath_allocator_capture cr(stack.result); + + return convert_string_to_number(string_value(c.n, stack.result).c_str()); + } + + case ast_func_number_1: + return _left->eval_number(c, stack); + + case ast_func_sum: + { + xpath_allocator_capture cr(stack.result); + + double r = 0; + + xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_all); + + for (const xpath_node* it = ns.begin(); it != ns.end(); ++it) + { + xpath_allocator_capture cri(stack.result); + + r += convert_string_to_number(string_value(*it, stack.result).c_str()); + } + + return r; + } + + case ast_func_floor: + { + double r = _left->eval_number(c, stack); + + return r == r ? floor(r) : r; + } + + case ast_func_ceiling: + { + double r = _left->eval_number(c, stack); + + return r == r ? ceil(r) : r; + } + + case ast_func_round: + return round_nearest_nzero(_left->eval_number(c, stack)); + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_number) + return _data.variable->get_number(); + + // fallthrough to type conversion + } + + default: + { + switch (_rettype) + { + case xpath_type_boolean: + return eval_boolean(c, stack) ? 1 : 0; + + case xpath_type_string: + { + xpath_allocator_capture cr(stack.result); + + return convert_string_to_number(eval_string(c, stack).c_str()); + } + + case xpath_type_node_set: + { + xpath_allocator_capture cr(stack.result); + + return convert_string_to_number(eval_string(c, stack).c_str()); + } + + default: + assert(!"Wrong expression for return type number"); + return 0; + } + + } + } + } + + xpath_string eval_string_concat(const xpath_context& c, const xpath_stack& stack) + { + assert(_type == ast_func_concat); + + xpath_allocator_capture ct(stack.temp); + + // count the string number + size_t count = 1; + for (xpath_ast_node* nc = _right; nc; nc = nc->_next) count++; + + // gather all strings + xpath_string static_buffer[4]; + xpath_string* buffer = static_buffer; + + // allocate on-heap for large concats + if (count > sizeof(static_buffer) / sizeof(static_buffer[0])) + { + buffer = static_cast(stack.temp->allocate(count * sizeof(xpath_string))); + assert(buffer); + } + + // evaluate all strings to temporary stack + xpath_stack swapped_stack = {stack.temp, stack.result}; + + buffer[0] = _left->eval_string(c, swapped_stack); + + size_t pos = 1; + for (xpath_ast_node* n = _right; n; n = n->_next, ++pos) buffer[pos] = n->eval_string(c, swapped_stack); + assert(pos == count); + + // get total length + size_t length = 0; + for (size_t i = 0; i < count; ++i) length += buffer[i].length(); + + // create final string + char_t* result = static_cast(stack.result->allocate((length + 1) * sizeof(char_t))); + assert(result); + + char_t* ri = result; + + for (size_t j = 0; j < count; ++j) + for (const char_t* bi = buffer[j].c_str(); *bi; ++bi) + *ri++ = *bi; + + *ri = 0; + + return xpath_string::from_heap_preallocated(result, ri); + } + + xpath_string eval_string(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_string_constant: + return xpath_string::from_const(_data.string); + + case ast_func_local_name_0: + { + xpath_node na = c.n; + + return xpath_string::from_const(local_name(na)); + } + + case ast_func_local_name_1: + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first); + xpath_node na = ns.first(); + + return xpath_string::from_const(local_name(na)); + } + + case ast_func_name_0: + { + xpath_node na = c.n; + + return xpath_string::from_const(qualified_name(na)); + } + + case ast_func_name_1: + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first); + xpath_node na = ns.first(); + + return xpath_string::from_const(qualified_name(na)); + } + + case ast_func_namespace_uri_0: + { + xpath_node na = c.n; + + return xpath_string::from_const(namespace_uri(na)); + } + + case ast_func_namespace_uri_1: + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first); + xpath_node na = ns.first(); + + return xpath_string::from_const(namespace_uri(na)); + } + + case ast_func_string_0: + return string_value(c.n, stack.result); + + case ast_func_string_1: + return _left->eval_string(c, stack); + + case ast_func_concat: + return eval_string_concat(c, stack); + + case ast_func_substring_before: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + xpath_string p = _right->eval_string(c, swapped_stack); + + const char_t* pos = find_substring(s.c_str(), p.c_str()); + + return pos ? xpath_string::from_heap(s.c_str(), pos, stack.result) : xpath_string(); + } + + case ast_func_substring_after: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + xpath_string p = _right->eval_string(c, swapped_stack); + + const char_t* pos = find_substring(s.c_str(), p.c_str()); + if (!pos) return xpath_string(); + + const char_t* rbegin = pos + p.length(); + const char_t* rend = s.c_str() + s.length(); + + return s.uses_heap() ? xpath_string::from_heap(rbegin, rend, stack.result) : xpath_string::from_const(rbegin); + } + + case ast_func_substring_2: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + size_t s_length = s.length(); + + double first = round_nearest(_right->eval_number(c, stack)); + + if (is_nan(first)) return xpath_string(); // NaN + else if (first >= s_length + 1) return xpath_string(); + + size_t pos = first < 1 ? 1 : static_cast(first); + assert(1 <= pos && pos <= s_length + 1); + + const char_t* rbegin = s.c_str() + (pos - 1); + const char_t* rend = s.c_str() + s.length(); + + return s.uses_heap() ? xpath_string::from_heap(rbegin, rend, stack.result) : xpath_string::from_const(rbegin); + } + + case ast_func_substring_3: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + size_t s_length = s.length(); + + double first = round_nearest(_right->eval_number(c, stack)); + double last = first + round_nearest(_right->_next->eval_number(c, stack)); + + if (is_nan(first) || is_nan(last)) return xpath_string(); + else if (first >= s_length + 1) return xpath_string(); + else if (first >= last) return xpath_string(); + else if (last < 1) return xpath_string(); + + size_t pos = first < 1 ? 1 : static_cast(first); + size_t end = last >= s_length + 1 ? s_length + 1 : static_cast(last); + + assert(1 <= pos && pos <= end && end <= s_length + 1); + const char_t* rbegin = s.c_str() + (pos - 1); + const char_t* rend = s.c_str() + (end - 1); + + return (end == s_length + 1 && !s.uses_heap()) ? xpath_string::from_const(rbegin) : xpath_string::from_heap(rbegin, rend, stack.result); + } + + case ast_func_normalize_space_0: + { + xpath_string s = string_value(c.n, stack.result); + + char_t* begin = s.data(stack.result); + char_t* end = normalize_space(begin); + + return xpath_string::from_heap_preallocated(begin, end); + } + + case ast_func_normalize_space_1: + { + xpath_string s = _left->eval_string(c, stack); + + char_t* begin = s.data(stack.result); + char_t* end = normalize_space(begin); + + return xpath_string::from_heap_preallocated(begin, end); + } + + case ast_func_translate: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, stack); + xpath_string from = _right->eval_string(c, swapped_stack); + xpath_string to = _right->_next->eval_string(c, swapped_stack); + + char_t* begin = s.data(stack.result); + char_t* end = translate(begin, from.c_str(), to.c_str(), to.length()); + + return xpath_string::from_heap_preallocated(begin, end); + } + + case ast_opt_translate_table: + { + xpath_string s = _left->eval_string(c, stack); + + char_t* begin = s.data(stack.result); + char_t* end = translate_table(begin, _data.table); + + return xpath_string::from_heap_preallocated(begin, end); + } + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_string) + return xpath_string::from_const(_data.variable->get_string()); + + // fallthrough to type conversion + } + + default: + { + switch (_rettype) + { + case xpath_type_boolean: + return xpath_string::from_const(eval_boolean(c, stack) ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); + + case xpath_type_number: + return convert_number_to_string(eval_number(c, stack), stack.result); + + case xpath_type_node_set: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_node_set_raw ns = eval_node_set(c, swapped_stack, nodeset_eval_first); + return ns.empty() ? xpath_string() : string_value(ns.first(), stack.result); + } + + default: + assert(!"Wrong expression for return type string"); + return xpath_string(); + } + } + } + } + + xpath_node_set_raw eval_node_set(const xpath_context& c, const xpath_stack& stack, nodeset_eval_t eval) + { + switch (_type) + { + case ast_op_union: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_node_set_raw ls = _left->eval_node_set(c, swapped_stack, eval); + xpath_node_set_raw rs = _right->eval_node_set(c, stack, eval); + + // we can optimize merging two sorted sets, but this is a very rare operation, so don't bother + rs.set_type(xpath_node_set::type_unsorted); + + rs.append(ls.begin(), ls.end(), stack.result); + rs.remove_duplicates(); + + return rs; + } + + case ast_filter: + { + xpath_node_set_raw set = _left->eval_node_set(c, stack, _test == predicate_constant_one ? nodeset_eval_first : nodeset_eval_all); + + // either expression is a number or it contains position() call; sort by document order + if (_test != predicate_posinv) set.sort_do(); + + bool once = eval_once(set.type(), eval); + + apply_predicate(set, 0, stack, once); + + return set; + } + + case ast_func_id: + return xpath_node_set_raw(); + + case ast_step: + { + switch (_axis) + { + case axis_ancestor: + return step_do(c, stack, eval, axis_to_type()); + + case axis_ancestor_or_self: + return step_do(c, stack, eval, axis_to_type()); + + case axis_attribute: + return step_do(c, stack, eval, axis_to_type()); + + case axis_child: + return step_do(c, stack, eval, axis_to_type()); + + case axis_descendant: + return step_do(c, stack, eval, axis_to_type()); + + case axis_descendant_or_self: + return step_do(c, stack, eval, axis_to_type()); + + case axis_following: + return step_do(c, stack, eval, axis_to_type()); + + case axis_following_sibling: + return step_do(c, stack, eval, axis_to_type()); + + case axis_namespace: + // namespaced axis is not supported + return xpath_node_set_raw(); + + case axis_parent: + return step_do(c, stack, eval, axis_to_type()); + + case axis_preceding: + return step_do(c, stack, eval, axis_to_type()); + + case axis_preceding_sibling: + return step_do(c, stack, eval, axis_to_type()); + + case axis_self: + return step_do(c, stack, eval, axis_to_type()); + + default: + assert(!"Unknown axis"); + return xpath_node_set_raw(); + } + } + + case ast_step_root: + { + assert(!_right); // root step can't have any predicates + + xpath_node_set_raw ns; + + ns.set_type(xpath_node_set::type_sorted); + + if (c.n.node()) ns.push_back(c.n.node().root(), stack.result); + else if (c.n.attribute()) ns.push_back(c.n.parent().root(), stack.result); + + return ns; + } + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_node_set) + { + const xpath_node_set& s = _data.variable->get_node_set(); + + xpath_node_set_raw ns; + + ns.set_type(s.type()); + ns.append(s.begin(), s.end(), stack.result); + + return ns; + } + + // fallthrough to type conversion + } + + default: + assert(!"Wrong expression for return type node set"); + return xpath_node_set_raw(); + } + } + + void optimize(xpath_allocator* alloc) + { + if (_left) _left->optimize(alloc); + if (_right) _right->optimize(alloc); + if (_next) _next->optimize(alloc); + + // Rewrite [position()=expr] with [expr] + // Note that this step has to go before classification to recognize [position()=1] + if ((_type == ast_filter || _type == ast_predicate) && + _right->_type == ast_op_equal && _right->_left->_type == ast_func_position && _right->_right->_rettype == xpath_type_number) + { + _right = _right->_right; + } + + // Classify filter/predicate ops to perform various optimizations during evaluation + if (_type == ast_filter || _type == ast_predicate) + { + assert(_test == predicate_default); + + if (_right->_type == ast_number_constant && _right->_data.number == 1.0) + _test = predicate_constant_one; + else if (_right->_rettype == xpath_type_number && (_right->_type == ast_number_constant || _right->_type == ast_variable || _right->_type == ast_func_last)) + _test = predicate_constant; + else if (_right->_rettype != xpath_type_number && _right->is_posinv_expr()) + _test = predicate_posinv; + } + + // Rewrite descendant-or-self::node()/child::foo with descendant::foo + // The former is a full form of //foo, the latter is much faster since it executes the node test immediately + // Do a similar kind of rewrite for self/descendant/descendant-or-self axes + // Note that we only rewrite positionally invariant steps (//foo[1] != /descendant::foo[1]) + if (_type == ast_step && (_axis == axis_child || _axis == axis_self || _axis == axis_descendant || _axis == axis_descendant_or_self) && _left && + _left->_type == ast_step && _left->_axis == axis_descendant_or_self && _left->_test == nodetest_type_node && !_left->_right && + is_posinv_step()) + { + if (_axis == axis_child || _axis == axis_descendant) + _axis = axis_descendant; + else + _axis = axis_descendant_or_self; + + _left = _left->_left; + } + + // Use optimized lookup table implementation for translate() with constant arguments + if (_type == ast_func_translate && _right->_type == ast_string_constant && _right->_next->_type == ast_string_constant) + { + unsigned char* table = translate_table_generate(alloc, _right->_data.string, _right->_next->_data.string); + + if (table) + { + _type = ast_opt_translate_table; + _data.table = table; + } + } + + // Use optimized path for @attr = 'value' or @attr = $value + if (_type == ast_op_equal && + _left->_type == ast_step && _left->_axis == axis_attribute && _left->_test == nodetest_name && !_left->_left && !_left->_right && + (_right->_type == ast_string_constant || (_right->_type == ast_variable && _right->_rettype == xpath_type_string))) + { + _type = ast_opt_compare_attribute; + } + } + + bool is_posinv_expr() const + { + switch (_type) + { + case ast_func_position: + case ast_func_last: + return false; + + case ast_string_constant: + case ast_number_constant: + case ast_variable: + return true; + + case ast_step: + case ast_step_root: + return true; + + case ast_predicate: + case ast_filter: + return true; + + default: + if (_left && !_left->is_posinv_expr()) return false; + + for (xpath_ast_node* n = _right; n; n = n->_next) + if (!n->is_posinv_expr()) return false; + + return true; + } + } + + bool is_posinv_step() const + { + assert(_type == ast_step); + + for (xpath_ast_node* n = _right; n; n = n->_next) + { + assert(n->_type == ast_predicate); + + if (n->_test != predicate_posinv) + return false; + } + + return true; + } + + xpath_value_type rettype() const + { + return static_cast(_rettype); + } + }; + + struct xpath_parser + { + xpath_allocator* _alloc; + xpath_lexer _lexer; + + const char_t* _query; + xpath_variable_set* _variables; + + xpath_parse_result* _result; + + char_t _scratch[32]; + + #ifdef PUGIXML_NO_EXCEPTIONS + jmp_buf _error_handler; + #endif + + void throw_error(const char* message) + { + _result->error = message; + _result->offset = _lexer.current_pos() - _query; + + #ifdef PUGIXML_NO_EXCEPTIONS + longjmp(_error_handler, 1); + #else + throw xpath_exception(*_result); + #endif + } + + void throw_error_oom() + { + #ifdef PUGIXML_NO_EXCEPTIONS + throw_error("Out of memory"); + #else + throw std::bad_alloc(); + #endif + } + + void* alloc_node() + { + void* result = _alloc->allocate_nothrow(sizeof(xpath_ast_node)); + + if (!result) throw_error_oom(); + + return result; + } + + const char_t* alloc_string(const xpath_lexer_string& value) + { + if (value.begin) + { + size_t length = static_cast(value.end - value.begin); + + char_t* c = static_cast(_alloc->allocate_nothrow((length + 1) * sizeof(char_t))); + if (!c) throw_error_oom(); + assert(c); // workaround for clang static analysis + + memcpy(c, value.begin, length * sizeof(char_t)); + c[length] = 0; + + return c; + } + else return 0; + } + + xpath_ast_node* parse_function_helper(ast_type_t type0, ast_type_t type1, size_t argc, xpath_ast_node* args[2]) + { + assert(argc <= 1); + + if (argc == 1 && args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); + + return new (alloc_node()) xpath_ast_node(argc == 0 ? type0 : type1, xpath_type_string, args[0]); + } + + xpath_ast_node* parse_function(const xpath_lexer_string& name, size_t argc, xpath_ast_node* args[2]) + { + switch (name.begin[0]) + { + case 'b': + if (name == PUGIXML_TEXT("boolean") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_boolean, xpath_type_boolean, args[0]); + + break; + + case 'c': + if (name == PUGIXML_TEXT("count") && argc == 1) + { + if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); + return new (alloc_node()) xpath_ast_node(ast_func_count, xpath_type_number, args[0]); + } + else if (name == PUGIXML_TEXT("contains") && argc == 2) + return new (alloc_node()) xpath_ast_node(ast_func_contains, xpath_type_boolean, args[0], args[1]); + else if (name == PUGIXML_TEXT("concat") && argc >= 2) + return new (alloc_node()) xpath_ast_node(ast_func_concat, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("ceiling") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_ceiling, xpath_type_number, args[0]); + + break; + + case 'f': + if (name == PUGIXML_TEXT("false") && argc == 0) + return new (alloc_node()) xpath_ast_node(ast_func_false, xpath_type_boolean); + else if (name == PUGIXML_TEXT("floor") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_floor, xpath_type_number, args[0]); + + break; + + case 'i': + if (name == PUGIXML_TEXT("id") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_id, xpath_type_node_set, args[0]); + + break; + + case 'l': + if (name == PUGIXML_TEXT("last") && argc == 0) + return new (alloc_node()) xpath_ast_node(ast_func_last, xpath_type_number); + else if (name == PUGIXML_TEXT("lang") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_lang, xpath_type_boolean, args[0]); + else if (name == PUGIXML_TEXT("local-name") && argc <= 1) + return parse_function_helper(ast_func_local_name_0, ast_func_local_name_1, argc, args); + + break; + + case 'n': + if (name == PUGIXML_TEXT("name") && argc <= 1) + return parse_function_helper(ast_func_name_0, ast_func_name_1, argc, args); + else if (name == PUGIXML_TEXT("namespace-uri") && argc <= 1) + return parse_function_helper(ast_func_namespace_uri_0, ast_func_namespace_uri_1, argc, args); + else if (name == PUGIXML_TEXT("normalize-space") && argc <= 1) + return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_normalize_space_0 : ast_func_normalize_space_1, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("not") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_not, xpath_type_boolean, args[0]); + else if (name == PUGIXML_TEXT("number") && argc <= 1) + return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_number_0 : ast_func_number_1, xpath_type_number, args[0]); + + break; + + case 'p': + if (name == PUGIXML_TEXT("position") && argc == 0) + return new (alloc_node()) xpath_ast_node(ast_func_position, xpath_type_number); + + break; + + case 'r': + if (name == PUGIXML_TEXT("round") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_round, xpath_type_number, args[0]); + + break; + + case 's': + if (name == PUGIXML_TEXT("string") && argc <= 1) + return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_0 : ast_func_string_1, xpath_type_string, args[0]); + else if (name == PUGIXML_TEXT("string-length") && argc <= 1) + return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_length_0 : ast_func_string_length_1, xpath_type_number, args[0]); + else if (name == PUGIXML_TEXT("starts-with") && argc == 2) + return new (alloc_node()) xpath_ast_node(ast_func_starts_with, xpath_type_boolean, args[0], args[1]); + else if (name == PUGIXML_TEXT("substring-before") && argc == 2) + return new (alloc_node()) xpath_ast_node(ast_func_substring_before, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("substring-after") && argc == 2) + return new (alloc_node()) xpath_ast_node(ast_func_substring_after, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("substring") && (argc == 2 || argc == 3)) + return new (alloc_node()) xpath_ast_node(argc == 2 ? ast_func_substring_2 : ast_func_substring_3, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("sum") && argc == 1) + { + if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); + return new (alloc_node()) xpath_ast_node(ast_func_sum, xpath_type_number, args[0]); + } + + break; + + case 't': + if (name == PUGIXML_TEXT("translate") && argc == 3) + return new (alloc_node()) xpath_ast_node(ast_func_translate, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("true") && argc == 0) + return new (alloc_node()) xpath_ast_node(ast_func_true, xpath_type_boolean); + + break; + + default: + break; + } + + throw_error("Unrecognized function or wrong parameter count"); + + return 0; + } + + axis_t parse_axis_name(const xpath_lexer_string& name, bool& specified) + { + specified = true; + + switch (name.begin[0]) + { + case 'a': + if (name == PUGIXML_TEXT("ancestor")) + return axis_ancestor; + else if (name == PUGIXML_TEXT("ancestor-or-self")) + return axis_ancestor_or_self; + else if (name == PUGIXML_TEXT("attribute")) + return axis_attribute; + + break; + + case 'c': + if (name == PUGIXML_TEXT("child")) + return axis_child; + + break; + + case 'd': + if (name == PUGIXML_TEXT("descendant")) + return axis_descendant; + else if (name == PUGIXML_TEXT("descendant-or-self")) + return axis_descendant_or_self; + + break; + + case 'f': + if (name == PUGIXML_TEXT("following")) + return axis_following; + else if (name == PUGIXML_TEXT("following-sibling")) + return axis_following_sibling; + + break; + + case 'n': + if (name == PUGIXML_TEXT("namespace")) + return axis_namespace; + + break; + + case 'p': + if (name == PUGIXML_TEXT("parent")) + return axis_parent; + else if (name == PUGIXML_TEXT("preceding")) + return axis_preceding; + else if (name == PUGIXML_TEXT("preceding-sibling")) + return axis_preceding_sibling; + + break; + + case 's': + if (name == PUGIXML_TEXT("self")) + return axis_self; + + break; + + default: + break; + } + + specified = false; + return axis_child; + } + + nodetest_t parse_node_test_type(const xpath_lexer_string& name) + { + switch (name.begin[0]) + { + case 'c': + if (name == PUGIXML_TEXT("comment")) + return nodetest_type_comment; + + break; + + case 'n': + if (name == PUGIXML_TEXT("node")) + return nodetest_type_node; + + break; + + case 'p': + if (name == PUGIXML_TEXT("processing-instruction")) + return nodetest_type_pi; + + break; + + case 't': + if (name == PUGIXML_TEXT("text")) + return nodetest_type_text; + + break; + + default: + break; + } + + return nodetest_none; + } + + // PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall + xpath_ast_node* parse_primary_expression() + { + switch (_lexer.current()) + { + case lex_var_ref: + { + xpath_lexer_string name = _lexer.contents(); + + if (!_variables) + throw_error("Unknown variable: variable set is not provided"); + + xpath_variable* var = get_variable_scratch(_scratch, _variables, name.begin, name.end); + + if (!var) + throw_error("Unknown variable: variable set does not contain the given name"); + + _lexer.next(); + + return new (alloc_node()) xpath_ast_node(ast_variable, var->type(), var); + } + + case lex_open_brace: + { + _lexer.next(); + + xpath_ast_node* n = parse_expression(); + + if (_lexer.current() != lex_close_brace) + throw_error("Unmatched braces"); + + _lexer.next(); + + return n; + } + + case lex_quoted_string: + { + const char_t* value = alloc_string(_lexer.contents()); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_string_constant, xpath_type_string, value); + _lexer.next(); + + return n; + } + + case lex_number: + { + double value = 0; + + if (!convert_string_to_number_scratch(_scratch, _lexer.contents().begin, _lexer.contents().end, &value)) + throw_error_oom(); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_number_constant, xpath_type_number, value); + _lexer.next(); + + return n; + } + + case lex_string: + { + xpath_ast_node* args[2] = {0}; + size_t argc = 0; + + xpath_lexer_string function = _lexer.contents(); + _lexer.next(); + + xpath_ast_node* last_arg = 0; + + if (_lexer.current() != lex_open_brace) + throw_error("Unrecognized function call"); + _lexer.next(); + + if (_lexer.current() != lex_close_brace) + args[argc++] = parse_expression(); + + while (_lexer.current() != lex_close_brace) + { + if (_lexer.current() != lex_comma) + throw_error("No comma between function arguments"); + _lexer.next(); + + xpath_ast_node* n = parse_expression(); + + if (argc < 2) args[argc] = n; + else last_arg->set_next(n); + + argc++; + last_arg = n; + } + + _lexer.next(); + + return parse_function(function, argc, args); + } + + default: + throw_error("Unrecognizable primary expression"); + + return 0; + } + } + + // FilterExpr ::= PrimaryExpr | FilterExpr Predicate + // Predicate ::= '[' PredicateExpr ']' + // PredicateExpr ::= Expr + xpath_ast_node* parse_filter_expression() + { + xpath_ast_node* n = parse_primary_expression(); + + while (_lexer.current() == lex_open_square_brace) + { + _lexer.next(); + + xpath_ast_node* expr = parse_expression(); + + if (n->rettype() != xpath_type_node_set) throw_error("Predicate has to be applied to node set"); + + n = new (alloc_node()) xpath_ast_node(ast_filter, n, expr, predicate_default); + + if (_lexer.current() != lex_close_square_brace) + throw_error("Unmatched square brace"); + + _lexer.next(); + } + + return n; + } + + // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep + // AxisSpecifier ::= AxisName '::' | '@'? + // NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')' + // NameTest ::= '*' | NCName ':' '*' | QName + // AbbreviatedStep ::= '.' | '..' + xpath_ast_node* parse_step(xpath_ast_node* set) + { + if (set && set->rettype() != xpath_type_node_set) + throw_error("Step has to be applied to node set"); + + bool axis_specified = false; + axis_t axis = axis_child; // implied child axis + + if (_lexer.current() == lex_axis_attribute) + { + axis = axis_attribute; + axis_specified = true; + + _lexer.next(); + } + else if (_lexer.current() == lex_dot) + { + _lexer.next(); + + return new (alloc_node()) xpath_ast_node(ast_step, set, axis_self, nodetest_type_node, 0); + } + else if (_lexer.current() == lex_double_dot) + { + _lexer.next(); + + return new (alloc_node()) xpath_ast_node(ast_step, set, axis_parent, nodetest_type_node, 0); + } + + nodetest_t nt_type = nodetest_none; + xpath_lexer_string nt_name; + + if (_lexer.current() == lex_string) + { + // node name test + nt_name = _lexer.contents(); + _lexer.next(); + + // was it an axis name? + if (_lexer.current() == lex_double_colon) + { + // parse axis name + if (axis_specified) throw_error("Two axis specifiers in one step"); + + axis = parse_axis_name(nt_name, axis_specified); + + if (!axis_specified) throw_error("Unknown axis"); + + // read actual node test + _lexer.next(); + + if (_lexer.current() == lex_multiply) + { + nt_type = nodetest_all; + nt_name = xpath_lexer_string(); + _lexer.next(); + } + else if (_lexer.current() == lex_string) + { + nt_name = _lexer.contents(); + _lexer.next(); + } + else throw_error("Unrecognized node test"); + } + + if (nt_type == nodetest_none) + { + // node type test or processing-instruction + if (_lexer.current() == lex_open_brace) + { + _lexer.next(); + + if (_lexer.current() == lex_close_brace) + { + _lexer.next(); + + nt_type = parse_node_test_type(nt_name); + + if (nt_type == nodetest_none) throw_error("Unrecognized node type"); + + nt_name = xpath_lexer_string(); + } + else if (nt_name == PUGIXML_TEXT("processing-instruction")) + { + if (_lexer.current() != lex_quoted_string) + throw_error("Only literals are allowed as arguments to processing-instruction()"); + + nt_type = nodetest_pi; + nt_name = _lexer.contents(); + _lexer.next(); + + if (_lexer.current() != lex_close_brace) + throw_error("Unmatched brace near processing-instruction()"); + _lexer.next(); + } + else + throw_error("Unmatched brace near node type test"); + + } + // QName or NCName:* + else + { + if (nt_name.end - nt_name.begin > 2 && nt_name.end[-2] == ':' && nt_name.end[-1] == '*') // NCName:* + { + nt_name.end--; // erase * + + nt_type = nodetest_all_in_namespace; + } + else nt_type = nodetest_name; + } + } + } + else if (_lexer.current() == lex_multiply) + { + nt_type = nodetest_all; + _lexer.next(); + } + else throw_error("Unrecognized node test"); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step, set, axis, nt_type, alloc_string(nt_name)); + + xpath_ast_node* last = 0; + + while (_lexer.current() == lex_open_square_brace) + { + _lexer.next(); + + xpath_ast_node* expr = parse_expression(); + + xpath_ast_node* pred = new (alloc_node()) xpath_ast_node(ast_predicate, 0, expr, predicate_default); + + if (_lexer.current() != lex_close_square_brace) + throw_error("Unmatched square brace"); + _lexer.next(); + + if (last) last->set_next(pred); + else n->set_right(pred); + + last = pred; + } + + return n; + } + + // RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step + xpath_ast_node* parse_relative_location_path(xpath_ast_node* set) + { + xpath_ast_node* n = parse_step(set); + + while (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) + { + lexeme_t l = _lexer.current(); + _lexer.next(); + + if (l == lex_double_slash) + n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + + n = parse_step(n); + } + + return n; + } + + // LocationPath ::= RelativeLocationPath | AbsoluteLocationPath + // AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath + xpath_ast_node* parse_location_path() + { + if (_lexer.current() == lex_slash) + { + _lexer.next(); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); + + // relative location path can start from axis_attribute, dot, double_dot, multiply and string lexemes; any other lexeme means standalone root path + lexeme_t l = _lexer.current(); + + if (l == lex_string || l == lex_axis_attribute || l == lex_dot || l == lex_double_dot || l == lex_multiply) + return parse_relative_location_path(n); + else + return n; + } + else if (_lexer.current() == lex_double_slash) + { + _lexer.next(); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); + n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + + return parse_relative_location_path(n); + } + + // else clause moved outside of if because of bogus warning 'control may reach end of non-void function being inlined' in gcc 4.0.1 + return parse_relative_location_path(0); + } + + // PathExpr ::= LocationPath + // | FilterExpr + // | FilterExpr '/' RelativeLocationPath + // | FilterExpr '//' RelativeLocationPath + // UnionExpr ::= PathExpr | UnionExpr '|' PathExpr + // UnaryExpr ::= UnionExpr | '-' UnaryExpr + xpath_ast_node* parse_path_or_unary_expression() + { + // Clarification. + // PathExpr begins with either LocationPath or FilterExpr. + // FilterExpr begins with PrimaryExpr + // PrimaryExpr begins with '$' in case of it being a variable reference, + // '(' in case of it being an expression, string literal, number constant or + // function call. + + if (_lexer.current() == lex_var_ref || _lexer.current() == lex_open_brace || + _lexer.current() == lex_quoted_string || _lexer.current() == lex_number || + _lexer.current() == lex_string) + { + if (_lexer.current() == lex_string) + { + // This is either a function call, or not - if not, we shall proceed with location path + const char_t* state = _lexer.state(); + + while (PUGI__IS_CHARTYPE(*state, ct_space)) ++state; + + if (*state != '(') return parse_location_path(); + + // This looks like a function call; however this still can be a node-test. Check it. + if (parse_node_test_type(_lexer.contents()) != nodetest_none) return parse_location_path(); + } + + xpath_ast_node* n = parse_filter_expression(); + + if (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) + { + lexeme_t l = _lexer.current(); + _lexer.next(); + + if (l == lex_double_slash) + { + if (n->rettype() != xpath_type_node_set) throw_error("Step has to be applied to node set"); + + n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + } + + // select from location path + return parse_relative_location_path(n); + } + + return n; + } + else if (_lexer.current() == lex_minus) + { + _lexer.next(); + + // precedence 7+ - only parses union expressions + xpath_ast_node* expr = parse_expression_rec(parse_path_or_unary_expression(), 7); + + return new (alloc_node()) xpath_ast_node(ast_op_negate, xpath_type_number, expr); + } + else + return parse_location_path(); + } + + struct binary_op_t + { + ast_type_t asttype; + xpath_value_type rettype; + int precedence; + + binary_op_t(): asttype(ast_unknown), rettype(xpath_type_none), precedence(0) + { + } + + binary_op_t(ast_type_t asttype_, xpath_value_type rettype_, int precedence_): asttype(asttype_), rettype(rettype_), precedence(precedence_) + { + } + + static binary_op_t parse(xpath_lexer& lexer) + { + switch (lexer.current()) + { + case lex_string: + if (lexer.contents() == PUGIXML_TEXT("or")) + return binary_op_t(ast_op_or, xpath_type_boolean, 1); + else if (lexer.contents() == PUGIXML_TEXT("and")) + return binary_op_t(ast_op_and, xpath_type_boolean, 2); + else if (lexer.contents() == PUGIXML_TEXT("div")) + return binary_op_t(ast_op_divide, xpath_type_number, 6); + else if (lexer.contents() == PUGIXML_TEXT("mod")) + return binary_op_t(ast_op_mod, xpath_type_number, 6); + else + return binary_op_t(); + + case lex_equal: + return binary_op_t(ast_op_equal, xpath_type_boolean, 3); + + case lex_not_equal: + return binary_op_t(ast_op_not_equal, xpath_type_boolean, 3); + + case lex_less: + return binary_op_t(ast_op_less, xpath_type_boolean, 4); + + case lex_greater: + return binary_op_t(ast_op_greater, xpath_type_boolean, 4); + + case lex_less_or_equal: + return binary_op_t(ast_op_less_or_equal, xpath_type_boolean, 4); + + case lex_greater_or_equal: + return binary_op_t(ast_op_greater_or_equal, xpath_type_boolean, 4); + + case lex_plus: + return binary_op_t(ast_op_add, xpath_type_number, 5); + + case lex_minus: + return binary_op_t(ast_op_subtract, xpath_type_number, 5); + + case lex_multiply: + return binary_op_t(ast_op_multiply, xpath_type_number, 6); + + case lex_union: + return binary_op_t(ast_op_union, xpath_type_node_set, 7); + + default: + return binary_op_t(); + } + } + }; + + xpath_ast_node* parse_expression_rec(xpath_ast_node* lhs, int limit) + { + binary_op_t op = binary_op_t::parse(_lexer); + + while (op.asttype != ast_unknown && op.precedence >= limit) + { + _lexer.next(); + + xpath_ast_node* rhs = parse_path_or_unary_expression(); + + binary_op_t nextop = binary_op_t::parse(_lexer); + + while (nextop.asttype != ast_unknown && nextop.precedence > op.precedence) + { + rhs = parse_expression_rec(rhs, nextop.precedence); + + nextop = binary_op_t::parse(_lexer); + } + + if (op.asttype == ast_op_union && (lhs->rettype() != xpath_type_node_set || rhs->rettype() != xpath_type_node_set)) + throw_error("Union operator has to be applied to node sets"); + + lhs = new (alloc_node()) xpath_ast_node(op.asttype, op.rettype, lhs, rhs); + + op = binary_op_t::parse(_lexer); + } + + return lhs; + } + + // Expr ::= OrExpr + // OrExpr ::= AndExpr | OrExpr 'or' AndExpr + // AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr + // EqualityExpr ::= RelationalExpr + // | EqualityExpr '=' RelationalExpr + // | EqualityExpr '!=' RelationalExpr + // RelationalExpr ::= AdditiveExpr + // | RelationalExpr '<' AdditiveExpr + // | RelationalExpr '>' AdditiveExpr + // | RelationalExpr '<=' AdditiveExpr + // | RelationalExpr '>=' AdditiveExpr + // AdditiveExpr ::= MultiplicativeExpr + // | AdditiveExpr '+' MultiplicativeExpr + // | AdditiveExpr '-' MultiplicativeExpr + // MultiplicativeExpr ::= UnaryExpr + // | MultiplicativeExpr '*' UnaryExpr + // | MultiplicativeExpr 'div' UnaryExpr + // | MultiplicativeExpr 'mod' UnaryExpr + xpath_ast_node* parse_expression() + { + return parse_expression_rec(parse_path_or_unary_expression(), 0); + } + + xpath_parser(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result): _alloc(alloc), _lexer(query), _query(query), _variables(variables), _result(result) + { + } + + xpath_ast_node* parse() + { + xpath_ast_node* result = parse_expression(); + + if (_lexer.current() != lex_eof) + { + // there are still unparsed tokens left, error + throw_error("Incorrect query"); + } + + return result; + } + + static xpath_ast_node* parse(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result) + { + xpath_parser parser(query, variables, alloc, result); + + #ifdef PUGIXML_NO_EXCEPTIONS + int error = setjmp(parser._error_handler); + + return (error == 0) ? parser.parse() : 0; + #else + return parser.parse(); + #endif + } + }; + + struct xpath_query_impl + { + static xpath_query_impl* create() + { + void* memory = xml_memory::allocate(sizeof(xpath_query_impl)); + + return new (memory) xpath_query_impl(); + } + + static void destroy(void* ptr) + { + if (!ptr) return; + + // free all allocated pages + static_cast(ptr)->alloc.release(); + + // free allocator memory (with the first page) + xml_memory::deallocate(ptr); + } + + xpath_query_impl(): root(0), alloc(&block) + { + block.next = 0; + block.capacity = sizeof(block.data); + } + + xpath_ast_node* root; + xpath_allocator alloc; + xpath_memory_block block; + }; + + PUGI__FN xpath_string evaluate_string_impl(xpath_query_impl* impl, const xpath_node& n, xpath_stack_data& sd) + { + if (!impl) return xpath_string(); + + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) return xpath_string(); + #endif + + xpath_context c(n, 1, 1); + + return impl->root->eval_string(c, sd.stack); + } + + PUGI__FN impl::xpath_ast_node* evaluate_node_set_prepare(xpath_query_impl* impl) + { + if (!impl) return 0; + + if (impl->root->rettype() != xpath_type_node_set) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return 0; + #else + xpath_parse_result res; + res.error = "Expression does not evaluate to node set"; + + throw xpath_exception(res); + #endif + } + + return impl->root; + } +PUGI__NS_END + +namespace pugi +{ +#ifndef PUGIXML_NO_EXCEPTIONS + PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_) + { + assert(_result.error); + } + + PUGI__FN const char* xpath_exception::what() const throw() + { + return _result.error; + } + + PUGI__FN const xpath_parse_result& xpath_exception::result() const + { + return _result; + } +#endif + + PUGI__FN xpath_node::xpath_node() + { + } + + PUGI__FN xpath_node::xpath_node(const xml_node& node_): _node(node_) + { + } + + PUGI__FN xpath_node::xpath_node(const xml_attribute& attribute_, const xml_node& parent_): _node(attribute_ ? parent_ : xml_node()), _attribute(attribute_) + { + } + + PUGI__FN xml_node xpath_node::node() const + { + return _attribute ? xml_node() : _node; + } + + PUGI__FN xml_attribute xpath_node::attribute() const + { + return _attribute; + } + + PUGI__FN xml_node xpath_node::parent() const + { + return _attribute ? _node : _node.parent(); + } + + PUGI__FN static void unspecified_bool_xpath_node(xpath_node***) + { + } + + PUGI__FN xpath_node::operator xpath_node::unspecified_bool_type() const + { + return (_node || _attribute) ? unspecified_bool_xpath_node : 0; + } + + PUGI__FN bool xpath_node::operator!() const + { + return !(_node || _attribute); + } + + PUGI__FN bool xpath_node::operator==(const xpath_node& n) const + { + return _node == n._node && _attribute == n._attribute; + } + + PUGI__FN bool xpath_node::operator!=(const xpath_node& n) const + { + return _node != n._node || _attribute != n._attribute; + } + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xpath_node& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xpath_node& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN void xpath_node_set::_assign(const_iterator begin_, const_iterator end_) + { + assert(begin_ <= end_); + + size_t size_ = static_cast(end_ - begin_); + + if (size_ <= 1) + { + // deallocate old buffer + if (_begin != &_storage) impl::xml_memory::deallocate(_begin); + + // use internal buffer + if (begin_ != end_) _storage = *begin_; + + _begin = &_storage; + _end = &_storage + size_; + } + else + { + // make heap copy + xpath_node* storage = static_cast(impl::xml_memory::allocate(size_ * sizeof(xpath_node))); + + if (!storage) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return; + #else + throw std::bad_alloc(); + #endif + } + + memcpy(storage, begin_, size_ * sizeof(xpath_node)); + + // deallocate old buffer + if (_begin != &_storage) impl::xml_memory::deallocate(_begin); + + // finalize + _begin = storage; + _end = storage + size_; + } + } + + PUGI__FN xpath_node_set::xpath_node_set(): _type(type_unsorted), _begin(&_storage), _end(&_storage) + { + } + + PUGI__FN xpath_node_set::xpath_node_set(const_iterator begin_, const_iterator end_, type_t type_): _type(type_), _begin(&_storage), _end(&_storage) + { + _assign(begin_, end_); + } + + PUGI__FN xpath_node_set::~xpath_node_set() + { + if (_begin != &_storage) impl::xml_memory::deallocate(_begin); + } + + PUGI__FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(ns._type), _begin(&_storage), _end(&_storage) + { + _assign(ns._begin, ns._end); + } + + PUGI__FN xpath_node_set& xpath_node_set::operator=(const xpath_node_set& ns) + { + if (this == &ns) return *this; + + _type = ns._type; + _assign(ns._begin, ns._end); + + return *this; + } + + PUGI__FN xpath_node_set::type_t xpath_node_set::type() const + { + return _type; + } + + PUGI__FN size_t xpath_node_set::size() const + { + return _end - _begin; + } + + PUGI__FN bool xpath_node_set::empty() const + { + return _begin == _end; + } + + PUGI__FN const xpath_node& xpath_node_set::operator[](size_t index) const + { + assert(index < size()); + return _begin[index]; + } + + PUGI__FN xpath_node_set::const_iterator xpath_node_set::begin() const + { + return _begin; + } + + PUGI__FN xpath_node_set::const_iterator xpath_node_set::end() const + { + return _end; + } + + PUGI__FN void xpath_node_set::sort(bool reverse) + { + _type = impl::xpath_sort(_begin, _end, _type, reverse); + } + + PUGI__FN xpath_node xpath_node_set::first() const + { + return impl::xpath_first(_begin, _end, _type); + } + + PUGI__FN xpath_parse_result::xpath_parse_result(): error("Internal error"), offset(0) + { + } + + PUGI__FN xpath_parse_result::operator bool() const + { + return error == 0; + } + + PUGI__FN const char* xpath_parse_result::description() const + { + return error ? error : "No error"; + } + + PUGI__FN xpath_variable::xpath_variable(): _type(xpath_type_none), _next(0) + { + } + + PUGI__FN const char_t* xpath_variable::name() const + { + switch (_type) + { + case xpath_type_node_set: + return static_cast(this)->name; + + case xpath_type_number: + return static_cast(this)->name; + + case xpath_type_string: + return static_cast(this)->name; + + case xpath_type_boolean: + return static_cast(this)->name; + + default: + assert(!"Invalid variable type"); + return 0; + } + } + + PUGI__FN xpath_value_type xpath_variable::type() const + { + return _type; + } + + PUGI__FN bool xpath_variable::get_boolean() const + { + return (_type == xpath_type_boolean) ? static_cast(this)->value : false; + } + + PUGI__FN double xpath_variable::get_number() const + { + return (_type == xpath_type_number) ? static_cast(this)->value : impl::gen_nan(); + } + + PUGI__FN const char_t* xpath_variable::get_string() const + { + const char_t* value = (_type == xpath_type_string) ? static_cast(this)->value : 0; + return value ? value : PUGIXML_TEXT(""); + } + + PUGI__FN const xpath_node_set& xpath_variable::get_node_set() const + { + return (_type == xpath_type_node_set) ? static_cast(this)->value : impl::dummy_node_set; + } + + PUGI__FN bool xpath_variable::set(bool value) + { + if (_type != xpath_type_boolean) return false; + + static_cast(this)->value = value; + return true; + } + + PUGI__FN bool xpath_variable::set(double value) + { + if (_type != xpath_type_number) return false; + + static_cast(this)->value = value; + return true; + } + + PUGI__FN bool xpath_variable::set(const char_t* value) + { + if (_type != xpath_type_string) return false; + + impl::xpath_variable_string* var = static_cast(this); + + // duplicate string + size_t size = (impl::strlength(value) + 1) * sizeof(char_t); + + char_t* copy = static_cast(impl::xml_memory::allocate(size)); + if (!copy) return false; + + memcpy(copy, value, size); + + // replace old string + if (var->value) impl::xml_memory::deallocate(var->value); + var->value = copy; + + return true; + } + + PUGI__FN bool xpath_variable::set(const xpath_node_set& value) + { + if (_type != xpath_type_node_set) return false; + + static_cast(this)->value = value; + return true; + } + + PUGI__FN xpath_variable_set::xpath_variable_set() + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) _data[i] = 0; + } + + PUGI__FN xpath_variable_set::~xpath_variable_set() + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) + { + xpath_variable* var = _data[i]; + + while (var) + { + xpath_variable* next = var->_next; + + impl::delete_xpath_variable(var->_type, var); + + var = next; + } + } + } + + PUGI__FN xpath_variable* xpath_variable_set::find(const char_t* name) const + { + const size_t hash_size = sizeof(_data) / sizeof(_data[0]); + size_t hash = impl::hash_string(name) % hash_size; + + // look for existing variable + for (xpath_variable* var = _data[hash]; var; var = var->_next) + if (impl::strequal(var->name(), name)) + return var; + + return 0; + } + + PUGI__FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type) + { + const size_t hash_size = sizeof(_data) / sizeof(_data[0]); + size_t hash = impl::hash_string(name) % hash_size; + + // look for existing variable + for (xpath_variable* var = _data[hash]; var; var = var->_next) + if (impl::strequal(var->name(), name)) + return var->type() == type ? var : 0; + + // add new variable + xpath_variable* result = impl::new_xpath_variable(type, name); + + if (result) + { + result->_type = type; + result->_next = _data[hash]; + + _data[hash] = result; + } + + return result; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, bool value) + { + xpath_variable* var = add(name, xpath_type_boolean); + return var ? var->set(value) : false; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, double value) + { + xpath_variable* var = add(name, xpath_type_number); + return var ? var->set(value) : false; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, const char_t* value) + { + xpath_variable* var = add(name, xpath_type_string); + return var ? var->set(value) : false; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value) + { + xpath_variable* var = add(name, xpath_type_node_set); + return var ? var->set(value) : false; + } + + PUGI__FN xpath_variable* xpath_variable_set::get(const char_t* name) + { + return find(name); + } + + PUGI__FN const xpath_variable* xpath_variable_set::get(const char_t* name) const + { + return find(name); + } + + PUGI__FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables): _impl(0) + { + impl::xpath_query_impl* qimpl = impl::xpath_query_impl::create(); + + if (!qimpl) + { + #ifdef PUGIXML_NO_EXCEPTIONS + _result.error = "Out of memory"; + #else + throw std::bad_alloc(); + #endif + } + else + { + impl::buffer_holder impl_holder(qimpl, impl::xpath_query_impl::destroy); + + qimpl->root = impl::xpath_parser::parse(query, variables, &qimpl->alloc, &_result); + + if (qimpl->root) + { + qimpl->root->optimize(&qimpl->alloc); + + _impl = static_cast(impl_holder.release()); + _result.error = 0; + } + } + } + + PUGI__FN xpath_query::~xpath_query() + { + impl::xpath_query_impl::destroy(_impl); + } + + PUGI__FN xpath_value_type xpath_query::return_type() const + { + if (!_impl) return xpath_type_none; + + return static_cast(_impl)->root->rettype(); + } + + PUGI__FN bool xpath_query::evaluate_boolean(const xpath_node& n) const + { + if (!_impl) return false; + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) return false; + #endif + + return static_cast(_impl)->root->eval_boolean(c, sd.stack); + } + + PUGI__FN double xpath_query::evaluate_number(const xpath_node& n) const + { + if (!_impl) return impl::gen_nan(); + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) return impl::gen_nan(); + #endif + + return static_cast(_impl)->root->eval_number(c, sd.stack); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN string_t xpath_query::evaluate_string(const xpath_node& n) const + { + impl::xpath_stack_data sd; + + impl::xpath_string r = impl::evaluate_string_impl(static_cast(_impl), n, sd); + + return string_t(r.c_str(), r.length()); + } +#endif + + PUGI__FN size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const + { + impl::xpath_stack_data sd; + + impl::xpath_string r = impl::evaluate_string_impl(static_cast(_impl), n, sd); + + size_t full_size = r.length() + 1; + + if (capacity > 0) + { + size_t size = (full_size < capacity) ? full_size : capacity; + assert(size > 0); + + memcpy(buffer, r.c_str(), (size - 1) * sizeof(char_t)); + buffer[size - 1] = 0; + } + + return full_size; + } + + PUGI__FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const + { + impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast(_impl)); + if (!root) return xpath_node_set(); + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) return xpath_node_set(); + #endif + + impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack, impl::nodeset_eval_all); + + return xpath_node_set(r.begin(), r.end(), r.type()); + } + + PUGI__FN xpath_node xpath_query::evaluate_node(const xpath_node& n) const + { + impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast(_impl)); + if (!root) return xpath_node(); + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) return xpath_node(); + #endif + + impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack, impl::nodeset_eval_first); + + return r.first(); + } + + PUGI__FN const xpath_parse_result& xpath_query::result() const + { + return _result; + } + + PUGI__FN static void unspecified_bool_xpath_query(xpath_query***) + { + } + + PUGI__FN xpath_query::operator xpath_query::unspecified_bool_type() const + { + return _impl ? unspecified_bool_xpath_query : 0; + } + + PUGI__FN bool xpath_query::operator!() const + { + return !_impl; + } + + PUGI__FN xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables) const + { + xpath_query q(query, variables); + return select_node(q); + } + + PUGI__FN xpath_node xml_node::select_node(const xpath_query& query) const + { + return query.evaluate_node(*this); + } + + PUGI__FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const + { + xpath_query q(query, variables); + return select_nodes(q); + } + + PUGI__FN xpath_node_set xml_node::select_nodes(const xpath_query& query) const + { + return query.evaluate_node_set(*this); + } + + PUGI__FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const + { + xpath_query q(query, variables); + return select_single_node(q); + } + + PUGI__FN xpath_node xml_node::select_single_node(const xpath_query& query) const + { + return query.evaluate_node(*this); + } +} + +#endif + +#ifdef __BORLANDC__ +# pragma option pop +#endif + +// Intel C++ does not properly keep warning state for function templates, +// so popping warning state at the end of translation unit leads to warnings in the middle. +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) +# pragma warning(pop) +#endif + +// Undefine all local macros (makes sure we're not leaking macros in header-only mode) +#undef PUGI__NO_INLINE +#undef PUGI__UNLIKELY +#undef PUGI__STATIC_ASSERT +#undef PUGI__DMC_VOLATILE +#undef PUGI__MSVC_CRT_VERSION +#undef PUGI__NS_BEGIN +#undef PUGI__NS_END +#undef PUGI__FN +#undef PUGI__FN_NO_INLINE +#undef PUGI__NODETYPE +#undef PUGI__IS_CHARTYPE_IMPL +#undef PUGI__IS_CHARTYPE +#undef PUGI__IS_CHARTYPEX +#undef PUGI__ENDSWITH +#undef PUGI__SKIPWS +#undef PUGI__OPTSET +#undef PUGI__PUSHNODE +#undef PUGI__POPNODE +#undef PUGI__SCANFOR +#undef PUGI__SCANWHILE +#undef PUGI__SCANWHILE_UNROLL +#undef PUGI__ENDSEG +#undef PUGI__THROW_ERROR +#undef PUGI__CHECK_ERROR + +#endif + +/** + * Copyright (c) 2006-2015 Arseny Kapoulkine + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ diff --git a/vnr/profile/pugixml.hpp b/vnr/profile/pugixml.hpp new file mode 100644 index 0000000..d59f864 --- /dev/null +++ b/vnr/profile/pugixml.hpp @@ -0,0 +1,1366 @@ +/** + * pugixml parser - version 1.6 + * -------------------------------------------------------- + * Copyright (C) 2006-2015, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Report bugs and download new versions at http://pugixml.org/ + * + * This library is distributed under the MIT License. See notice at the end + * of this file. + * + * This work is based on the pugxml parser, which is: + * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) + */ + +#ifndef PUGIXML_VERSION +// Define version macro; evaluates to major * 100 + minor so that it's safe to use in less-than comparisons +# define PUGIXML_VERSION 160 +#endif + +// Include user configuration file (this can define various configuration macros) +#include "pugiconfig.hpp" + +#ifndef HEADER_PUGIXML_HPP +#define HEADER_PUGIXML_HPP + +// Include stddef.h for size_t and ptrdiff_t +#include + +// Include exception header for XPath +#if !defined(PUGIXML_NO_XPATH) && !defined(PUGIXML_NO_EXCEPTIONS) +# include +#endif + +// Include STL headers +#ifndef PUGIXML_NO_STL +# include +# include +# include +#endif + +// Macro for deprecated features +#ifndef PUGIXML_DEPRECATED +# if defined(__GNUC__) +# define PUGIXML_DEPRECATED __attribute__((deprecated)) +# elif defined(_MSC_VER) && _MSC_VER >= 1300 +# define PUGIXML_DEPRECATED __declspec(deprecated) +# else +# define PUGIXML_DEPRECATED +# endif +#endif + +// If no API is defined, assume default +#ifndef PUGIXML_API +# define PUGIXML_API +#endif + +// If no API for classes is defined, assume default +#ifndef PUGIXML_CLASS +# define PUGIXML_CLASS PUGIXML_API +#endif + +// If no API for functions is defined, assume default +#ifndef PUGIXML_FUNCTION +# define PUGIXML_FUNCTION PUGIXML_API +#endif + +// If the platform is known to have long long support, enable long long functions +#ifndef PUGIXML_HAS_LONG_LONG +# if defined(__cplusplus) && __cplusplus >= 201103 +# define PUGIXML_HAS_LONG_LONG +# elif defined(_MSC_VER) && _MSC_VER >= 1400 +# define PUGIXML_HAS_LONG_LONG +# endif +#endif + +// Character interface macros +#ifdef PUGIXML_WCHAR_MODE +# define PUGIXML_TEXT(t) L ## t +# define PUGIXML_CHAR wchar_t +#else +# define PUGIXML_TEXT(t) t +# define PUGIXML_CHAR char +#endif + +namespace pugi +{ + // Character type used for all internal storage and operations; depends on PUGIXML_WCHAR_MODE + typedef PUGIXML_CHAR char_t; + +#ifndef PUGIXML_NO_STL + // String type used for operations that work with STL string; depends on PUGIXML_WCHAR_MODE + typedef std::basic_string, std::allocator > string_t; +#endif +} + +// The PugiXML namespace +namespace pugi +{ + // Tree node types + enum xml_node_type + { + node_null, // Empty (null) node handle + node_document, // A document tree's absolute root + node_element, // Element tag, i.e. '' + node_pcdata, // Plain character data, i.e. 'text' + node_cdata, // Character data, i.e. '' + node_comment, // Comment tag, i.e. '' + node_pi, // Processing instruction, i.e. '' + node_declaration, // Document declaration, i.e. '' + node_doctype // Document type declaration, i.e. '' + }; + + // Parsing options + + // Minimal parsing mode (equivalent to turning all other flags off). + // Only elements and PCDATA sections are added to the DOM tree, no text conversions are performed. + const unsigned int parse_minimal = 0x0000; + + // This flag determines if processing instructions (node_pi) are added to the DOM tree. This flag is off by default. + const unsigned int parse_pi = 0x0001; + + // This flag determines if comments (node_comment) are added to the DOM tree. This flag is off by default. + const unsigned int parse_comments = 0x0002; + + // This flag determines if CDATA sections (node_cdata) are added to the DOM tree. This flag is on by default. + const unsigned int parse_cdata = 0x0004; + + // This flag determines if plain character data (node_pcdata) that consist only of whitespace are added to the DOM tree. + // This flag is off by default; turning it on usually results in slower parsing and more memory consumption. + const unsigned int parse_ws_pcdata = 0x0008; + + // This flag determines if character and entity references are expanded during parsing. This flag is on by default. + const unsigned int parse_escapes = 0x0010; + + // This flag determines if EOL characters are normalized (converted to #xA) during parsing. This flag is on by default. + const unsigned int parse_eol = 0x0020; + + // This flag determines if attribute values are normalized using CDATA normalization rules during parsing. This flag is on by default. + const unsigned int parse_wconv_attribute = 0x0040; + + // This flag determines if attribute values are normalized using NMTOKENS normalization rules during parsing. This flag is off by default. + const unsigned int parse_wnorm_attribute = 0x0080; + + // This flag determines if document declaration (node_declaration) is added to the DOM tree. This flag is off by default. + const unsigned int parse_declaration = 0x0100; + + // This flag determines if document type declaration (node_doctype) is added to the DOM tree. This flag is off by default. + const unsigned int parse_doctype = 0x0200; + + // This flag determines if plain character data (node_pcdata) that is the only child of the parent node and that consists only + // of whitespace is added to the DOM tree. + // This flag is off by default; turning it on may result in slower parsing and more memory consumption. + const unsigned int parse_ws_pcdata_single = 0x0400; + + // This flag determines if leading and trailing whitespace is to be removed from plain character data. This flag is off by default. + const unsigned int parse_trim_pcdata = 0x0800; + + // This flag determines if plain character data that does not have a parent node is added to the DOM tree, and if an empty document + // is a valid document. This flag is off by default. + const unsigned int parse_fragment = 0x1000; + + // The default parsing mode. + // Elements, PCDATA and CDATA sections are added to the DOM tree, character/reference entities are expanded, + // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. + const unsigned int parse_default = parse_cdata | parse_escapes | parse_wconv_attribute | parse_eol; + + // The full parsing mode. + // Nodes of all types are added to the DOM tree, character/reference entities are expanded, + // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. + const unsigned int parse_full = parse_default | parse_pi | parse_comments | parse_declaration | parse_doctype; + + // These flags determine the encoding of input data for XML document + enum xml_encoding + { + encoding_auto, // Auto-detect input encoding using BOM or < / class xml_object_range + { + public: + typedef It const_iterator; + typedef It iterator; + + xml_object_range(It b, It e): _begin(b), _end(e) + { + } + + It begin() const { return _begin; } + It end() const { return _end; } + + private: + It _begin, _end; + }; + + // Writer interface for node printing (see xml_node::print) + class PUGIXML_CLASS xml_writer + { + public: + virtual ~xml_writer() {} + + // Write memory chunk into stream/file/whatever + virtual void write(const void* data, size_t size) = 0; + }; + + // xml_writer implementation for FILE* + class PUGIXML_CLASS xml_writer_file: public xml_writer + { + public: + // Construct writer from a FILE* object; void* is used to avoid header dependencies on stdio + xml_writer_file(void* file); + + virtual void write(const void* data, size_t size); + + private: + void* file; + }; + + #ifndef PUGIXML_NO_STL + // xml_writer implementation for streams + class PUGIXML_CLASS xml_writer_stream: public xml_writer + { + public: + // Construct writer from an output stream object + xml_writer_stream(std::basic_ostream >& stream); + xml_writer_stream(std::basic_ostream >& stream); + + virtual void write(const void* data, size_t size); + + private: + std::basic_ostream >* narrow_stream; + std::basic_ostream >* wide_stream; + }; + #endif + + // A light-weight handle for manipulating attributes in DOM tree + class PUGIXML_CLASS xml_attribute + { + friend class xml_attribute_iterator; + friend class xml_node; + + private: + xml_attribute_struct* _attr; + + typedef void (*unspecified_bool_type)(xml_attribute***); + + public: + // Default constructor. Constructs an empty attribute. + xml_attribute(); + + // Constructs attribute from internal pointer + explicit xml_attribute(xml_attribute_struct* attr); + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Comparison operators (compares wrapped attribute pointers) + bool operator==(const xml_attribute& r) const; + bool operator!=(const xml_attribute& r) const; + bool operator<(const xml_attribute& r) const; + bool operator>(const xml_attribute& r) const; + bool operator<=(const xml_attribute& r) const; + bool operator>=(const xml_attribute& r) const; + + // Check if attribute is empty + bool empty() const; + + // Get attribute name/value, or "" if attribute is empty + const char_t* name() const; + const char_t* value() const; + + // Get attribute value, or the default value if attribute is empty + const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; + + // Get attribute value as a number, or the default value if conversion did not succeed or attribute is empty + int as_int(int def = 0) const; + unsigned int as_uint(unsigned int def = 0) const; + double as_double(double def = 0) const; + float as_float(float def = 0) const; + + #ifdef PUGIXML_HAS_LONG_LONG + long long as_llong(long long def = 0) const; + unsigned long long as_ullong(unsigned long long def = 0) const; + #endif + + // Get attribute value as bool (returns true if first character is in '1tTyY' set), or the default value if attribute is empty + bool as_bool(bool def = false) const; + + // Set attribute name/value (returns false if attribute is empty or there is not enough memory) + bool set_name(const char_t* rhs); + bool set_value(const char_t* rhs); + + // Set attribute value with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") + bool set_value(int rhs); + bool set_value(unsigned int rhs); + bool set_value(double rhs); + bool set_value(float rhs); + bool set_value(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + bool set_value(long long rhs); + bool set_value(unsigned long long rhs); + #endif + + // Set attribute value (equivalent to set_value without error checking) + xml_attribute& operator=(const char_t* rhs); + xml_attribute& operator=(int rhs); + xml_attribute& operator=(unsigned int rhs); + xml_attribute& operator=(double rhs); + xml_attribute& operator=(float rhs); + xml_attribute& operator=(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + xml_attribute& operator=(long long rhs); + xml_attribute& operator=(unsigned long long rhs); + #endif + + // Get next/previous attribute in the attribute list of the parent node + xml_attribute next_attribute() const; + xml_attribute previous_attribute() const; + + // Get hash value (unique for handles to the same object) + size_t hash_value() const; + + // Get internal pointer + xml_attribute_struct* internal_object() const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xml_attribute& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xml_attribute& lhs, bool rhs); +#endif + + // A light-weight handle for manipulating nodes in DOM tree + class PUGIXML_CLASS xml_node + { + friend class xml_attribute_iterator; + friend class xml_node_iterator; + friend class xml_named_node_iterator; + + protected: + xml_node_struct* _root; + + typedef void (*unspecified_bool_type)(xml_node***); + + public: + // Default constructor. Constructs an empty node. + xml_node(); + + // Constructs node from internal pointer + explicit xml_node(xml_node_struct* p); + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Comparison operators (compares wrapped node pointers) + bool operator==(const xml_node& r) const; + bool operator!=(const xml_node& r) const; + bool operator<(const xml_node& r) const; + bool operator>(const xml_node& r) const; + bool operator<=(const xml_node& r) const; + bool operator>=(const xml_node& r) const; + + // Check if node is empty. + bool empty() const; + + // Get node type + xml_node_type type() const; + + // Get node name, or "" if node is empty or it has no name + const char_t* name() const; + + // Get node value, or "" if node is empty or it has no value + // Note: For text node.value() does not return "text"! Use child_value() or text() methods to access text inside nodes. + const char_t* value() const; + + // Get attribute list + xml_attribute first_attribute() const; + xml_attribute last_attribute() const; + + // Get children list + xml_node first_child() const; + xml_node last_child() const; + + // Get next/previous sibling in the children list of the parent node + xml_node next_sibling() const; + xml_node previous_sibling() const; + + // Get parent node + xml_node parent() const; + + // Get root of DOM tree this node belongs to + xml_node root() const; + + // Get text object for the current node + xml_text text() const; + + // Get child, attribute or next/previous sibling with the specified name + xml_node child(const char_t* name) const; + xml_attribute attribute(const char_t* name) const; + xml_node next_sibling(const char_t* name) const; + xml_node previous_sibling(const char_t* name) const; + + // Get child value of current node; that is, value of the first child node of type PCDATA/CDATA + const char_t* child_value() const; + + // Get child value of child with specified name. Equivalent to child(name).child_value(). + const char_t* child_value(const char_t* name) const; + + // Set node name/value (returns false if node is empty, there is not enough memory, or node can not have name/value) + bool set_name(const char_t* rhs); + bool set_value(const char_t* rhs); + + // Add attribute with specified name. Returns added attribute, or empty attribute on errors. + xml_attribute append_attribute(const char_t* name); + xml_attribute prepend_attribute(const char_t* name); + xml_attribute insert_attribute_after(const char_t* name, const xml_attribute& attr); + xml_attribute insert_attribute_before(const char_t* name, const xml_attribute& attr); + + // Add a copy of the specified attribute. Returns added attribute, or empty attribute on errors. + xml_attribute append_copy(const xml_attribute& proto); + xml_attribute prepend_copy(const xml_attribute& proto); + xml_attribute insert_copy_after(const xml_attribute& proto, const xml_attribute& attr); + xml_attribute insert_copy_before(const xml_attribute& proto, const xml_attribute& attr); + + // Add child node with specified type. Returns added node, or empty node on errors. + xml_node append_child(xml_node_type type = node_element); + xml_node prepend_child(xml_node_type type = node_element); + xml_node insert_child_after(xml_node_type type, const xml_node& node); + xml_node insert_child_before(xml_node_type type, const xml_node& node); + + // Add child element with specified name. Returns added node, or empty node on errors. + xml_node append_child(const char_t* name); + xml_node prepend_child(const char_t* name); + xml_node insert_child_after(const char_t* name, const xml_node& node); + xml_node insert_child_before(const char_t* name, const xml_node& node); + + // Add a copy of the specified node as a child. Returns added node, or empty node on errors. + xml_node append_copy(const xml_node& proto); + xml_node prepend_copy(const xml_node& proto); + xml_node insert_copy_after(const xml_node& proto, const xml_node& node); + xml_node insert_copy_before(const xml_node& proto, const xml_node& node); + + // Move the specified node to become a child of this node. Returns moved node, or empty node on errors. + xml_node append_move(const xml_node& moved); + xml_node prepend_move(const xml_node& moved); + xml_node insert_move_after(const xml_node& moved, const xml_node& node); + xml_node insert_move_before(const xml_node& moved, const xml_node& node); + + // Remove specified attribute + bool remove_attribute(const xml_attribute& a); + bool remove_attribute(const char_t* name); + + // Remove specified child + bool remove_child(const xml_node& n); + bool remove_child(const char_t* name); + + // Parses buffer as an XML document fragment and appends all nodes as children of the current node. + // Copies/converts the buffer, so it may be deleted or changed after the function returns. + // Note: append_buffer allocates memory that has the lifetime of the owning document; removing the appended nodes does not immediately reclaim that memory. + xml_parse_result append_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Find attribute using predicate. Returns first attribute for which predicate returned true. + template xml_attribute find_attribute(Predicate pred) const + { + if (!_root) return xml_attribute(); + + for (xml_attribute attrib = first_attribute(); attrib; attrib = attrib.next_attribute()) + if (pred(attrib)) + return attrib; + + return xml_attribute(); + } + + // Find child node using predicate. Returns first child for which predicate returned true. + template xml_node find_child(Predicate pred) const + { + if (!_root) return xml_node(); + + for (xml_node node = first_child(); node; node = node.next_sibling()) + if (pred(node)) + return node; + + return xml_node(); + } + + // Find node from subtree using predicate. Returns first node from subtree (depth-first), for which predicate returned true. + template xml_node find_node(Predicate pred) const + { + if (!_root) return xml_node(); + + xml_node cur = first_child(); + + while (cur._root && cur._root != _root) + { + if (pred(cur)) return cur; + + if (cur.first_child()) cur = cur.first_child(); + else if (cur.next_sibling()) cur = cur.next_sibling(); + else + { + while (!cur.next_sibling() && cur._root != _root) cur = cur.parent(); + + if (cur._root != _root) cur = cur.next_sibling(); + } + } + + return xml_node(); + } + + // Find child node by attribute name/value + xml_node find_child_by_attribute(const char_t* name, const char_t* attr_name, const char_t* attr_value) const; + xml_node find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const; + + #ifndef PUGIXML_NO_STL + // Get the absolute node path from root as a text string. + string_t path(char_t delimiter = '/') const; + #endif + + // Search for a node by path consisting of node names and . or .. elements. + xml_node first_element_by_path(const char_t* path, char_t delimiter = '/') const; + + // Recursively traverse subtree with xml_tree_walker + bool traverse(xml_tree_walker& walker); + + #ifndef PUGIXML_NO_XPATH + // Select single node by evaluating XPath query. Returns first node from the resulting node set. + xpath_node select_node(const char_t* query, xpath_variable_set* variables = 0) const; + xpath_node select_node(const xpath_query& query) const; + + // Select node set by evaluating XPath query + xpath_node_set select_nodes(const char_t* query, xpath_variable_set* variables = 0) const; + xpath_node_set select_nodes(const xpath_query& query) const; + + // (deprecated: use select_node instead) Select single node by evaluating XPath query. + xpath_node select_single_node(const char_t* query, xpath_variable_set* variables = 0) const; + xpath_node select_single_node(const xpath_query& query) const; + + #endif + + // Print subtree using a writer object + void print(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; + + #ifndef PUGIXML_NO_STL + // Print subtree to stream + void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; + void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const; + #endif + + // Child nodes iterators + typedef xml_node_iterator iterator; + + iterator begin() const; + iterator end() const; + + // Attribute iterators + typedef xml_attribute_iterator attribute_iterator; + + attribute_iterator attributes_begin() const; + attribute_iterator attributes_end() const; + + // Range-based for support + xml_object_range children() const; + xml_object_range children(const char_t* name) const; + xml_object_range attributes() const; + + // Get node offset in parsed file/string (in char_t units) for debugging purposes + ptrdiff_t offset_debug() const; + + // Get hash value (unique for handles to the same object) + size_t hash_value() const; + + // Get internal pointer + xml_node_struct* internal_object() const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xml_node& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xml_node& lhs, bool rhs); +#endif + + // A helper for working with text inside PCDATA nodes + class PUGIXML_CLASS xml_text + { + friend class xml_node; + + xml_node_struct* _root; + + typedef void (*unspecified_bool_type)(xml_text***); + + explicit xml_text(xml_node_struct* root); + + xml_node_struct* _data_new(); + xml_node_struct* _data() const; + + public: + // Default constructor. Constructs an empty object. + xml_text(); + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Check if text object is empty + bool empty() const; + + // Get text, or "" if object is empty + const char_t* get() const; + + // Get text, or the default value if object is empty + const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; + + // Get text as a number, or the default value if conversion did not succeed or object is empty + int as_int(int def = 0) const; + unsigned int as_uint(unsigned int def = 0) const; + double as_double(double def = 0) const; + float as_float(float def = 0) const; + + #ifdef PUGIXML_HAS_LONG_LONG + long long as_llong(long long def = 0) const; + unsigned long long as_ullong(unsigned long long def = 0) const; + #endif + + // Get text as bool (returns true if first character is in '1tTyY' set), or the default value if object is empty + bool as_bool(bool def = false) const; + + // Set text (returns false if object is empty or there is not enough memory) + bool set(const char_t* rhs); + + // Set text with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") + bool set(int rhs); + bool set(unsigned int rhs); + bool set(double rhs); + bool set(float rhs); + bool set(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + bool set(long long rhs); + bool set(unsigned long long rhs); + #endif + + // Set text (equivalent to set without error checking) + xml_text& operator=(const char_t* rhs); + xml_text& operator=(int rhs); + xml_text& operator=(unsigned int rhs); + xml_text& operator=(double rhs); + xml_text& operator=(float rhs); + xml_text& operator=(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + xml_text& operator=(long long rhs); + xml_text& operator=(unsigned long long rhs); + #endif + + // Get the data node (node_pcdata or node_cdata) for this object + xml_node data() const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xml_text& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xml_text& lhs, bool rhs); +#endif + + // Child node iterator (a bidirectional iterator over a collection of xml_node) + class PUGIXML_CLASS xml_node_iterator + { + friend class xml_node; + + private: + mutable xml_node _wrap; + xml_node _parent; + + xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent); + + public: + // Iterator traits + typedef ptrdiff_t difference_type; + typedef xml_node value_type; + typedef xml_node* pointer; + typedef xml_node& reference; + + #ifndef PUGIXML_NO_STL + typedef std::bidirectional_iterator_tag iterator_category; + #endif + + // Default constructor + xml_node_iterator(); + + // Construct an iterator which points to the specified node + xml_node_iterator(const xml_node& node); + + // Iterator operators + bool operator==(const xml_node_iterator& rhs) const; + bool operator!=(const xml_node_iterator& rhs) const; + + xml_node& operator*() const; + xml_node* operator->() const; + + const xml_node_iterator& operator++(); + xml_node_iterator operator++(int); + + const xml_node_iterator& operator--(); + xml_node_iterator operator--(int); + }; + + // Attribute iterator (a bidirectional iterator over a collection of xml_attribute) + class PUGIXML_CLASS xml_attribute_iterator + { + friend class xml_node; + + private: + mutable xml_attribute _wrap; + xml_node _parent; + + xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent); + + public: + // Iterator traits + typedef ptrdiff_t difference_type; + typedef xml_attribute value_type; + typedef xml_attribute* pointer; + typedef xml_attribute& reference; + + #ifndef PUGIXML_NO_STL + typedef std::bidirectional_iterator_tag iterator_category; + #endif + + // Default constructor + xml_attribute_iterator(); + + // Construct an iterator which points to the specified attribute + xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent); + + // Iterator operators + bool operator==(const xml_attribute_iterator& rhs) const; + bool operator!=(const xml_attribute_iterator& rhs) const; + + xml_attribute& operator*() const; + xml_attribute* operator->() const; + + const xml_attribute_iterator& operator++(); + xml_attribute_iterator operator++(int); + + const xml_attribute_iterator& operator--(); + xml_attribute_iterator operator--(int); + }; + + // Named node range helper + class PUGIXML_CLASS xml_named_node_iterator + { + friend class xml_node; + + public: + // Iterator traits + typedef ptrdiff_t difference_type; + typedef xml_node value_type; + typedef xml_node* pointer; + typedef xml_node& reference; + + #ifndef PUGIXML_NO_STL + typedef std::bidirectional_iterator_tag iterator_category; + #endif + + // Default constructor + xml_named_node_iterator(); + + // Construct an iterator which points to the specified node + xml_named_node_iterator(const xml_node& node, const char_t* name); + + // Iterator operators + bool operator==(const xml_named_node_iterator& rhs) const; + bool operator!=(const xml_named_node_iterator& rhs) const; + + xml_node& operator*() const; + xml_node* operator->() const; + + const xml_named_node_iterator& operator++(); + xml_named_node_iterator operator++(int); + + const xml_named_node_iterator& operator--(); + xml_named_node_iterator operator--(int); + + private: + mutable xml_node _wrap; + xml_node _parent; + const char_t* _name; + + xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name); + }; + + // Abstract tree walker class (see xml_node::traverse) + class PUGIXML_CLASS xml_tree_walker + { + friend class xml_node; + + private: + int _depth; + + protected: + // Get current traversal depth + int depth() const; + + public: + xml_tree_walker(); + virtual ~xml_tree_walker(); + + // Callback that is called when traversal begins + virtual bool begin(xml_node& node); + + // Callback that is called for each node traversed + virtual bool for_each(xml_node& node) = 0; + + // Callback that is called when traversal ends + virtual bool end(xml_node& node); + }; + + // Parsing status, returned as part of xml_parse_result object + enum xml_parse_status + { + status_ok = 0, // No error + + status_file_not_found, // File was not found during load_file() + status_io_error, // Error reading from file/stream + status_out_of_memory, // Could not allocate memory + status_internal_error, // Internal error occurred + + status_unrecognized_tag, // Parser could not determine tag type + + status_bad_pi, // Parsing error occurred while parsing document declaration/processing instruction + status_bad_comment, // Parsing error occurred while parsing comment + status_bad_cdata, // Parsing error occurred while parsing CDATA section + status_bad_doctype, // Parsing error occurred while parsing document type declaration + status_bad_pcdata, // Parsing error occurred while parsing PCDATA section + status_bad_start_element, // Parsing error occurred while parsing start element tag + status_bad_attribute, // Parsing error occurred while parsing element attribute + status_bad_end_element, // Parsing error occurred while parsing end element tag + status_end_element_mismatch,// There was a mismatch of start-end tags (closing tag had incorrect name, some tag was not closed or there was an excessive closing tag) + + status_append_invalid_root, // Unable to append nodes since root type is not node_element or node_document (exclusive to xml_node::append_buffer) + + status_no_document_element // Parsing resulted in a document without element nodes + }; + + // Parsing result + struct PUGIXML_CLASS xml_parse_result + { + // Parsing status (see xml_parse_status) + xml_parse_status status; + + // Last parsed offset (in char_t units from start of input data) + ptrdiff_t offset; + + // Source document encoding + xml_encoding encoding; + + // Default constructor, initializes object to failed state + xml_parse_result(); + + // Cast to bool operator + operator bool() const; + + // Get error description + const char* description() const; + }; + + // Document class (DOM tree root) + class PUGIXML_CLASS xml_document: public xml_node + { + private: + char_t* _buffer; + + char _memory[192]; + + // Non-copyable semantics + xml_document(const xml_document&); + const xml_document& operator=(const xml_document&); + + void create(); + void destroy(); + + public: + // Default constructor, makes empty document + xml_document(); + + // Destructor, invalidates all node/attribute handles to this document + ~xml_document(); + + // Removes all nodes, leaving the empty document + void reset(); + + // Removes all nodes, then copies the entire contents of the specified document + void reset(const xml_document& proto); + + #ifndef PUGIXML_NO_STL + // Load document from stream. + xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default); + #endif + + // (deprecated: use load_string instead) Load document from zero-terminated string. No encoding conversions are applied. + xml_parse_result load(const char_t* contents, unsigned int options = parse_default); + + // Load document from zero-terminated string. No encoding conversions are applied. + xml_parse_result load_string(const char_t* contents, unsigned int options = parse_default); + + // Load document from file + xml_parse_result load_file(const char* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + xml_parse_result load_file(const wchar_t* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Load document from buffer. Copies/converts the buffer, so it may be deleted or changed after the function returns. + xml_parse_result load_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). + // You should ensure that buffer data will persist throughout the document's lifetime, and free the buffer memory manually once document is destroyed. + xml_parse_result load_buffer_inplace(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). + // You should allocate the buffer with pugixml allocation function; document will free the buffer when it is no longer needed (you can't use it anymore). + xml_parse_result load_buffer_inplace_own(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Save XML document to writer (semantics is slightly different from xml_node::print, see documentation for details). + void save(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + + #ifndef PUGIXML_NO_STL + // Save XML document to stream (semantics is slightly different from xml_node::print, see documentation for details). + void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const; + #endif + + // Save XML to file + bool save_file(const char* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + bool save_file(const wchar_t* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + + // Get document element + xml_node document_element() const; + }; + +#ifndef PUGIXML_NO_XPATH + // XPath query return type + enum xpath_value_type + { + xpath_type_none, // Unknown type (query failed to compile) + xpath_type_node_set, // Node set (xpath_node_set) + xpath_type_number, // Number + xpath_type_string, // String + xpath_type_boolean // Boolean + }; + + // XPath parsing result + struct PUGIXML_CLASS xpath_parse_result + { + // Error message (0 if no error) + const char* error; + + // Last parsed offset (in char_t units from string start) + ptrdiff_t offset; + + // Default constructor, initializes object to failed state + xpath_parse_result(); + + // Cast to bool operator + operator bool() const; + + // Get error description + const char* description() const; + }; + + // A single XPath variable + class PUGIXML_CLASS xpath_variable + { + friend class xpath_variable_set; + + protected: + xpath_value_type _type; + xpath_variable* _next; + + xpath_variable(); + + // Non-copyable semantics + xpath_variable(const xpath_variable&); + xpath_variable& operator=(const xpath_variable&); + + public: + // Get variable name + const char_t* name() const; + + // Get variable type + xpath_value_type type() const; + + // Get variable value; no type conversion is performed, default value (false, NaN, empty string, empty node set) is returned on type mismatch error + bool get_boolean() const; + double get_number() const; + const char_t* get_string() const; + const xpath_node_set& get_node_set() const; + + // Set variable value; no type conversion is performed, false is returned on type mismatch error + bool set(bool value); + bool set(double value); + bool set(const char_t* value); + bool set(const xpath_node_set& value); + }; + + // A set of XPath variables + class PUGIXML_CLASS xpath_variable_set + { + private: + xpath_variable* _data[64]; + + // Non-copyable semantics + xpath_variable_set(const xpath_variable_set&); + xpath_variable_set& operator=(const xpath_variable_set&); + + xpath_variable* find(const char_t* name) const; + + public: + // Default constructor/destructor + xpath_variable_set(); + ~xpath_variable_set(); + + // Add a new variable or get the existing one, if the types match + xpath_variable* add(const char_t* name, xpath_value_type type); + + // Set value of an existing variable; no type conversion is performed, false is returned if there is no such variable or if types mismatch + bool set(const char_t* name, bool value); + bool set(const char_t* name, double value); + bool set(const char_t* name, const char_t* value); + bool set(const char_t* name, const xpath_node_set& value); + + // Get existing variable by name + xpath_variable* get(const char_t* name); + const xpath_variable* get(const char_t* name) const; + }; + + // A compiled XPath query object + class PUGIXML_CLASS xpath_query + { + private: + void* _impl; + xpath_parse_result _result; + + typedef void (*unspecified_bool_type)(xpath_query***); + + // Non-copyable semantics + xpath_query(const xpath_query&); + xpath_query& operator=(const xpath_query&); + + public: + // Construct a compiled object from XPath expression. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on compilation errors. + explicit xpath_query(const char_t* query, xpath_variable_set* variables = 0); + + // Destructor + ~xpath_query(); + + // Get query expression return type + xpath_value_type return_type() const; + + // Evaluate expression as boolean value in the specified context; performs type conversion if necessary. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + bool evaluate_boolean(const xpath_node& n) const; + + // Evaluate expression as double value in the specified context; performs type conversion if necessary. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + double evaluate_number(const xpath_node& n) const; + + #ifndef PUGIXML_NO_STL + // Evaluate expression as string value in the specified context; performs type conversion if necessary. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + string_t evaluate_string(const xpath_node& n) const; + #endif + + // Evaluate expression as string value in the specified context; performs type conversion if necessary. + // At most capacity characters are written to the destination buffer, full result size is returned (includes terminating zero). + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + // If PUGIXML_NO_EXCEPTIONS is defined, returns empty set instead. + size_t evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const; + + // Evaluate expression as node set in the specified context. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors. + // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node set instead. + xpath_node_set evaluate_node_set(const xpath_node& n) const; + + // Evaluate expression as node set in the specified context. + // Return first node in document order, or empty node if node set is empty. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors. + // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node instead. + xpath_node evaluate_node(const xpath_node& n) const; + + // Get parsing result (used to get compilation errors in PUGIXML_NO_EXCEPTIONS mode) + const xpath_parse_result& result() const; + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + }; + + #ifndef PUGIXML_NO_EXCEPTIONS + // XPath exception class + class PUGIXML_CLASS xpath_exception: public std::exception + { + private: + xpath_parse_result _result; + + public: + // Construct exception from parse result + explicit xpath_exception(const xpath_parse_result& result); + + // Get error message + virtual const char* what() const throw(); + + // Get parse result + const xpath_parse_result& result() const; + }; + #endif + + // XPath node class (either xml_node or xml_attribute) + class PUGIXML_CLASS xpath_node + { + private: + xml_node _node; + xml_attribute _attribute; + + typedef void (*unspecified_bool_type)(xpath_node***); + + public: + // Default constructor; constructs empty XPath node + xpath_node(); + + // Construct XPath node from XML node/attribute + xpath_node(const xml_node& node); + xpath_node(const xml_attribute& attribute, const xml_node& parent); + + // Get node/attribute, if any + xml_node node() const; + xml_attribute attribute() const; + + // Get parent of contained node/attribute + xml_node parent() const; + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Comparison operators + bool operator==(const xpath_node& n) const; + bool operator!=(const xpath_node& n) const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xpath_node& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xpath_node& lhs, bool rhs); +#endif + + // A fixed-size collection of XPath nodes + class PUGIXML_CLASS xpath_node_set + { + public: + // Collection type + enum type_t + { + type_unsorted, // Not ordered + type_sorted, // Sorted by document order (ascending) + type_sorted_reverse // Sorted by document order (descending) + }; + + // Constant iterator type + typedef const xpath_node* const_iterator; + + // We define non-constant iterator to be the same as constant iterator so that various generic algorithms (i.e. boost foreach) work + typedef const xpath_node* iterator; + + // Default constructor. Constructs empty set. + xpath_node_set(); + + // Constructs a set from iterator range; data is not checked for duplicates and is not sorted according to provided type, so be careful + xpath_node_set(const_iterator begin, const_iterator end, type_t type = type_unsorted); + + // Destructor + ~xpath_node_set(); + + // Copy constructor/assignment operator + xpath_node_set(const xpath_node_set& ns); + xpath_node_set& operator=(const xpath_node_set& ns); + + // Get collection type + type_t type() const; + + // Get collection size + size_t size() const; + + // Indexing operator + const xpath_node& operator[](size_t index) const; + + // Collection iterators + const_iterator begin() const; + const_iterator end() const; + + // Sort the collection in ascending/descending order by document order + void sort(bool reverse = false); + + // Get first node in the collection by document order + xpath_node first() const; + + // Check if collection is empty + bool empty() const; + + private: + type_t _type; + + xpath_node _storage; + + xpath_node* _begin; + xpath_node* _end; + + void _assign(const_iterator begin, const_iterator end); + }; +#endif + +#ifndef PUGIXML_NO_STL + // Convert wide string to UTF8 + std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const wchar_t* str); + std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const std::basic_string, std::allocator >& str); + + // Convert UTF8 to wide string + std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const char* str); + std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const std::basic_string, std::allocator >& str); +#endif + + // Memory allocation function interface; returns pointer to allocated memory or NULL on failure + typedef void* (*allocation_function)(size_t size); + + // Memory deallocation function interface + typedef void (*deallocation_function)(void* ptr); + + // Override default memory management functions. All subsequent allocations/deallocations will be performed via supplied functions. + void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate); + + // Get current memory management functions + allocation_function PUGIXML_FUNCTION get_memory_allocation_function(); + deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function(); +} + +#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) +namespace std +{ + // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) + std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_node_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_attribute_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_named_node_iterator&); +} +#endif + +#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) +namespace std +{ + // Workarounds for (non-standard) iterator category detection + std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_node_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_attribute_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_named_node_iterator&); +} +#endif + +#endif + +// Make sure implementation is included in header-only mode +// Use macro expansion in #include to work around QMake (QTBUG-11923) +#if defined(PUGIXML_HEADER_ONLY) && !defined(PUGIXML_SOURCE) +# define PUGIXML_SOURCE "pugixml.cpp" +# include PUGIXML_SOURCE +#endif + +/** + * Copyright (c) 2006-2015 Arseny Kapoulkine + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ diff --git a/vnr/sakurakit/sakurakit.pri b/vnr/sakurakit/sakurakit.pri new file mode 100644 index 0000000..71fe17b --- /dev/null +++ b/vnr/sakurakit/sakurakit.pri @@ -0,0 +1,62 @@ +# sakurakit.pri +# 6/28/2011 jichi +# +# config: +# - sakurakit_qml +# - sakurakit_gui +# - sakurakit_widgets + +DEFINES += WITH_LIB_SAKURAKIT +DEPENDPATH += $$PWD + +#QT += core +HEADERS += \ + $$PWD/skautorun.h \ + $$PWD/skdebug.h \ + $$PWD/skglobal.h \ + $$PWD/skhash.h + +#CONFIG(sakurakit_gui) { +# DEFINES += SK_ENABLE_GUI +# QT += gui +# HEADERS += \ +# $$PWD/skuiutil.h +# +# CONFIG(sakurakit_qml) { +# DEFINES += SK_ENABLE_QML +# QT += qml quick +# HEADERS += \ +# $$PWD/skdraggablequickview.h +# SOURCES += \ +# $$PWD/skdraggablequickview.cc +# } +# +# CONFIG(sakurakit_widgets) { +# DEFINES += SK_ENABLE_WIDGETS +# QT += widgets +# HEADERS += \ +# $$PWD/skdraggabledialog.h \ +# $$PWD/skdraggablemainwindow.h \ +# $$PWD/skdraggablewidget.h \ +# $$PWD/skpushbutton.h \ +# $$PWD/sksystemtrayicon.h \ +# $$PWD/sktoolbutton.h \ +# $$PWD/skwindowcontainer.h +# SOURCES += \ +# $$PWD/skdraggablewidget.cc \ +# $$PWD/skpushbutton.cc \ +# $$PWD/sktoolbutton.cc \ +# $$PWD/skwindowcontainer.cc \ +# $$PWD/skdraggabledialog.cc \ +# $$PWD/skdraggablemainwindow.cc +# +# CONFIG(sakurakit_qml) { +# HEADERS += \ +# $$PWD/skquickwidget.h +# SOURCES += \ +# $$PWD/skquickwidget.cc +# } +# } +#} + +# EOF diff --git a/vnr/sakurakit/skautorun.h b/vnr/sakurakit/skautorun.h new file mode 100644 index 0000000..2081492 --- /dev/null +++ b/vnr/sakurakit/skautorun.h @@ -0,0 +1,42 @@ +#ifndef SKAUTORUN_H +#define SKAUTORUN_H + +// skautorun.h +// 9/30/2012 jichi + +#include "sakurakit/skglobal.h" +#include + +SK_BEGIN_NAMESPACE + +class SkAutoRun +{ +public: + typedef std::function function_type; + SkAutoRun(const function_type &start, const function_type &exit) + : exit_(exit) { start(); } + ~SkAutoRun() { exit_(); } +private: + function_type exit_; +}; + +class SkAutoRunAtStartup +{ +public: + typedef SkAutoRun::function_type function_type; + explicit SkAutoRunAtStartup(const function_type &start) { start(); } +}; + +class SkAutoRunAtExit +{ +public: + typedef SkAutoRun::function_type function_type; + explicit SkAutoRunAtExit(const function_type &exit) : exit_(exit) {} + ~SkAutoRunAtExit() { exit_(); } +private: + function_type exit_; +}; + +SK_END_NAMESPACE + +#endif // SkAUTORUN_H diff --git a/vnr/sakurakit/skdebug.h b/vnr/sakurakit/skdebug.h new file mode 100644 index 0000000..b1446a6 --- /dev/null +++ b/vnr/sakurakit/skdebug.h @@ -0,0 +1,46 @@ +#ifndef SKDEBUG_H +#define SKDEBUG_H + +// skdebug.h +// 10/16/2011 jichi +// Macros for debug. + +// Debug I/O +// - DPRINT: similar to fprintf, or KDPrint. Print to qDebug +// - DOUT: similar to std::cout. Print to qDebug +// - DERR: similar to std::cerr. Print to qWarning +#if defined(DEBUG) && !defined(SK_NO_DEBUG) + +# if defined(QT_CORE_LIB) && !defined(SK_NO_QT) +# include +# define DPRINT(...) qDebug(QString("%1:%2:").arg((DEBUG), (__FUNCTION__)).toLocal8Bit().constData(), __VA_ARGS__) +# define DOUT(_msg) qDebug() << QString("%1:%2:").arg((DEBUG), (__FUNCTION__)).toLocal8Bit().constData() << _msg +# define DERR(_msg) qWarning() << QString("%1:%2:").arg((DEBUG), (__FUNCTION__)).toLocal8Bit().constData() << _msg +# else +# include +# include +# define DPRINT(...) fprintf(stderr, DEBUG ":" __FUNCTION__ ": " __VA_ARGS__) +# define DWPRINT(...) fwprintf(stderr, DEBUG ":" __FUNCTION__ ": " __VA_ARGS__) +# define DOUT(_msg) std::cout << DEBUG << ":" << __FUNCTION__ << ": " << _msg << std::endl +# define DWOUT(_msg) std::wcout << DEBUG << ":" << __FUNCTION__ << ": " << _msg << std::endl +# define DERR(_msg) std::cerr << DEBUG << ":" << __FUNCTION__ << ": " << _msg << std::endl +# define DWERR(_msg) std::wcerr << DEBUG << ":" << __FUNCTION__ << ": " << _msg << std::endl +# endif // QT_CORE_LIB + +#else // DEBUG + +# define DPRINT(_dummy) (void)0 +# define DOUT(_dummy) (void)0 +# define DERR(_dummy) (void)0 + +//#ifdef _MSC_VER +//# pragma warning (disable:4390) // C4390: empty controlled statement found: is this the intent? +//#endif // _MSC_VER + +//#ifdef __GNUC__ +//# pragma GCC diagnostic ignored "-Wempty-body" // empty body in an if or else statement +//#endif // __GNUC__ + +#endif // DEBUG + +#endif // SKDEBUG_H diff --git a/vnr/sakurakit/skglobal.h b/vnr/sakurakit/skglobal.h new file mode 100644 index 0000000..233b089 --- /dev/null +++ b/vnr/sakurakit/skglobal.h @@ -0,0 +1,94 @@ +#ifndef SKGLOBAL_H +#define SKGLOBAL_H + +// skglobal.h +// 9/15/2012 jichi +// Similar to QtGlobal from Qt. +// +// Conventions: +// - All classes in sakurakit will be wrapped with SK_BEGIN_NAMESPACE and SK_END_NAMESPACE +// - All classes from sakurakit begin with Sk, such as SkClassA. +// All functions from sakurakit begin with sk, such as skFuncA. + +// Redefine SK_BEGIN_NAMESPACE/SK_END_NAMESPACE if need custom namespace +#ifndef SK_BEGIN_NAMESPACE +# define SK_BEGIN_NAMESPACE namespace Sk { +#endif +#ifndef SK_END_NAMESPACE +# define SK_END_NAMESPACE } // namespace Sk +#endif + +#define SK_FORWARD_DECLARE_CLASS(_name) SK_BEGIN_NAMESPACE class _name; SK_END_NAMESPACE +#define SK_FORWARD_DECLARE_STRUCT(_name) SK_BEGIN_NAMESPACE struct _name; SK_END_NAMESPACE + +SK_BEGIN_NAMESPACE +namespace Sk {} +SK_END_NAMESPACE + +// In case Qt is not avaliable + +//inline void sk_noop(void) {} +// +//template +//inline void skUnused(T &x) { (void)x; } + +#define SK_UNUSED(_var) (void)(_var) +#define SK_NOP SK_UNUSED(0) + +// same as Q_DISABLE_COPY and boost::noncopyable +// Disable when BOOST_PYTHON is enabled +#ifdef BOOST_PYTHON +# define SK_DISABLE_COPY(_class) +#else +# define SK_DISABLE_COPY(_class) \ + _class(const _class &); \ + _class &operator=(const _class &); +#endif // BOOST_PYTHON + +// - Qt-like Pimp - + +// Similar to QT_DECLARE_PRIVATE +#define SK_DECLARE_PRIVATE(_class) \ + friend class _class; \ + typedef _class D; \ + D *const d_; + +// Similar to QT_DECLARE_PUBLIC +#define SK_DECLARE_PUBLIC(_class) \ + friend class _class; \ + typedef _class Q; \ + Q *const q_; + +// - Self and Base - + +#define SK_CLASS(_self) \ + typedef _self Self; \ + Self *self() const { return const_cast(this); } + +#define SK_EXTEND_CLASS(_self, _base) \ + SK_CLASS(_self) \ + typedef _base Base; + +#define SK_UNDEF_POS QPoint(-1, -1) +#define SK_UNDEF_POSF QPointF(-1, -1) + +// - QWidget Style Class for QSS - + +// Read-only property +#define SK_STYLE_CLASS(_class) \ + Q_PROPERTY(QString class READ styleClass) \ + public: \ + QString styleClass() const { return #_class; } \ + private: + +// Read-write property +#define SK_SYNTHESIZE_STYLE_CLASS \ + Q_PROPERTY(QString class READ styleClass WRITE setStyleClass) \ + QString styleClass_; \ + public: \ + QString styleClass() const { return styleClass_; } \ + public slots: \ + void seStyleClass(const QString &value) { styleClass_ = value; } \ + private: + +#endif // SKGLOBAL_H diff --git a/vnr/sakurakit/skhash.h b/vnr/sakurakit/skhash.h new file mode 100644 index 0000000..68ebec6 --- /dev/null +++ b/vnr/sakurakit/skhash.h @@ -0,0 +1,59 @@ +#ifndef SKHASH_H +#define SKHASH_H + +// skhash.h +// 8/1/2011 + +#include "sakurakit/skglobal.h" +#include + +SK_BEGIN_NAMESPACE + +enum : quint64 { djb2_hash0 = 5381 }; + +/// djb2: h = h*33 + c +inline quint64 djb2(const quint8 *str, quint64 hash = djb2_hash0) +{ + quint8 c; + while ((c = *str++)) + hash = ((hash << 5) + hash) + c; // hash * 33 + c + return hash; +} + +/// s: signed char +inline quint64 djb2_s(const char *str, quint64 hash = djb2_hash0) +{ + char c; + while ((c = *str++)) + hash = ((hash << 5) + hash) + c; // hash * 33 + c + return hash; +} + +/// n: length +inline quint64 djb2_n(const quint8 *str, size_t len, quint64 hash = djb2_hash0) +{ + while (len--) + hash = ((hash << 5) + hash) + (*str++); // hash * 33 + c + return hash; +} + +/// sdbm: hash(i) = hash(i - 1) * 65599 + str[i]; +inline quint64 sdbm(const quint8 *str, quint64 hash = 0) +{ + quint8 c; + while ((c = *str++)) + hash = c + (hash << 6) + (hash << 16) - hash; + return hash; +} + +inline quint64 loselose(const quint8 *str, quint64 hash = 0) +{ + quint8 c; + while ((c = *str++)) + hash += c; + return hash; +} + +SK_END_NAMESPACE + +#endif // SKHASH_H diff --git a/vnr/texthook/growl.h b/vnr/texthook/growl.h new file mode 100644 index 0000000..a99b5b2 --- /dev/null +++ b/vnr/texthook/growl.h @@ -0,0 +1,85 @@ +#pragma once + +// growl.h +// 9/17/2013 jichi + +//#ifdef GROWL_HAS_GROWL + +#include +#include + +#define GROWL_MSG_A(_msg) MessageBoxA(nullptr, _msg, "VNR Message", MB_OK) +#define GROWL_MSG(_msg) MessageBoxW(nullptr, _msg, L"VNR Message", MB_OK) +#define GROWL_WARN(_msg) MessageBoxW(nullptr, _msg, L"VNR Warning", MB_OK) +#define GROWL_ERROR(_msg) MessageBoxW(nullptr, _msg, L"VNR Error", MB_OK) + +inline void GROWL_DWORD(DWORD value) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD: %x", value); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD2(DWORD v, DWORD v2) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD2: %x,%x", v, v2); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD3(DWORD v, DWORD v2, DWORD v3) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD3: %x,%x,%x", v, v2, v3); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD4(DWORD v, DWORD v2, DWORD v3, DWORD v4) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD4: %x,%x,%x,%x", v, v2, v3, v4); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD5(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD5: %x,%x,%x,%x,%x", v, v2, v3, v4, v5); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD6(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5, DWORD v6) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD6: %x,%x,%x,%x,%x,%x", v, v2, v3, v4, v5, v6); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD7(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5, DWORD v6, DWORD v7) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD7: %x,%x,%x,%x,%x,%x,%x", v, v2, v3, v4, v5, v6, v7); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD8(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5, DWORD v6, DWORD v7, DWORD v8) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD8: %x,%x,%x,%x,%x,%x,%x,%x", v, v2, v3, v4, v5, v6, v7, v8); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD9(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5, DWORD v6, DWORD v7, DWORD v8, DWORD v9) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD9: %x,%x,%x,%x,%x,%x,%x,%x,%x", v, v2, v3, v4, v5, v6, v7, v8, v9); + GROWL_MSG(buf); +} + +inline void GROWL(DWORD v) { GROWL_DWORD(v); } +inline void GROWL(LPCWSTR v) { GROWL_MSG(v); } +inline void GROWL(LPCSTR v) { GROWL_MSG_A(v); } + +//#endif // GROWL_HAS_GROWL + +// EOF diff --git a/vnr/texthook/host/CMakeLists.txt b/vnr/texthook/host/CMakeLists.txt new file mode 100644 index 0000000..add5052 --- /dev/null +++ b/vnr/texthook/host/CMakeLists.txt @@ -0,0 +1,69 @@ +# texthook.pro +# CONFIG += noqtgui dll #eha # eha will catch all exceptions, but does not work on Windows XP + +# DEFINES += ITH_HAS_CRT # Use native CRT + +# # TODO: Get rid of dependence on msvc's swprintf +# DEFINES += _CRT_NON_CONFORMING_SWPRINTFS + +set(vnrhost_src + avl_p.h + config.h + hookman.h + host.h + host_p.h + settings.h + textthread.h + textthread_p.h + hookman.cc + host.cc + pipe.cc + textthread.cc + ${PROJECT_SOURCE_DIR}/winmaker/winmaker.h + ${PROJECT_SOURCE_DIR}/winmaker/winmaker.cc + ${PROJECT_SOURCE_DIR}/winmutex/winmutex.h +# ${PROJECT_SOURCE_DIR}/wintimer/wintimer.h +# ${PROJECT_SOURCE_DIR}/wintimer/wintimer.cc +# ${PROJECT_SOURCE_DIR}/wintimer/wintimerbase.cc +# ${PROJECT_SOURCE_DIR}/wintimer/wintimerbase.h + ${PROJECT_SOURCE_DIR}/windbg/windbg.h + ${PROJECT_SOURCE_DIR}/windbg/windbg_p.h + ${PROJECT_SOURCE_DIR}/windbg/inject.h + ${PROJECT_SOURCE_DIR}/windbg/inject.cc + ${PROJECT_SOURCE_DIR}/windbg/hijack.h + ${PROJECT_SOURCE_DIR}/windbg/hijack.cc + ${PROJECT_SOURCE_DIR}/windbg/util.h +# ${PROJECT_SOURCE_DIR}/windbg/util.cc + ${PROJECT_SOURCE_DIR}/windbg/unload.h + ${PROJECT_SOURCE_DIR}/windbg/unload.cc + ${PROJECT_SOURCE_DIR}/sakurakit/skdebug.h +) + +add_library(vnrhost SHARED ${vnrhost_src}) + +set_target_properties(vnrhost PROPERTIES LINK_FLAGS /SUBSYSTEM:WINDOWS) + +target_compile_options(vnrhost PRIVATE +# /GR- + $<$:> + $<$:> +) + +#STRING(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) + +target_link_libraries(vnrhost + ithsys + profile + ${WDK_HOME}/lib/wxp/i386/ntdll.lib +) + +target_compile_definitions(vnrhost + PRIVATE + ITH_HAS_CRT + _CRT_NON_CONFORMING_SWPRINTFS +) + +install(TARGETS vnrhost RUNTIME + DESTINATION . + CONFIGURATIONS Release +) diff --git a/vnr/texthook/host/avl_p.h b/vnr/texthook/host/avl_p.h new file mode 100644 index 0000000..5f06449 --- /dev/null +++ b/vnr/texthook/host/avl_p.h @@ -0,0 +1,588 @@ +#pragma once +// avl_p.h +// 8/23/2013 jichi +// Branch: ITH/AVL.h, rev 133 + +#include "config.h" +#include + +enum { STACK_SIZE = 32 }; + +//#ifndef ITH_STACK +//#define ITH_STACK + +template +class MyStack +{ + int index; + T s[stack_size]; + +public: + MyStack(): index(0) + { ::memset(s, 0, sizeof(s)); } // jichi 9/21/2013: assume T is atomic type + + T &back() { return s[index-1]; } + int size() { return index; } + + void push_back(const T &e) + { + if (index < stack_size) + s[index++]=e; + } + + void pop_back() { index--; } + + T &operator[](int i) { return s[i]; } +}; +//#endif // ITH_STACK + +// jichi 9/22/2013: T must be a pointer type which can be deleted +template +struct IHFSERVICE TreeNode +{ + //typedef TreeNode Self; + TreeNode() : + Left(nullptr), Right(nullptr), Parent(nullptr) + , rank(1) + , factor('\0'), reserve('\0') + //, key() + //, data() + { + ::memset(&key, 0, sizeof(key)); // jcihi 9/26/2013: zero memory + ::memset(&data, 0, sizeof(data)); // jcihi 9/26/2013: zero memory + } + + TreeNode(const T &k, const D &d) : + Left(nullptr), Right(nullptr), Parent(nullptr) + , rank(1) + , factor('\0'), reserve('\0') // jichi 9/21/2013: zero reserve + , key(k) + , data(d) + {} + + TreeNode *Successor() + { + TreeNode *Node, + *ParentNode; + Node = Right; + if (!Node) { + Node = this; + for (;;) { + ParentNode = Node->Parent; + if (!ParentNode) + return nullptr; + if (ParentNode->Left == Node) + break; + Node = ParentNode; + } + return ParentNode; + } + else + while (Node->Left) + Node = Node->Left; + return Node; + } + TreeNode *Predecessor() + { + TreeNode *Node, + *ParentNode; + Node = Left; + if (!Node) { + Node = this; + for(;;) { + ParentNode = Node->Parent; + if (!ParentNode) + return nullptr; + if (ParentNode->Right == Node) + break; + Node = ParentNode; + } + return ParentNode; + } + else + while (Node->Right) + Node = Node->Right; + return Node; + } + int height() + { + if (!this) // jichi 9/26/2013: what?! + return 0; + int l = Left->height(), + r = Right->height(), + f = factor; + if (l - r + f != 0) + __debugbreak(); + f = l > r ? l : r; + return f + 1; + } + TreeNode *Left, + *Right, + *Parent; + unsigned short rank; + char factor, + reserve; + T key; + D data; +}; + +template +struct NodePath +{ + NodePath() { ::memset(this, 0, sizeof(NodePath)); } // jichi 11/30/2013: This is the original code in ITH + NodePath(TreeNode *n, int f): Node(n), fact(f) {} + TreeNode *Node; + union { char factor; int fact; }; +}; + +template +class AVLTree +{ +protected: + TreeNode head; + fComp fCmp; + fCopy fCpy; + fLength fLen; + +public: + // - Construction - + AVLTree() {} + + virtual ~AVLTree() { DeleteAll(); } + + // - Properties - + + TreeNode *TreeRoot() const { return head.Left; } + + // - Actions - + + void DeleteAll() + { + while (head.Left) + DeleteRoot(); + } + + TreeNode *Insert(const T *key, const D &data) + { + if (head.Left) { + MyStack *,STACK_SIZE> path; + TreeNode *DownNode, *ParentNode, *BalanceNode, *TryNode, *NewNode; //P,T,S,Q + ParentNode = &head; + path.push_back(ParentNode); + char factor,f; + BalanceNode = DownNode = head.Left; + for (;;) { //The first part of AVL tree insert. Just do as binary tree insert routine and record some nodes. + factor = fCmp(key,DownNode->key); + if (factor == 0) + return DownNode; //Duplicate key. Return and do nothing. + TryNode = _FactorLink(DownNode, factor); + if (factor == -1) + path.push_back(DownNode); + if (TryNode) { //DownNode has a child. + if (TryNode->factor != 0) { //Keep track of unbalance node and its parent. + ParentNode = DownNode; + BalanceNode = TryNode; + } + DownNode = TryNode; + } + else + break; //Finished binary tree search; + } + while (path.size()) { + path.back()->rank++; + path.pop_back(); + } + size_t sz = fLen(key) + 1; + T *new_key = new T[sz]; + ::memset(new_key, 0, sz * sizeof(T)); // jichi 9/26/2013: Zero memory + fCpy(new_key, key); + TryNode = new TreeNode(new_key, data); + _FactorLink(DownNode, factor) = TryNode; + TryNode->Parent = DownNode; + NewNode = TryNode; + //Finished binary tree insert. Next to do is to modify balance factors between + //BalanceNode and the new node. + TreeNode *ModifyNode; + factor = fCmp(key, BalanceNode->key); + //factor=keykey ? factor=-1:1; //Determine the balance factor at BalanceNode. + ModifyNode = DownNode = _FactorLink(BalanceNode,factor); + //ModifyNode will be the 1st child. + //DownNode will travel from here to the recent inserted node (TryNode). + while (DownNode != TryNode) { //Check if we reach the bottom. + f = fCmp(key,DownNode->key); + //f=_FactorCompare(key,DownNode->key); + DownNode->factor = f; + DownNode = _FactorLink(DownNode, f);//Modify balance factor and travels down. + } + //Finshed modifying balance factor. + //Next to do is check the tree if it's unbalance and recover balance. + if (BalanceNode->factor == 0) { //Tree has grown higher. + BalanceNode->factor = factor; + _IncreaseHeight(); //Modify balance factor and increase the height. + return NewNode; + } + if (BalanceNode->factor + factor == 0) { //Tree has gotten more balanced. + BalanceNode->factor = 0; //Set balance factor to 0. + return NewNode; + } + //Tree has gotten out of balance. + if (ModifyNode->factor == factor) //A node and its child has same factor. Single rotation. + DownNode = _SingleRotation(BalanceNode, ModifyNode, factor); + else //A node and its child has converse factor. Double rotation. + DownNode = _DoubleRotation(BalanceNode, ModifyNode, factor); + //Finished the balancing work. Set child field to the root of the new child tree. + if (BalanceNode == ParentNode->Left) + ParentNode->Left = DownNode; + else + ParentNode->Right = DownNode; + return NewNode; + } + else { //root null? + size_t sz = fLen(key) + 1; + T *new_key = new T[sz]; + ::memset(new_key, 0, sz * sizeof(T)); // jichi 9/26/2013: Zero memory + fCpy(new_key, key); + head.Left = new TreeNode(new_key, data); + head.rank++; + _IncreaseHeight(); + return head.Left; + } + } + bool Delete(T *key) + { + NodePath PathNode; + MyStack,STACK_SIZE> path; //Use to record a path to the destination node. + path.push_back(NodePath(&head,-1)); + TreeNode *TryNode,*ChildNode,*BalanceNode,*SuccNode; + TryNode=head.Left; + char factor; + for (;;) { //Search for the + if (TryNode == 0) + return false; //Not found. + factor = fCmp(key, TryNode->key); + if (factor == 0) + break; //Key found, continue to delete. + //factor = _FactorCompare( key, TryNode->key ); + path.push_back(NodePath(TryNode,factor)); + TryNode = _FactorLink(TryNode,factor); //Move to left. + } + SuccNode = TryNode->Right; //Find a successor. + factor = 1; + if (SuccNode == 0) { + SuccNode = TryNode->Left; + factor = -1; + } + path.push_back(NodePath(TryNode,factor)); + while (SuccNode) { + path.push_back(NodePath(SuccNode, -factor)); + SuccNode = _FactorLink(SuccNode,-factor); + } + PathNode = path.back(); + delete[] TryNode->key; // jichi 9/22/2013: key is supposed to be an array + TryNode->key = PathNode.Node->key; //Replace key and data field with the successor or predecessor. + PathNode.Node->key = nullptr; + TryNode->data = PathNode.Node->data; + path.pop_back(); + _FactorLink(path.back().Node,path.back().factor) = _FactorLink(PathNode.Node,-PathNode.factor); + delete PathNode.Node; //Remove the successor from the tree and release memory. + PathNode = path.back(); + for (int i=0; irank--; + for (;;) { //Rebalance the tree along the path back to the root. + if (path.size()==1) { + _DecreaseHeight(); + break; + } + BalanceNode = PathNode.Node; + if (BalanceNode->factor == 0) { // A balance node, just need to adjust the factor. Don't have to recurve since subtree height stays. + BalanceNode->factor=-PathNode.factor; + break; + } + if (BalanceNode->factor == PathNode.factor) { // Node get more balance. Subtree height decrease, need to recurve. + BalanceNode->factor = 0; + path.pop_back(); + PathNode = path.back(); + continue; + } + //Node get out of balance. Here raises 3 cases. + ChildNode = _FactorLink(BalanceNode, -PathNode.factor); + if (ChildNode->factor == 0) { // New case different to insert operation. + TryNode = _SingleRotation2( BalanceNode, ChildNode, BalanceNode->factor ); + path.pop_back(); + PathNode = path.back(); + _FactorLink(PathNode.Node, PathNode.factor) = TryNode; + break; + } + else { + if (ChildNode->factor == BalanceNode->factor) // Analogous to insert operation case 1. + TryNode = _SingleRotation( BalanceNode, ChildNode, BalanceNode->factor ); + else if (ChildNode->factor + BalanceNode->factor == 0) // Analogous to insert operation case 2. + TryNode = _DoubleRotation( BalanceNode, ChildNode, BalanceNode->factor ); + } + path.pop_back(); //Recurse back along the path. + PathNode = path.back(); + _FactorLink(PathNode.Node, PathNode.factor) = TryNode; + } + return true; + } + + D &operator [](T *key) + { return (Insert(key,D())->data); } + + TreeNode *Search(const T *key) + { + TreeNode *Find=head.Left; + char k; + while (Find != 0) {//&&Find->key!=key) + k = fCmp(key, Find->key); + if (k == 0) break; + Find = _FactorLink(Find, k); + } + return Find; + } + + TreeNode *SearchIndex(unsigned int rank) + { + unsigned int r = head.rank; + if (rank == -1) + return 0; + if (++rank>=r) + return 0; + TreeNode *n=&head; + while (r!=rank) { + if (rank>r) { + n=n->Right; + rank-=r; + r=n->rank; + } else { + n=n->Left; + r=n->rank; + } + } + return n; + } + + TreeNode *Begin() + { + TreeNode *Node = head.Left; + if (Node) + while (Node->Left) Node = Node->Left; + return Node; + } + + TreeNode *End() + { + TreeNode *Node=head.Left; + if (Node) + while (Node->Right) Node = Node->Right; + return Node; + } + unsigned int Count() const { return head.rank - 1; } + + template + Fn TraverseTree(Fn &f) + { return TraverseTreeNode(head.Left,f); } + +protected: + bool DeleteRoot() + { + NodePath PathNode; + MyStack,STACK_SIZE> path; //Use to record a path to the destination node. + path.push_back(NodePath(&head,-1)); + TreeNode *TryNode,*ChildNode,*BalanceNode,*SuccNode; + TryNode=head.Left; + char factor; + SuccNode=TryNode->Right; //Find a successor. + factor=1; + if (SuccNode==0) + { + SuccNode=TryNode->Left; + factor=-1; + } + path.push_back(NodePath(TryNode,factor)); + while (SuccNode) { + path.push_back(NodePath(SuccNode,-factor)); + SuccNode=_FactorLink(SuccNode,-factor); + } + PathNode=path.back(); + delete[] TryNode->key; // jichi 9/22/2013: key is supposed to be an array + TryNode->key=PathNode.Node->key; //Replace key and data field with the successor. + PathNode.Node->key = nullptr; + TryNode->data=PathNode.Node->data; + path.pop_back(); + _FactorLink(path.back().Node,path.back().factor) = _FactorLink(PathNode.Node,-PathNode.factor); + delete PathNode.Node; //Remove the successor from the tree and release memory. + PathNode=path.back(); + for (int i=0;irank--; + for (;;) { //Rebalance the tree along the path back to the root. + if (path.size() == 1) { + _DecreaseHeight(); + break; + } + + BalanceNode = PathNode.Node; + if (BalanceNode->factor == 0) { // A balance node, just need to adjust the factor. Don't have to recurse since subtree height not changed. + BalanceNode->factor=-PathNode.factor; + break; + } + if (BalanceNode->factor==PathNode.factor) { // Node get more balance. Subtree height decrease, need to recurse. + BalanceNode->factor=0; + path.pop_back(); + PathNode=path.back(); + continue; + } + //Node get out of balance. Here raises 3 cases. + ChildNode = _FactorLink(BalanceNode, -PathNode.factor); + if (ChildNode->factor == 0) { // New case different to insert operation. + TryNode = _SingleRotation2( BalanceNode, ChildNode, BalanceNode->factor ); + path.pop_back(); + PathNode=path.back(); + _FactorLink(PathNode.Node, PathNode.factor) = TryNode; + break; + } else { + if (ChildNode->factor == BalanceNode->factor) // Analogous to insert operation case 1. + TryNode = _SingleRotation( BalanceNode, ChildNode, BalanceNode->factor ); + else if (ChildNode->factor + BalanceNode->factor == 0) // Analogous to insert operation case 2. + TryNode = _DoubleRotation( BalanceNode, ChildNode, BalanceNode->factor ); + } + path.pop_back(); // Recurve back along the path. + PathNode=path.back(); + _FactorLink(PathNode.Node, PathNode.factor) = TryNode; + } + return true; + } + template + Fn TraverseTreeNode(TreeNode *Node, Fn &f) + { + if (Node) { + if (Node->Left) + TraverseTreeNode(Node->Left,f); + f(Node); + if (Node->Right) + TraverseTreeNode(Node->Right,f); + } + return f; + } + TreeNode *_SingleRotation(TreeNode *BalanceNode, TreeNode *ModifyNode, char factor) + { + TreeNode *Node = _FactorLink(ModifyNode, -factor); + _FactorLink(BalanceNode, factor) = Node; + _FactorLink(ModifyNode, -factor) = BalanceNode; + if (Node) + Node->Parent = BalanceNode; + ModifyNode->Parent = BalanceNode->Parent; + BalanceNode->Parent = ModifyNode; + BalanceNode->factor = ModifyNode->factor = 0; //After single rotation, set all factor of 3 node to 0. + if (factor == 1) + ModifyNode->rank += BalanceNode->rank; + else + BalanceNode->rank -= ModifyNode->rank; + return ModifyNode; + } + TreeNode *_SingleRotation2(TreeNode *BalanceNode, TreeNode *ModifyNode, char factor) + { + TreeNode *Node = _FactorLink(ModifyNode, -factor); + _FactorLink(BalanceNode, factor) = Node; + _FactorLink(ModifyNode, -factor) = BalanceNode; + if (Node) Node->Parent = BalanceNode; + ModifyNode->Parent = BalanceNode->Parent; + BalanceNode->Parent = ModifyNode; + ModifyNode->factor = -factor; + if (factor == 1) + ModifyNode->rank+=BalanceNode->rank; + else + BalanceNode->rank-=ModifyNode->rank; + return ModifyNode; + } + TreeNode *_DoubleRotation(TreeNode *BalanceNode, TreeNode *ModifyNode, char factor) + { + TreeNode *DownNode = _FactorLink(ModifyNode, -factor); + TreeNode *Node1, *Node2; + Node1 = _FactorLink(DownNode, factor); + Node2 = _FactorLink(DownNode, -factor); + _FactorLink(ModifyNode, -factor) = Node1; + _FactorLink(DownNode, factor) = ModifyNode; + _FactorLink(BalanceNode, factor) = Node2; + _FactorLink(DownNode, -factor) = BalanceNode; + if (Node1) + Node1->Parent = ModifyNode; + if (Node2) + Node2->Parent = BalanceNode; + DownNode->Parent = BalanceNode->Parent; + BalanceNode->Parent = DownNode; + ModifyNode->Parent = DownNode; + //Set factor according to the result. + if (DownNode->factor == factor) { + BalanceNode->factor = -factor; + ModifyNode->factor = 0; + } else if (DownNode->factor == 0) + BalanceNode->factor = ModifyNode->factor = 0; + else { + BalanceNode->factor = 0; + ModifyNode->factor = factor; + } + DownNode->factor = 0; + if (factor==1) { + ModifyNode->rank -= DownNode->rank; + DownNode->rank += BalanceNode->rank; + } else { + DownNode->rank += ModifyNode->rank; + BalanceNode->rank -= DownNode->rank; + } + return DownNode; + } + + TreeNode* &__fastcall _FactorLink(TreeNode *Node, char factor) + //Private helper method to retrieve child according to factor. + //Return right child if factor>0 and left child otherwise. + { return factor>0? Node->Right : Node->Left; } + + void Check() + { + unsigned int k = (unsigned int)head.Right; + unsigned int t = head.Left->height(); + if (k != t) + __debugbreak(); + } + + void _IncreaseHeight() + { + unsigned int k = (unsigned int)head.Right; + head.Right = (TreeNode*)++k; + } + + void _DecreaseHeight() + { + unsigned int k = (unsigned int)head.Right; + head.Right = (TreeNode*)--k; + } +}; + +struct SCMP +{ + char operator()(const char *s1,const char *s2) + { + int t = _stricmp(s1, s2); + return t == 0 ? 0 : t > 0 ? 1 :-1; + } +}; + +struct SCPY { char *operator()(char *dest, const char *src) { return strcpy(dest, src); } }; +struct SLEN { int operator()(const char *str) { return strlen(str); } }; + +struct WCMP +{ + char operator()(const wchar_t *s1,const wchar_t *s2) + { + int t =_wcsicmp(s1, s2); + return t == 0 ? 0 : t > 0 ? 1 : -1; + } +}; + +struct WCPY { wchar_t *operator()(wchar_t *dest, const wchar_t *src) { return wcscpy(dest,src); } }; +struct WLEN { int operator()(const wchar_t *str) { return wcslen(str); } }; + +// EOF diff --git a/vnr/texthook/host/config.h b/vnr/texthook/host/config.h new file mode 100644 index 0000000..6262cf1 --- /dev/null +++ b/vnr/texthook/host/config.h @@ -0,0 +1,16 @@ +#pragma once + +// config.h +// 8/23/2013 jichi +// The first header file that are included by all source files. + +#define IHF // for dll import +//#include "ith/dllconfig.h" +#define IHFAPI __stdcall +#ifdef IHF +# define IHFSERVICE __declspec(dllexport) +#else +# define IHFSERVICE __declspec(dllimport) +#endif + +// EOF diff --git a/vnr/texthook/host/hookman.cc b/vnr/texthook/host/hookman.cc new file mode 100644 index 0000000..65c46b5 --- /dev/null +++ b/vnr/texthook/host/hookman.cc @@ -0,0 +1,1039 @@ +// hookman.cc +// 8/24/2013 jichi +// Branch IHF/HookManager.cpp, rev 133 +// 8/24/2013 TODO: Clean up this file + +#ifdef _MSC_VER +# pragma warning (disable:4100) // C4100: unreference formal parameter +# pragma warning (disable:4146) // C4146: unary minus operator applied to unsigned type +#endif // _MSC_VER + +#include "hookman.h" +#include "vnrhook/include/const.h" +#include "vnrhook/include/defs.h" +#include "vnrhook/include/types.h" +#include "ithsys/ithsys.h" +#include +//#include +#include "profile/Profile.h" +#include "profile/pugixml.hpp" +#include "profile/misc.h" + +#define DEBUG "vnrhost/hookman.cc" +#include "sakurakit/skdebug.h" + +namespace { // unnamed +//enum { MAX_ENTRY = 0x40 }; + +#define HM_LOCK win_mutex_lock d_locker(hmcs) // Synchronized scope for accessing private data +// jichi 9/23/2013: wine deficenciy on mapping sections +// Whe set to false, do not map sections. +//bool ith_has_section = true; + +// jichi 9/28/2013: Remove ConsoleOutput from available hooks +//LPWSTR HookNameInitTable[]={ L"ConsoleOutput" , HOOK_FUN_NAME_LIST }; +//LPCWSTR HookNameInitTable[] = {HOOK_FUN_NAME_LIST}; +//LPVOID DefaultHookAddr[HOOK_FUN_COUNT]; + +//BYTE null_buffer[4]={0,0,0,0}; +//BYTE static_small_buffer[0x100]; +//DWORD zeros[4]={0,0,0,0}; +//WCHAR user_entry[0x40]; + +bool GetProcessPath(HANDLE hProc, __out LPWSTR path) +{ + PROCESS_BASIC_INFORMATION info; + LDR_DATA_TABLE_ENTRY entry; + PEB_LDR_DATA ldr; + PEB peb; + if (NT_SUCCESS(NtQueryInformationProcess(hProc, ProcessBasicInformation, &info, sizeof(info), 0))) + if (info.PebBaseAddress) + if (NT_SUCCESS(NtReadVirtualMemory(hProc, info.PebBaseAddress, &peb,sizeof(peb), 0))) + if (NT_SUCCESS(NtReadVirtualMemory(hProc, peb.Ldr, &ldr, sizeof(ldr), 0))) + if (NT_SUCCESS(NtReadVirtualMemory(hProc, (LPVOID)ldr.InLoadOrderModuleList.Flink, + &entry, sizeof(LDR_DATA_TABLE_ENTRY), 0))) + if (NT_SUCCESS(NtReadVirtualMemory(hProc, entry.FullDllName.Buffer, + path, MAX_PATH * 2, 0))) + return true; + path = L""; + return false; +} + +bool GetProcessPath(DWORD pid, __out LPWSTR path) +{ + CLIENT_ID id; + OBJECT_ATTRIBUTES oa = {}; + HANDLE hProc; + id.UniqueProcess = pid; + id.UniqueThread = 0; + oa.uLength = sizeof(oa); + if (NT_SUCCESS(NtOpenProcess(&hProc , PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, &oa, &id))) { + bool flag = GetProcessPath(hProc, path); + NtClose(hProc); + return flag; + } + path = L""; + return false; +} + +} // unnamed namespace + +HookManager *man; // jichi 9/22/2013: initialized in main +//BitMap* pid_map; +DWORD clipboard_flag, + split_time, + repeat_count, + global_filter, + cyclic_remove; + +DWORD GetHookName(LPSTR str, DWORD pid, DWORD hook_addr, DWORD max) +{ + if (!pid) + return 0; + + DWORD len = 0; + max--; //for '\0' magic marker. + + //if (pid == 0) { + // len = wcslen(HookNameInitTable[0]); + // if (len >= max) + // len = max; + // memcpy(str, HookNameInitTable[0], len << 1); + // str[len] = 0; + // return len; + //} + + //::man->LockProcessHookman(pid); + ProcessRecord *pr = ::man->GetProcessRecord(pid); + if (!pr) + return 0; + NtWaitForSingleObject(pr->hookman_mutex, 0, 0); + Hook *hks = (Hook *)pr->hookman_map; + for (int i = 0; i < MAX_HOOK; i++) + if (hks[i].Address() == hook_addr) { + len = hks[i].NameLength(); + if (len >= max) + len = max; + NtReadVirtualMemory(pr->process_handle, hks[i].Name(), str, len, &len); + if (str[len - 1] == 0) + len--; + else + str[len] = 0; + break; + } + + // jichi 9/27/2013: The hook man should be consistent with the one defined in vnrcli + //Hook *h = (Hook *)hks; + //for (int i = 0; i < MAX_HOOK; i++) + // if (!h[i].hook_name) + // break; + // else { + // const Hook &hi = h[i]; + // wchar_t buffer[1000]; + // DWORD len = hi.NameLength(); + // NtReadVirtualMemory(pr->process_handle, hi.hook_name, buffer, len << 1, &len); + // buffer[len] = 0; + // ITH_MSG(buffer); + // } + + NtReleaseMutant(pr->hookman_mutex, 0); + //::man->UnlockProcessHookman(pid); + return len; +} + +// 7/2/2015 jichi: This function is not used and removed +//int GetHookNameByIndex(LPSTR str, DWORD pid, DWORD index) +//{ +// if (!pid) +// return 0; +// +// //if (pid == 0) { +// // wcscpy(str, HookNameInitTable[0]); +// // return wcslen(HookNameInitTable[0]); +// //} +// DWORD len = 0; +// //::man->LockProcessHookman(pid); +// ProcessRecord *pr = ::man->GetProcessRecord(pid); +// if (!pr) +// return 0; +// //NtWaitForSingleObject(pr->hookman_mutex,0,0); //already locked +// Hook *hks = (Hook *)pr->hookman_map; +// if (hks[index].Address()) { +// NtReadVirtualMemory(pr->process_handle, hks[index].Name(), str, hks[index].NameLength() << 1, &len); +// len = hks[index].NameLength(); +// } +// //NtReleaseMutant(pr->hookman_mutex,0); +// return len; +//} + +//int GetHookString(LPWSTR str, DWORD pid, DWORD hook_addr, DWORD status) +//{ +// LPWSTR begin=str; +// str+=swprintf(str,L"%4d:0x%08X:",pid,hook_addr); +// str+=GetHookName(str,pid,hook_addr); +// return str-begin; +//} + +void ThreadTable::SetThread(DWORD num, TextThread *ptr) +{ + int number = num; + if (number >= size) { + while (number >= size) + size <<= 1; + TextThread **temp; + //if (size < 0x10000) { + temp = new TextThread*[size]; + if (size > used) + ::memset(temp, 0, (size - used) * sizeof(TextThread *)); // jichi 9/21/2013: zero memory + memcpy(temp, storage, used * sizeof(TextThread *)); + //} + delete[] storage; + storage = temp; + } + storage[number] = ptr; + if (ptr == nullptr) { + if (number == used - 1) + while (storage[used - 1] == 0) + used--; + } else if (number >= used) + used = number + 1; +} + +TextThread *ThreadTable::FindThread(DWORD number) +{ return number <= (DWORD)used ? storage[number] : nullptr; } + +static const char sse_table_eq[0x100]={ + -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, //0, compare 1 + -1,-1,1,1, -1,-1,1,1, -1,-1,1,1, -1,-1,1,1, //1, compare 2 + -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, //0, compare 1 + -1,-1,-1,-1, 1,1,1,1, -1,-1,-1,-1, 1,1,1,1, //3, compare 3 + -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, //0, compare 1 + -1,-1,1,1, -1,-1,1,1, -1,-1,1,1, -1,-1,1,1, //1, compare 2 + -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, //0, compare 1 + -1,-1,-1,-1, -1,-1,-1,-1, 1,1,1,1, 1,1,1,1, //7, compare 4 + -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, //0, compare 1 + -1,-1,1,1, -1,-1,1,1, -1,-1,1,1, -1,-1,1,1, //1, compare 2 + -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, //0, compare 1 + -1,-1,-1,-1, 1,1,1,1, -1,-1,-1,-1, 1,1,1,1, //3, compare 3 + -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, //0, compare 1 + -1,-1,1,1, -1,-1,1,1, -1,-1,1,1, -1,-1,1,1, //1, compare 2 + -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, -1,1,-1,1, //0, compare 1 + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 //f, equal +}; +char original_cmp(const ThreadParameter *t1, const ThreadParameter *t2) +{ + //Q_ASSERT(t1 && t2); + int t = t1->pid - t2->pid; + if (t == 0) { + t = t1->hook - t2->hook; + if (t == 0) { + t = t1->retn - t2->retn; + if (t == 0) { + t = t1->spl-t2->spl; + if (t == 0) return 0; + return t1->spl > t2->spl ? 1 : -1; + } + else return t1->retn > t2->retn ? 1 : -1; + } + else return t1->hook > t2->hook ? 1: -1; + } + else return t1->pid > t2->pid ? 1 : -1; + //return t>0?1:-1; +} +char TCmp::operator()(const ThreadParameter* t1, const ThreadParameter* t2) + //SSE speed up. Compare four integers in const time without branching. + //The AVL tree branching operation needs 2 bit of information. + //One bit for equality and one bit for "less than" or "greater than". + +{ + union{__m128 m0;__m128i i0;}; + union{__m128 m1;__m128i i1;}; + union{__m128 m2;__m128i i2;}; + int k0,k1; + i1 = _mm_loadu_si128((const __m128i*)t1); + i2 = _mm_loadu_si128((const __m128i*)t2); + i0 = _mm_cmpgt_epi32(i1,i2); + k0 = _mm_movemask_ps(m0); + i1 = _mm_cmpeq_epi32(i1,i2); + k1 = _mm_movemask_ps(m1); + return sse_table_eq[k1*16+k0]; +} +void TCpy::operator()(ThreadParameter* t1, const ThreadParameter* t2) +{ memcpy(t1,t2,sizeof(ThreadParameter)); } + +int TLen::operator()(const ThreadParameter* t) { return 0; } + +#define NAMED_PIPE_DISCONNECT 1 +//Class member of HookManger +HookManager::HookManager() : + // jichi 9/21/2013: Zero memory + //CRITICAL_SECTION hmcs; + current(nullptr) + , create(nullptr) + , remove(nullptr) + , reset(nullptr) + , attach(nullptr) + , detach(nullptr) + , hook(nullptr) + , current_pid(0) + , thread_table(nullptr) + , destroy_event(nullptr) + , register_count(0) + , new_thread_number(0) +{ + // jichi 9/21/2013: zero memory + ::memset(record, 0, sizeof(record)); + ::memset(text_pipes, 0, sizeof(text_pipes)); + ::memset(cmd_pipes, 0, sizeof(cmd_pipes)); + ::memset(recv_threads, 0, sizeof(recv_threads)); + + head.key = new ThreadParameter; + head.key->pid = 0; + head.key->hook = -1; + head.key->retn = -1; + head.key->spl = -1; + head.data = 0; + thread_table = new ThreadTable; // jichi 9/26/2013: zero memory in ThreadTable + + TextThread *entry = new TextThread(0, -1,-1,-1, new_thread_number++); // jichi 9/26/2013: zero memory in TextThread + thread_table->SetThread(0, entry); + SetCurrent(entry); + entry->Status() |= USING_UNICODE; + //texts->SetUnicode(true); + //entry->AddToCombo(); + //entry->ComboSelectCurrent(); + + //if (background==0) entry->AddToStore((BYTE*)BackgroundMsg,wcslen(BackgroundMsg)<<1,0,1); + + //InitializeCriticalSection(&hmcs); + destroy_event = IthCreateEvent(0, 0, 0); +} + +HookManager::~HookManager() +{ + //LARGE_INTEGER timeout={-1000*1000,-1}; + //IthBreak(); + NtWaitForSingleObject(destroy_event, 0, 0); + NtClose(destroy_event); + NtClose(cmd_pipes[0]); + NtClose(recv_threads[0]); + delete thread_table; + delete head.key; + //DeleteCriticalSection(&hmcs); +} + +TextThread *HookManager::FindSingle(DWORD pid, DWORD hook, DWORD retn, DWORD split) +{ + if (pid == 0) + return thread_table->FindThread(0); + ThreadParameter tp = {pid, hook, retn, split}; + TreeNode *node = Search(&tp); + return node ? thread_table->FindThread(node->data) : nullptr; +} + +TextThread *HookManager::FindSingle(DWORD number) +{ return (number & 0x80008000) ? nullptr : thread_table->FindThread(number); } + +void HookManager::DetachProcess(DWORD pid) {} + +void HookManager::SetCurrent(TextThread *it) +{ + if (current) + current->Status() &= ~CURRENT_SELECT; + current = it; + if (it) + it->Status() |= CURRENT_SELECT; +} +void HookManager::SelectCurrent(DWORD num) +{ + if (TextThread *st = FindSingle(num)) { + SetCurrent(st); + if (reset) + reset(st); + //st->ResetEditText(); + } +} +void HookManager::RemoveSingleHook(DWORD pid, DWORD addr) +{ + HM_LOCK; + //ConsoleOutput("vnrhost:RemoveSingleHook: lock"); + //EnterCriticalSection(&hmcs); + DWORD max = thread_table->Used(); + bool flag = false; + for (DWORD i = 1; i <= max; i++) + if (TextThread *it = thread_table->FindThread(i)) + if (it->PID() == pid && it->Addr() == addr) { + flag |= (it == current); + //flag|=it->RemoveFromCombo(); + thread_table->SetThread(i, 0); + if (it->Number() < new_thread_number) + new_thread_number = it->Number(); + Delete(it->GetThreadParameter()); + if (remove) + remove(it); + delete it; + } + + for (DWORD i = 0; i <= max; i++) + if (TextThread *it = thread_table->FindThread(i)) + if (it->Link() && thread_table->FindThread(it->LinkNumber()) == nullptr) { + it->LinkNumber() = -1; + it->Link() = nullptr; + } + + if (flag) { + current = nullptr; + DWORD number = head.Left ? head.Left->data : 0; + SetCurrent(thread_table->FindThread(number)); + if (reset && current) + reset(current); + //it->ResetEditText(); + } + //LeaveCriticalSection(&hmcs); + //ConsoleOutput("vnrhost:RemoveSingleHook: unlock"); +} +void HookManager::RemoveSingleThread(DWORD number) +{ + if (number == 0) + return; + HM_LOCK; + //ConsoleOutput("vnrhost:RemoveSingleThread: lock"); + //EnterCriticalSection(&hmcs); + if (TextThread *it = thread_table->FindThread(number)) { + thread_table->SetThread(number, 0); + Delete(it->GetThreadParameter()); + if (remove) + remove(it); + bool flag = (it == current); + if (it->Number() < new_thread_number) + new_thread_number = it->Number(); + delete it; + for (int i = 0; i <= thread_table->Used(); i++) + if (TextThread *t = thread_table->FindThread(i)) + if (t->LinkNumber() == number) { + t->Link() = 0; + t->LinkNumber() = -1; + } + + if (flag) { + current = nullptr; + number = head.Left ? head.Left->data : 0; + SetCurrent(thread_table->FindThread(number)); + if (reset && current) + reset(current); + //it->ResetEditText(); + } + } + //LeaveCriticalSection(&hmcs); + //ConsoleOutput("vnrhost:RemoveSingleThread: unlock"); +} + +void HookManager::RemoveProcessContext(DWORD pid) +{ + HM_LOCK; + bool flag = false; + //ConsoleOutput("vnrhost:RemoveProcessContext: lock"); + //EnterCriticalSection(&hmcs); + for (int i = 1; i < thread_table->Used(); i++) + if (TextThread *it = thread_table->FindThread(i)) + if (it->PID() == pid) { + Delete(it->GetThreadParameter()); + //if (false == Delete(it->GetThreadParameter())) { + // // jichi 11/26/2013: Remove debugging instructions + // //if (debug) + // // __asm int 3 + //} + flag |= (it == current); + //flag|=it->RemoveFromCombo(); + if (it->Number() Number(); + thread_table->SetThread(i,0); + if (remove) + remove(it); + delete it; + } + + for (int i = 0; i < thread_table->Used(); i++) + if (TextThread *it=thread_table->FindThread(i)) + if (it->Link() && thread_table->FindThread(it->LinkNumber()) == nullptr) { + it->LinkNumber()=-1; + it->Link() = nullptr; + } + + if (flag) { + current = nullptr; + DWORD number = head.Left ? head.Left->data : 0; + SetCurrent(thread_table->FindThread(number)); + if (reset && current) + reset(current); + //if (it) it->ResetEditText(); + } + //LeaveCriticalSection(&hmcs); + //ConsoleOutput("vnrhost:RemoveProcessContext: unlock"); +} +void HookManager::RegisterThread(TextThread* it, DWORD num) +{ thread_table->SetThread(num, it); } + +void HookManager::RegisterPipe(HANDLE text, HANDLE cmd, HANDLE thread) +{ + text_pipes[register_count] = text; + cmd_pipes[register_count] = cmd; + recv_threads[register_count] = thread; + register_count++; + if (register_count == 1) + NtSetEvent(destroy_event, 0); + else + NtClearEvent(destroy_event); +} +void HookManager::RegisterProcess(DWORD pid, DWORD hookman, DWORD module) +{ + HM_LOCK; + wchar_t str[0x40], + path[MAX_PATH]; + //pid_map->Set(pid>>2); + //ConsoleOutput("vnrhost:RegisterProcess: lock"); + //EnterCriticalSection(&hmcs); + record[register_count - 1].pid_register = pid; + record[register_count - 1].hookman_register = hookman; + record[register_count - 1].module_register = module; + //record[register_count - 1].engine_register = engine; + swprintf(str, ITH_SECTION_ L"%d", pid); + HANDLE hSection = IthCreateSection(str, HOOK_SECTION_SIZE, PAGE_READONLY); + LPVOID map = nullptr; + //DWORD map_size = 0x1000; + DWORD map_size = HOOK_SECTION_SIZE / 2; // jichi 1/16/2015: Changed to half to hook section size + //if (::ith_has_section) + NtMapViewOfSection(hSection, NtCurrentProcess(), + &map, 0, map_size, 0, &map_size, ViewUnmap, 0, + PAGE_READONLY); + + record[register_count - 1].hookman_section = hSection; + record[register_count - 1].hookman_map = map; + + HANDLE hProc; + CLIENT_ID id; + id.UniqueProcess = pid; + id.UniqueThread = 0; + OBJECT_ATTRIBUTES oa = {}; + oa.uLength = sizeof(oa); + if (NT_SUCCESS(NtOpenProcess(&hProc, + PROCESS_QUERY_INFORMATION| + PROCESS_CREATE_THREAD| + PROCESS_VM_READ| + PROCESS_VM_WRITE| + PROCESS_VM_OPERATION, + &oa,&id))) + record[register_count - 1].process_handle = hProc; + else { + DOUT("failed to open process"); + //::man->AddConsoleOutput(ErrorOpenProcess); + //LeaveCriticalSection(&hmcs); + //ConsoleOutput("vnrhost:RegisterProcess: unlock"); + return; + } + + // jichi 9/27/2013: The hook man should be consistent with the one defined in vnrcli + //Hook *h = (Hook *)map; + //for (int i = 0; i < MAX_HOOK; i++) + // if (!h[i].hook_name) + // break; + // else { + // const Hook &hi = h[i]; + // wchar_t buffer[1000]; + // DWORD len = hi.NameLength(); + // NtReadVirtualMemory(hProc, hi.hook_name, buffer, len << 1, &len); + // buffer[len] = 0; + // ITH_MSG(buffer); + // } + + swprintf(str, ITH_HOOKMAN_MUTEX_ L"%d", pid); + record[register_count - 1].hookman_mutex = IthOpenMutex(str); + if (!GetProcessPath(pid, path)) + path[0] = 0; + //swprintf(str,L"%.4d:%s", pid, wcsrchr(path, L'\\') + 1); // jichi 9/25/2013: this is useless? + current_pid = pid; + if (attach) + attach(pid); + //LeaveCriticalSection(&hmcs); + //ConsoleOutput("vnrhost:RegisterProcess: unlock"); +} + +void HookManager::UnRegisterProcess(DWORD pid) +{ + HM_LOCK; + //ConsoleOutput("vnrhost:UnRegisterProcess: lock"); + //EnterCriticalSection(&hmcs); + + int i; + for (i = 0; i < MAX_REGISTER; i++) + if(record[i].pid_register == pid) + break; + + if (i < MAX_REGISTER) { + NtClose(text_pipes[i]); + NtClose(cmd_pipes[i]); + NtClose(recv_threads[i]); + NtClose(record[i].hookman_mutex); + + //if (::ith_has_section) + NtUnmapViewOfSection(NtCurrentProcess(), record[i].hookman_map); + //else + // delete[] record[i].hookman_map; + + NtClose(record[i].process_handle); + NtClose(record[i].hookman_section); + + for (; i < MAX_REGISTER; i++) { + record[i] = record[i+1]; + text_pipes[i] = text_pipes[i+1]; + cmd_pipes[i] = cmd_pipes[i+1]; + recv_threads[i] = recv_threads[i+1]; + if (text_pipes[i] == 0) + break; + } + register_count--; + if (current_pid == pid) + current_pid = register_count ? record[0].pid_register : 0; + RemoveProcessContext(pid); + } + //pid_map->Clear(pid>>2); + + if (register_count == 1) + NtSetEvent(destroy_event, 0); + //LeaveCriticalSection(&hmcs); + //ConsoleOutput("vnrhost:UnRegisterProcess: unlock"); + if (detach) + detach(pid); +} + +// jichi 9/28/2013: I do not need this +//void HookManager::SetName(DWORD type) +//{ +// WCHAR c; +// if (type & PRINT_DWORD) +// c = L'H'; +// else if (type & USING_UNICODE) { +// if (type & STRING_LAST_CHAR) +// c = L'L'; +// else if (type & USING_STRING) +// c = L'Q'; +// else +// c = L'W'; +// } else { +// if (type & USING_STRING) +// c = L'S'; +// else if (type & BIG_ENDIAN) +// c = L'A'; +// else +// c = L'B'; +// } +// //swprintf(user_entry,L"UserHook%c",c); +//} + +void HookManager::AddLink(WORD from, WORD to) +{ + HM_LOCK; + //bool flag=false; + //ConsoleOutput("vnrhost:AddLink: lock"); + //EnterCriticalSection(&hmcs); + TextThread *from_thread = thread_table->FindThread(from), + *to_thread = thread_table->FindThread(to); + if (to_thread && from_thread) { + if (from_thread->GetThreadParameter()->pid != to_thread->GetThreadParameter()->pid) + DOUT("link to different process"); + else if (from_thread->Link()==to_thread) + DOUT("link already exists"); + else if (to_thread->CheckCycle(from_thread)) + DOUT("cyclic link"); + else { + from_thread->Link()=to_thread; + from_thread->LinkNumber()=to; + DOUT("thread linked"); + if (addRemoveLink) + addRemoveLink(from_thread); + //WCHAR str[0x40]; + //swprintf(str,FormatLink,from,to); + //AddConsoleOutput(str); + } + } else + DOUT("error link"); + //else + // AddConsoleOutput(ErrorLink); + //LeaveCriticalSection(&hmcs); + //ConsoleOutput("vnrhost:AddLink: unlock"); +} +void HookManager::UnLink(WORD from) +{ + HM_LOCK; + //bool flag=false; + //ConsoleOutput("vnrhost:UnLink: lock"); + //EnterCriticalSection(&hmcs); + if (TextThread *from_thread = thread_table->FindThread(from)) { + from_thread->Link() = nullptr; + from_thread->LinkNumber() = 0xffff; + DOUT("link deleted"); + if (addRemoveLink) + addRemoveLink(from_thread); + } + //else // jichi 12/25/2013: This could happen when the game exist + // ConsoleOutput("vnrhost:UnLink: thread does not exist"); + //LeaveCriticalSection(&hmcs); + //ConsoleOutput("vnrhost:UnLink: unlock"); +} +void HookManager::UnLinkAll(WORD from) +{ + HM_LOCK; + //bool flag=false; + //ConsoleOutput("vnrhost:UnLinkAll: lock"); + //EnterCriticalSection(&hmcs); + if (TextThread *from_thread = thread_table->FindThread(from)) { + from_thread->UnLinkAll(); + DOUT("link deleted"); + } + //else // jichi 12/25/2013: This could happen after the process exists + // ConsoleOutput("vnrhost:UnLinkAll: thread not exist"); + //AddConsoleOutput(L"Link deleted."); + //} else + // AddConsoleOutput(L"Thread not exist."); + //LeaveCriticalSection(&hmcs); + //ConsoleOutput("vnrhost:UnLinkAll: unlock"); +} + +void HookManager::DispatchText(DWORD pid, const BYTE *text, DWORD hook, DWORD retn, DWORD spl, int len, bool space) +{ + // jichi 20/27/2013: When PID is zero, the text comes from console, which I don't need + if (!text || !pid || (len <= 0 && !space)) + return; + HM_LOCK; + //bool flag=false; + ThreadParameter tp = {pid, hook, retn, spl}; + //ConsoleOutput("vnrhost:DispatchText: lock"); + //EnterCriticalSection(&hmcs); + TextThread *it; + //`try { + if (TreeNode *in = Search(&tp)) { + DWORD number = in->data; + it = thread_table->FindThread(number); + } else if (IsFull()) { // jichi 1/16/2015: Skip adding threads when full + static bool once = true; // output only once + if (once) { + once = false; + DOUT("so many new threads, skip"); + } + return; + } else { // New thread + Insert(&tp, new_thread_number); + it = new TextThread(pid, hook, retn, spl, new_thread_number); + RegisterThread(it, new_thread_number); + DOUT("found new thread"); + char entstr[0x200]; + it->GetEntryString(entstr); + DOUT(entstr); + while (thread_table->FindThread(++new_thread_number)); + if (create) + create(it); + } + if (it) + it->AddText(text, len, false, space); // jichi 10/27/2013: new line is false + //LeaveCriticalSection(&hmcs); + //ConsoleOutput("vnrhost:DispatchText: unlock"); + //} catch (...) { + // // ignored + //} +} + +void HookManager::AddConsoleOutput(LPCWSTR text) +{ + if (text) { + int len = wcslen(text) << 1; + TextThread *console = thread_table->FindThread(0); + //EnterCriticalSection(&hmcs); + console->AddText((BYTE*)text,len,false,true); + console->AddText((BYTE*)L"\r\n",4,false,true); + //LeaveCriticalSection(&hmcs); + } +} + +void HookManager::ClearText(DWORD pid, DWORD hook, DWORD retn, DWORD spl) +{ + HM_LOCK; + //bool flag=false; + //ConsoleOutput("vnrhost:ClearText: lock"); + //EnterCriticalSection(&hmcs); + ThreadParameter tp = {pid, hook, retn, spl}; + if (TreeNode *in = Search(&tp)) + if (TextThread *it = thread_table->FindThread(in->data)) { + it->Reset(); + //SetCurrent(it); + if (reset) + reset(it); + //it->ResetEditText(); + } + + //LeaveCriticalSection(&hmcs); + //ConsoleOutput("vnrhost:ClearText: unlock"); +} +void HookManager::ClearCurrent() +{ + HM_LOCK; + //ConsoleOutput("vnrhost:ClearCurrent: lock"); + //EnterCriticalSection(&hmcs); + if (current) { + current->Reset(); + if (reset) + reset(current); + } + //current->ResetEditText(); + //LeaveCriticalSection(&hmcs); + //ConsoleOutput("vnrhost:ClearCurrent: unlock"); +} +void HookManager::ResetRepeatStatus() +{ + HM_LOCK; + //ConsoleOutput("vnrhost:ResetRepeatStatus: lock"); + //EnterCriticalSection(&hmcs); + for (int i = 1; i < thread_table->Used(); i++) + if (TextThread *it = thread_table->FindThread(i)) + it->ResetRepeatStatus(); + + //LeaveCriticalSection(&hmcs); + //ConsoleOutput("vnrhost:ResetRepeatStatus: unlock"); +} +//void HookManager::LockHookman(){ EnterCriticalSection(&hmcs); } +//void HookManager::UnlockHookman(){ LeaveCriticalSection(&hmcs); } + +/*void HookManager::SetProcessEngineType(DWORD pid, DWORD type) +{ + int i; + for (i=0;i>7)|(hash<<25)) + *module; +// return hash; +//} + +DWORD GetCurrentPID() { return ::man->GetCurrentPID(); } + +HANDLE GetCmdHandleByPID(DWORD pid) { return ::man->GetCmdHandleByPID(pid); } + +//void AddLink(WORD from, WORD to) { ::man->AddLink(from, to); } + +// jichi 9/27/2013: Unparse to hook parameters /H code +void GetCode(const HookParam &hp, LPWSTR buffer, DWORD pid) +{ + WCHAR c; + LPWSTR ptr = buffer; + // jichi 12/7/2014: disabled + //if (hp.type&PRINT_DWORD) + // c = L'H'; + if (hp.type&USING_UNICODE) { + if (hp.type&USING_STRING) + c = L'Q'; + else if (hp.type&STRING_LAST_CHAR) + c = L'L'; + else + c = L'W'; + } else { + if (hp.type&USING_STRING) + c = L'S'; + else if (hp.type&BIG_ENDIAN) + c = L'A'; + else if (hp.type&STRING_LAST_CHAR) + c = L'E'; + else + c = L'B'; + } + ptr += swprintf(ptr, L"/H%c",c); + if (hp.type & NO_CONTEXT) + *ptr++ = L'N'; + if (hp.offset>>31) + ptr += swprintf(ptr, L"-%X",-(hp.offset+4)); + else + ptr += swprintf(ptr, L"%X",hp.offset); + if (hp.type & DATA_INDIRECT) { + if (hp.index>>31) + ptr += swprintf(ptr, L"*-%X",-hp.index); + else + ptr += swprintf(ptr,L"*%X",hp.index); + } + if (hp.type & USING_SPLIT) { + if (hp.split >> 31) + ptr += swprintf(ptr, L":-%X", -(4 + hp.split)); + else + ptr += swprintf(ptr, L":%X", hp.split); + } + if (hp.type & SPLIT_INDIRECT) { + if (hp.split_index >> 31) + ptr += swprintf(ptr, L"*-%X", -hp.split_index); + else + ptr += swprintf(ptr, L"*%X", hp.split_index); + } + if (hp.module) { + if (pid) { + WCHAR path[MAX_PATH]; + MEMORY_BASIC_INFORMATION info; + ProcessRecord* pr = ::man->GetProcessRecord(pid); + if (pr) { + HANDLE hProc = pr->process_handle; + if (NT_SUCCESS(NtQueryVirtualMemory(hProc,(PVOID)hp.address, MemorySectionName, path, MAX_PATH*2, 0)) && + NT_SUCCESS(NtQueryVirtualMemory(hProc,(PVOID)hp.address, MemoryBasicInformation, &info, sizeof(info), 0))) + ptr += swprintf(ptr, L"@%X:%s", hp.address - (DWORD)info. AllocationBase, wcsrchr(path,L'\\') + 1); + } + } else { + ptr += swprintf(ptr, L"@%X!%X", hp.address, hp.module); + if (hp.function) + ptr += swprintf(ptr, L"!%X", hp.function); + } + } + else + ptr += swprintf(ptr, L"@%X", hp.address); +} + +// jichi 1/16/2015 +bool HookManager::IsFull() const { return new_thread_number >= MAX_HOOK; } + +void AddHooksToProfile(Profile& pf, const ProcessRecord& pr); +DWORD AddThreadToProfile(Profile& pf, const ProcessRecord& pr, TextThread* thread); +void MakeHookRelative(const ProcessRecord& pr, HookParam& hp); + +void HookManager::GetProfile(DWORD pid, pugi::xml_node profile_node) +{ + const ProcessRecord* pr = GetProcessRecord(pid); + if (pr == NULL) + return; + Profile pf(L"serialize"); + AddHooksToProfile(pf, *pr); + AddThreadsToProfile(pf, *pr, pid); + pf.XmlWriteProfile(profile_node); +} + +void AddHooksToProfile(Profile& pf, const ProcessRecord& pr) +{ + WaitForSingleObject(pr.hookman_mutex, 0); + auto hooks = (const Hook*)pr.hookman_map; + for (DWORD i = 0; i < MAX_HOOK; ++i) + { + if (hooks[i].Address() == 0) + continue; + auto& hook = hooks[i]; + DWORD type = hook.Type(); + if ((type & HOOK_ADDITIONAL) && (type & HOOK_ENGINE) == 0) + { + std::unique_ptr name(new CHAR[hook.NameLength()]); + if (ReadProcessMemory(pr.process_handle, hook.Name(), name.get(), hook.NameLength(), NULL)) + { + if (hook.hp.module) + { + HookParam hp = hook.hp; + MakeHookRelative(pr, hp); + pf.AddHook(hook_ptr(new HookProfile(hp, toUnicodeString(name.get())))); + } + else + pf.AddHook(hook_ptr(new HookProfile(hook.hp, toUnicodeString(name.get())))); + } + } + } + ReleaseMutex(pr.hookman_mutex); +} + +void MakeHookRelative(const ProcessRecord& pr, HookParam& hp) +{ + MEMORY_BASIC_INFORMATION info; + VirtualQueryEx(pr.process_handle, (LPCVOID)hp.address, &info, sizeof(info)); + hp.address -= (DWORD)info.AllocationBase; + hp.function = 0; +} + +void HookManager::AddThreadsToProfile(Profile& pf, const ProcessRecord& pr, DWORD pid) +{ + HM_LOCK; + ThreadTable* table = Table(); + for (int i = 0; i < table->Used(); ++i) + { + TextThread* tt = table->FindThread(i); + if (tt == NULL || tt->GetThreadParameter()->pid != pid) + continue; + //if (tt->Status() & CURRENT_SELECT || tt->Link() || tt->GetComment()) + if (tt->Status() & CURRENT_SELECT || tt->Link()) + AddThreadToProfile(pf, pr, tt); + } +} + +DWORD AddThreadToProfile(Profile& pf, const ProcessRecord& pr, TextThread* thread) +{ + const ThreadParameter* tp = thread->GetThreadParameter(); + std::wstring hook_name = GetHookNameByAddress(pr, tp->hook); + if (hook_name.empty()) + return -1; + auto thread_profile = new ThreadProfile(hook_name, tp->retn, tp->spl, 0, 0, + THREAD_MASK_RETN | THREAD_MASK_SPLIT, L""); + DWORD threads_size = pf.Threads().size(); + int thread_profile_index = pf.AddThread(thread_ptr(thread_profile)); + if (thread_profile_index == threads_size) // new thread + { + WORD iw = thread_profile_index & 0xFFFF; + if (thread->Status() & CURRENT_SELECT) + pf.SelectedIndex() = iw; + if (thread->Link()) + { + WORD to_index = AddThreadToProfile(pf, pr, thread->Link()) & 0xFFFF; + if (iw >= 0) + pf.AddLink(link_ptr(new LinkProfile(iw, to_index))); + } + } + return thread_profile_index; // in case more than one thread links to the same thread +} + +// EOF diff --git a/vnr/texthook/host/hookman.h b/vnr/texthook/host/hookman.h new file mode 100644 index 0000000..bcbbeba --- /dev/null +++ b/vnr/texthook/host/hookman.h @@ -0,0 +1,153 @@ +#pragma once + +// hookman.h +// 8/23/2013 jichi +// Branch: ITH/HookManager.h, rev 133 + +#include "host/avl_p.h" +#include "host/textthread.h" +#include "winmutex/winmutex.h" + +namespace pugi { + class xml_node; +} +class Profile; + +enum { MAX_REGISTER = 0xf }; +enum { MAX_PREV_REPEAT_LENGTH = 0x20 }; + +struct ProcessRecord { + DWORD pid_register; + DWORD hookman_register; + DWORD module_register; + //DWORD engine_register; // jichi 10/19/2014: removed + HANDLE process_handle; + HANDLE hookman_mutex; + HANDLE hookman_section; + LPVOID hookman_map; +}; + +class ThreadTable : public MyVector +{ +public: + virtual void SetThread(DWORD number, TextThread *ptr); + virtual TextThread *FindThread(DWORD number); +}; + +struct IHFSERVICE TCmp { char operator()(const ThreadParameter *t1, const ThreadParameter *t2); }; +struct IHFSERVICE TCpy { void operator()(ThreadParameter *t1, const ThreadParameter *t2); }; +struct IHFSERVICE TLen { int operator()(const ThreadParameter *t); }; + +typedef DWORD (*ProcessEventCallback)(DWORD pid); + +class IHFSERVICE HookManager : public AVLTree +{ +public: + HookManager(); + ~HookManager(); + // jichi 12/26/2013: remove virtual modifiers + TextThread *FindSingle(DWORD pid, DWORD hook, DWORD retn, DWORD split); + TextThread *FindSingle(DWORD number); + ProcessRecord *GetProcessRecord(DWORD pid); + DWORD GetProcessIDByPath(LPCWSTR str); // private + void RemoveSingleThread(DWORD number); + //void LockHookman(); + //void UnlockHookman(); + void ResetRepeatStatus(); + void ClearCurrent(); + void AddLink(WORD from, WORD to); + void UnLink(WORD from); + void UnLinkAll(WORD from); + void SelectCurrent(DWORD num); + void DetachProcess(DWORD pid); + void SetCurrent(TextThread *it); + void AddConsoleOutput(LPCWSTR text); + + // jichi 10/27/2013: Add const; add space. + void DispatchText(DWORD pid, const BYTE *text, DWORD hook, DWORD retn, DWORD split, int len, bool space); + + void ClearText(DWORD pid, DWORD hook, DWORD retn, DWORD split); // private + void RemoveProcessContext(DWORD pid); // private + void RemoveSingleHook(DWORD pid, DWORD addr); + void RegisterThread(TextThread*, DWORD); // private + void RegisterPipe(HANDLE text, HANDLE cmd, HANDLE thread); + void RegisterProcess(DWORD pid, DWORD hookman, DWORD module); + void UnRegisterProcess(DWORD pid); + //void SetName(DWORD); + + DWORD GetCurrentPID(); // private + HANDLE GetCmdHandleByPID(DWORD pid); + + ConsoleCallback RegisterConsoleCallback(ConsoleCallback cf) + { return (ConsoleCallback)_InterlockedExchange((long*)&console,(long)cf); } + + ConsoleWCallback RegisterConsoleWCallback(ConsoleWCallback cf) + { return (ConsoleWCallback)_InterlockedExchange((long*)&wconsole,(long)cf); } + + ThreadEventCallback RegisterThreadCreateCallback(ThreadEventCallback cf) + { return (ThreadEventCallback)_InterlockedExchange((long*)&create,(long)cf); } + + ThreadEventCallback RegisterThreadRemoveCallback(ThreadEventCallback cf) + { return (ThreadEventCallback)_InterlockedExchange((long*)&remove,(long)cf); } + + ThreadEventCallback RegisterThreadResetCallback(ThreadEventCallback cf) + { return (ThreadEventCallback)_InterlockedExchange((long*)&reset,(long)cf); } + + ThreadEventCallback RegisterAddRemoveLinkCallback(ThreadEventCallback cf) + { return (ThreadEventCallback)_InterlockedExchange((long*)&addRemoveLink, (long)cf); } + + ProcessEventCallback RegisterProcessAttachCallback(ProcessEventCallback cf) + { return (ProcessEventCallback)_InterlockedExchange((long*)&attach,(long)cf); } + + ProcessEventCallback RegisterProcessDetachCallback(ProcessEventCallback cf) + { return (ProcessEventCallback)_InterlockedExchange((long*)&detach,(long)cf); } + + ProcessEventCallback RegisterProcessNewHookCallback(ProcessEventCallback cf) + { return (ProcessEventCallback)_InterlockedExchange((long*)&hook,(long)cf); } + + ProcessEventCallback ProcessNewHook() { return hook; } + TextThread *GetCurrentThread() { return current; } // private + ProcessRecord *Records() { return record; } // private + ThreadTable *Table() { return thread_table; } // private + + //DWORD& SplitTime() { return split_time; } + //DWORD& RepeatCount() { return repeat_count; } + //DWORD& CyclicRemove() { return cyclic_remove; } + //DWORD& GlobalFilter() { return global_filter; } + void ConsoleOutput(LPCSTR text) { if (console) console(text); } // not thread safe + void ConsoleOutputW(LPCWSTR text) { if (wconsole) wconsole(text); } // not thread safe + + void OnThreadCreate(pugi::xml_node profile_node, TextThread* thread); + void GetProfile(DWORD pid, pugi::xml_node profile_node); + +private: + typedef win_mutex mutex_type; + mutex_type hmcs; + + TextThread *current; + ConsoleCallback console; // jichi 12/25/2013: add console output callback + ConsoleWCallback wconsole; + ThreadEventCallback create, + remove, + reset, + addRemoveLink; + ProcessEventCallback attach, + detach, + hook; + DWORD current_pid; + ThreadTable *thread_table; + HANDLE destroy_event; + ProcessRecord record[MAX_REGISTER + 1]; + HANDLE text_pipes[MAX_REGISTER + 1], + cmd_pipes[MAX_REGISTER + 1], + recv_threads[MAX_REGISTER + 1]; + WORD register_count, + new_thread_number; + + // jichi 1/16/2014: Stop adding new threads when full + bool IsFull() const; // { return new_thread_number >= MAX_HOOK; } + bool IsEmpty() const { return !new_thread_number; } + void HookManager::AddThreadsToProfile(Profile& pf, const ProcessRecord& pr, DWORD pid); +}; + +// EOF diff --git a/vnr/texthook/host/host.cc b/vnr/texthook/host/host.cc new file mode 100644 index 0000000..9340f3a --- /dev/null +++ b/vnr/texthook/host/host.cc @@ -0,0 +1,646 @@ +// host.cc +// 8/24/2013 jichi +// Branch IHF/main.cpp, rev 111 +// 8/24/2013 TODO: Clean up this file + +//#ifdef _MSC_VER +//# pragma warning(disable:4800) // C4800: forcing value to bool (performance warning) +//#endif // _MSC_VER + +//#include "customfilter.h" +#include "growl.h" +#include "host.h" +#include "host_p.h" +#include "settings.h" +#include "vnrhook/include/const.h" +#include "vnrhook/include/defs.h" +#include "vnrhook/include/types.h" +#include "ithsys/ithsys.h" +#include "windbg/inject.h" +#include "winmaker/winmaker.h" +#include "ccutil/ccmacro.h" +#include + +//#define ITH_WINE +//#define ITH_USE_UX_DLLS IthIsWine() +//#define ITH_USE_XP_DLLS (IthIsWindowsXp() && !IthIsWine()) + +#define DEBUG "vnrhost/host.cc" +#include "sakurakit/skdebug.h" + +namespace { // unnamed + +//enum { HOOK_TIMEOUT = -50000000 }; // in nanoseconds = 5 seconds + +CRITICAL_SECTION cs; +//WCHAR exist[] = ITH_PIPEEXISTS_EVENT; +//WCHAR mutex[] = L"ITH_RUNNING"; +//WCHAR EngineName[] = ITH_ENGINE_DLL; +//WCHAR EngineNameXp[] = ITH_ENGINE_XP_DLL; +//WCHAR DllName[] = ITH_CLIENT_DLL; +//WCHAR DllNameXp[] = ITH_CLIENT_XP_DLL; +HANDLE hServerMutex; // jichi 9/28/2013: used to guard pipe +HANDLE hHookMutex; // jichi 9/28/2013: used to guard hook modification +} // unnamed namespace + +//extern LPWSTR current_dir; +extern CRITICAL_SECTION detach_cs; + +Settings *settings; +HWND hMainWnd; +HANDLE hPipeExist; +BOOL running; + +#define ITH_SYNC_HOOK IthMutexLocker locker(::hHookMutex) + +namespace { // unnamed + +void GetDebugPriv() +{ + HANDLE hToken; + DWORD dwRet; + NTSTATUS status; + + TOKEN_PRIVILEGES Privileges = {1,{0x14,0,SE_PRIVILEGE_ENABLED}}; + + NtOpenProcessToken(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken); + + status = NtAdjustPrivilegesToken(hToken, 0, &Privileges, sizeof(Privileges), 0, &dwRet); + //if (STATUS_SUCCESS == status) + //{ + // admin = 1; + //} + //else + //{ + // WCHAR buffer[0x10]; + // swprintf(buffer, L"%.8X",status); + // MessageBox(0, NotAdmin, buffer, 0); + //} + NtClose(hToken); +} + +bool sendCommand(HANDLE hCmd, HostCommandType cmd) +{ + IO_STATUS_BLOCK ios; + //SendParam sp = {}; + //sp.type = cmd; + DWORD data = cmd; + return hCmd && NT_SUCCESS(NtWriteFile(hCmd, 0,0,0, &ios, &data, sizeof(data), 0,0)); +} + +// jichi 9/22/2013: Change current directory to the same as main module path +// Otherwise NtFile* would not work for files with relative paths. +//BOOL ChangeCurrentDirectory() +//{ +// if (const wchar_t *path = GetMainModulePath()) // path to VNR's python exe +// if (const wchar_t *base = wcsrchr(path, L'\\')) { +// size_t len = base - path; +// if (len < MAX_PATH) { +// wchar_t buf[MAX_PATH]; +// wcsncpy(buf, path, len); +// buf[len] = 0; +// return SetCurrentDirectoryW(buf); +// } +// } +// return FALSE; +//} + +#if 0 +bool injectUsingWin32Api(LPCWSTR path, HANDLE hProc) +{ return WinDbg::injectDllW(path, 0, hProc); } + +bool ejectUsingWin32Api(HANDLE hModule, HANDLE hProc) +{ return WinDbg::ejectDll(hModule, hProc); } + +// The original inject logic in ITH +bool injectUsingNTApi(LPCWSTR path, HANDLE hProc) +{ + LPVOID lpvAllocAddr = 0; + DWORD dwWrite = 0x1000; //, len = 0; + //if (IthIsWine()) + // lpvAllocAddr = VirtualAllocEx(hProc, nullptr, dwWrite, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + //else + NtAllocateVirtualMemory(hProc, &lpvAllocAddr, 0, &dwWrite, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + if (!lpvAllocAddr) + return false; + + CheckThreadStart(); + + //Copy module path into address space of target process. + //if (IthIsWine()) + // WriteProcessMemory(hProc, lpvAllocAddr, path, MAX_PATH << 1, &dwWrite); + //else + NtWriteVirtualMemory(hProc, lpvAllocAddr, (LPVOID)path, MAX_PATH << 1, &dwWrite); + HANDLE hTH = IthCreateThread(LoadLibraryW, (DWORD)lpvAllocAddr, hProc); + if (hTH == 0 || hTH == INVALID_HANDLE_VALUE) { + DOUT("ERROR: failed to create remote cli thread"); + //ConsoleOutput(ErrorRemoteThread); + return false; + } + // jichi 9/28/2013: no wait as it will not blocked + NtWaitForSingleObject(hTH, 0, nullptr); + THREAD_BASIC_INFORMATION info; + NtQueryInformationThread(hTH, ThreadBasicInformation, &info, sizeof(info), &dwWrite); + NtClose(hTH); + + // jichi 10/19/2014: Disable inject the second dll + //if (info.ExitStatus) { + // //IthCoolDown(); + // wcscpy(p, engine); + // //if (IthIsWine()) + // // WriteProcessMemory(hProc, lpvAllocAddr, path, MAX_PATH << 1, &dwWrite); + // //else + // NtWriteVirtualMemory(hProc, lpvAllocAddr, path, MAX_PATH << 1, &dwWrite); + // hTH = IthCreateThread(LoadLibraryW, (DWORD)lpvAllocAddr, hProc); + // if (hTH == 0 || hTH == INVALID_HANDLE_VALUE) { + // //ConsoleOutput(ErrorRemoteThread); + // ConsoleOutput("vnrhost:inject: ERROR: failed to create remote eng thread"); + // return error; + // } + // + // // jichi 9/28/2013: no wait as it will not blocked + // NtWaitForSingleObject(hTH, 0, nullptr); + // NtClose(hTH); + //} + + dwWrite = 0; + //if (IthIsWine()) + // VirtualFreeEx(hProc, lpvAllocAddr, dwWrite, MEM_RELEASE); + //else + NtFreeVirtualMemory(hProc, &lpvAllocAddr, &dwWrite, MEM_RELEASE); + return info.ExitStatus != -1; +} + +bool ejectUsingNTApi(HANDLE hModule, HANDLE hProc) +{ + //IthCoolDown(); +//#ifdef ITH_WINE // Nt series crash on wine +// hThread = IthCreateThread(FreeLibrary, engine, hProc); +//#else + HANDLE hThread = IthCreateThread(LdrUnloadDll, module, hProc); +//#endif // ITH_WINE + if (hThread == 0 || hThread == INVALID_HANDLE_VALUE) + return false; + // jichi 10/22/2013: Timeout might crash vnrsrv + //NtWaitForSingleObject(hThread, 0, (PLARGE_INTEGER)&timeout); + NtWaitForSingleObject(hThread, 0, nullptr); + //man->UnlockHookman(); + THREAD_BASIC_INFORMATION info; + NtQueryInformationThread(hThread, ThreadBasicInformation, &info, sizeof(info), 0); + NtClose(hThread); + NtSetEvent(hPipeExist, 0); + FreeThreadStart(hProc); + return info.ExitStatus; +} +#endif // 0 + +bool Inject(HANDLE hProc) +{ + //LPWSTR dllname = (IthIsWindowsXp() && !IthIsWine()) ? DllNameXp : DllName; + //LPCWSTR dllname = ITH_USE_XP_DLLS ? ITH_DLL_XP : ITH_DLL; + //LPCWSTR dllname = ITH_DLL; + //if (!IthCheckFile(dllname)) + // return error; + wchar_t path[MAX_PATH]; + size_t len = IthGetCurrentModulePath(path, MAX_PATH); + if (!len) + return false; + + wchar_t *p; + for (p = path + len; *p != L'\\'; p--); + p++; // ending with L"\\" + + //LPCWSTR mp = GetMainModulePath(); + //len = wcslen(mp); + //memcpy(path, mp, len << 1); + //memset(path + len, 0, (MAX_PATH - len) << 1); + //LPWSTR p; + //for (p = path + len; *p != L'\\'; p--); // Always a \ after drive letter. + //p++; + ::wcscpy(p, ITH_DLL); + + return WinDbg::injectDllW(path, 0, hProc); + //if (IthIsWindowsXp()) // && !IthIsWine()) + // return injectUsingWin32Api(path, hProc); + //else + // return injectUsingNTApi(path, hProc); +} + +} // unnamed namespace + +void CreateNewPipe(); + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + CC_UNUSED(lpvReserved); + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + LdrDisableThreadCalloutsForDll(hinstDLL); + InitializeCriticalSection(&::cs); + IthInitSystemService(); + GetDebugPriv(); + // jichi 12/20/2013: Since I already have a GUI, I don't have to InitCommonControls() + //Used by timers. + InitCommonControls(); + // jichi 8/24/2013: Create hidden window so that ITH can access timer and events + hMainWnd = CreateWindowW(L"Button", L"InternalWindow", 0, 0, 0, 0, 0, 0, 0, hinstDLL, 0); + //wm_register_hidden_class("vnrsrv.class"); + //hMainWnd = (HWND)wm_create_hidden_window("vnrsrv", "Button", hinstDLL); + //ChangeCurrentDirectory(); + break; + case DLL_PROCESS_DETACH: + if (::running) + Host_Close(); + DeleteCriticalSection(&::cs); + IthCloseSystemService(); + //wm_destroy_window(hMainWnd); + DestroyWindow(hMainWnd); + break; + default: + break; + } + return true; +} + +HANDLE IthOpenPipe(LPWSTR name, ACCESS_MASK direction) +{ + UNICODE_STRING us; + RtlInitUnicodeString(&us, name); + SECURITY_DESCRIPTOR sd = {1}; + OBJECT_ATTRIBUTES oa = {sizeof(oa), 0, &us, OBJ_CASE_INSENSITIVE, &sd, 0}; + HANDLE hFile; + IO_STATUS_BLOCK isb; + if (NT_SUCCESS(NtCreateFile(&hFile, direction, &oa, &isb, 0, 0, FILE_SHARE_READ, FILE_OPEN, 0, 0, 0))) + return hFile; + else + return INVALID_HANDLE_VALUE; +} + +enum { IHS_SIZE = 0x80 }; +enum { IHS_BUFF_SIZE = IHS_SIZE - sizeof(HookParam) }; + +struct InsertHookStruct +{ + SendParam sp; + BYTE name_buffer[IHS_SIZE]; +}; + +IHFSERVICE void IHFAPI Host_Init() +{ + InitializeCriticalSection(&::cs); + GetDebugPriv(); +} + +IHFSERVICE void IHFAPI Host_Destroy() +{ + InitializeCriticalSection(&::cs); +} + +IHFSERVICE BOOL IHFAPI Host_Open() +{ + BOOL result = false; + EnterCriticalSection(&::cs); + DWORD present; + hServerMutex = IthCreateMutex(ITH_SERVER_MUTEX, 1, &present); + if (present) + //MessageBox(0,L"Already running.",0,0); + // jichi 8/24/2013 + GROWL_WARN(L"I am sorry that this game is attached by some other VNR ><\nPlease restart the game and try again!"); + else if (!::running) { + ::running = true; + ::settings = new Settings; + ::man = new HookManager; + //cmdq = new CommandQueue; + InitializeCriticalSection(&detach_cs); + + ::hHookMutex = IthCreateMutex(ITH_SERVER_HOOK_MUTEX, FALSE); + result = true; + } + LeaveCriticalSection(&::cs); + return result; +} + +IHFSERVICE DWORD IHFAPI Host_Start() +{ + //IthBreak(); + CreateNewPipe(); + ::hPipeExist = IthCreateEvent(ITH_PIPEEXISTS_EVENT); + NtSetEvent(::hPipeExist, nullptr); + return 0; +} + +IHFSERVICE DWORD IHFAPI Host_Close() +{ + BOOL result = FALSE; + EnterCriticalSection(&::cs); + if (::running) { + ::running = FALSE; + HANDLE hRecvPipe = IthOpenPipe(recv_pipe, GENERIC_WRITE); + NtClose(hRecvPipe); + NtClearEvent(::hPipeExist); + //delete cmdq; + delete man; + delete settings; + NtClose(::hHookMutex); + NtClose(hServerMutex); + NtClose(::hPipeExist); + DeleteCriticalSection(&detach_cs); + result = TRUE; + } + LeaveCriticalSection(&::cs); + return result; +} + +IHFSERVICE DWORD IHFAPI Host_GetPIDByName(LPCWSTR pwcTarget) +{ + DWORD dwSize = 0x20000, + dwExpectSize = 0; + LPVOID pBuffer = 0; + SYSTEM_PROCESS_INFORMATION *spiProcessInfo; + DWORD dwPid = 0; + DWORD dwStatus; + + NtAllocateVirtualMemory(NtCurrentProcess(), &pBuffer, 0, &dwSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + dwStatus = NtQuerySystemInformation(SystemProcessInformation, pBuffer, dwSize, &dwExpectSize); + if (!NT_SUCCESS(dwStatus)) { + NtFreeVirtualMemory(NtCurrentProcess(),&pBuffer,&dwSize,MEM_RELEASE); + if (dwStatus != STATUS_INFO_LENGTH_MISMATCH || dwExpectSize < dwSize) + return 0; + dwSize = (dwExpectSize | 0xFFF) + 0x4001; // + pBuffer = 0; + NtAllocateVirtualMemory(NtCurrentProcess(), &pBuffer, 0, &dwSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + dwStatus = NtQuerySystemInformation(SystemProcessInformation, pBuffer, dwSize, &dwExpectSize); + if (!NT_SUCCESS(dwStatus)) goto _end; + } + + for (spiProcessInfo = (SYSTEM_PROCESS_INFORMATION *)pBuffer; spiProcessInfo->dNext;) { + spiProcessInfo = (SYSTEM_PROCESS_INFORMATION *) + ((DWORD)spiProcessInfo + spiProcessInfo -> dNext); + if (_wcsicmp(pwcTarget, spiProcessInfo -> usName.Buffer) == 0) { + dwPid = spiProcessInfo->dUniqueProcessId; + break; + } + } + if (!dwPid) + DOUT("pid not found"); + //if (dwPid == 0) ConsoleOutput(ErrorNoProcess); +_end: + NtFreeVirtualMemory(NtCurrentProcess(),&pBuffer,&dwSize,MEM_RELEASE); + return dwPid; +} + +IHFSERVICE bool IHFAPI Host_InjectByPID(DWORD pid) +{ + WCHAR str[0x80]; + if (!::running) + return 0; + if (pid == current_process_id) { + //ConsoleOutput(SelfAttach); + DOUT("refuse to inject myself"); + return false; + } + if (man->GetProcessRecord(pid)) { + //ConsoleOutput(AlreadyAttach); + DOUT("already attached"); + return false; + } + swprintf(str, ITH_HOOKMAN_MUTEX_ L"%d", pid); + DWORD s; + NtClose(IthCreateMutex(str, 0, &s)); + if (s) { + DOUT("already locked"); + return false; + } + CLIENT_ID id; + OBJECT_ATTRIBUTES oa = {}; + HANDLE hProc; + id.UniqueProcess = pid; + id.UniqueThread = 0; + oa.uLength = sizeof(oa); + if (!NT_SUCCESS(NtOpenProcess(&hProc, + PROCESS_QUERY_INFORMATION| + PROCESS_CREATE_THREAD| + PROCESS_VM_OPERATION| + PROCESS_VM_READ| + PROCESS_VM_WRITE, + &oa, &id))) { + //ConsoleOutput(ErrorOpenProcess); + DOUT("failed to open process"); + return false; + } + + //if (!engine) + // engine = ITH_USE_XP_DLLS ? ITH_ENGINE_XP_DLL : ITH_ENGINE_DLL; + bool ok = Inject(hProc); + //NtClose(hProc); //already closed + if (!ok) { + DOUT("inject failed"); + return false; + } + //swprintf(str, FormatInject, pid, module); + //ConsoleOutput(str); + DOUT("inject succeed"); + return true; +} + +// jichi 7/16/2014: Test if process is valid before creating remote threads +// See: http://msdn.microsoft.com/en-us/library/ms687032.aspx +static bool isProcessTerminated(HANDLE hProc) +{ return WAIT_OBJECT_0 == ::WaitForSingleObject(hProc, 0); } +//static bool isProcessRunning(HANDLE hProc) +//{ return WAIT_TIMEOUT == ::WaitForSingleObject(hProc, 0); } + +// jichi 7/16/2014: Test if process is valid before creating remote threads +//static bool isProcessRunning(DWORD pid) +//{ +// bool ret = false; +// HANDLE hProc = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); +// if (hProc) { +// DWORD status; +// if (::GetExitCodeProcess(hProc, &status)) { +// ret = status == STILL_ACTIVE; +// ::CloseHandle(hProc); +// } else +// ret = true; +// } +// return ret; +//} + +IHFSERVICE bool IHFAPI Host_ActiveDetachProcess(DWORD pid) +{ + ITH_SYNC_HOOK; + + //man->LockHookman(); + ProcessRecord *pr = man->GetProcessRecord(pid); + HANDLE hCmd = man->GetCmdHandleByPID(pid); + if (pr == 0 || hCmd == 0) + return false; + HANDLE hProc; + //hProc = pr->process_handle; //This handle may be closed(thus invalid) during the detach process. + NtDuplicateObject(NtCurrentProcess(), pr->process_handle, + NtCurrentProcess(), &hProc, 0, 0, DUPLICATE_SAME_ACCESS); // Make a copy of the process handle. + HANDLE hModule = (HANDLE)pr->module_register; + if (!hModule) { + DOUT("process module not found"); + return false; + } + + // jichi 7/15/2014: Process already closed + if (isProcessTerminated(hProc)) { + DOUT("process has terminated"); + return false; + } + + // jichi 10/19/2014: Disable the second dll + //engine = pr->engine_register; + //engine &= ~0xff; + + DOUT("send detach command"); + bool ret = sendCommand(hCmd, HOST_COMMAND_DETACH); + + // jichi 7/15/2014: Process already closed + //if (isProcessTerminated(hProc)) { + // DOUT("process has terminated"); + // return false; + //} + //WinDbg::ejectDll(hModule, 0, hProc); // eject in case module has not loaded yet + + //cmdq->AddRequest(sp, pid); +////#ifdef ITH_WINE // Nt series crash on wine +//// hThread = IthCreateThread(FreeLibrary, engine, hProc); +////#else +// hThread = IthCreateThread(LdrUnloadDll, engine, hProc); +////#endif // ITH_WINE +// if (hThread == 0 || hThread == INVALID_HANDLE_VALUE) +// return FALSE; +// // jichi 10/22/2013: Timeout might crash vnrsrv +// //const LONGLONG timeout = HOOK_TIMEOUT; +// //NtWaitForSingleObject(hThread, 0, (PLARGE_INTEGER)&timeout); +// NtWaitForSingleObject(hThread, 0, nullptr); +// NtClose(hThread); + NtClose(hProc); + return ret; +} + +IHFSERVICE DWORD IHFAPI Host_GetHookManager(HookManager** hookman) +{ + if (::running) { + *hookman = man; + return 0; + } + else + return 1; +} + +IHFSERVICE bool IHFAPI Host_GetSettings(Settings **p) +{ + if (::running) { + *p = settings; + return true; + } + else + return false; +} + +IHFSERVICE bool IHFAPI Host_HijackProcess(DWORD pid) +{ + //ITH_SYNC_HOOK; + HANDLE hCmd = man->GetCmdHandleByPID(pid); + return hCmd && sendCommand(hCmd, HOST_COMMAND_HIJACK_PROCESS); +} + +IHFSERVICE DWORD IHFAPI Host_InsertHook(DWORD pid, HookParam *hp, LPCSTR name) +{ + ITH_SYNC_HOOK; + + HANDLE hCmd = man->GetCmdHandleByPID(pid); + if (hCmd == 0) + return -1; + + InsertHookStruct s; + s.sp.type = HOST_COMMAND_NEW_HOOK; + s.sp.hp = *hp; + size_t len; + if (name) + len = ::strlen(name); + else + len = 0; + if (len) { + if (len >= IHS_BUFF_SIZE) len = IHS_BUFF_SIZE - 1; + memcpy(s.name_buffer, name, len); + } + s.name_buffer[len] = 0; + IO_STATUS_BLOCK ios; + NtWriteFile(hCmd, 0,0,0, &ios, &s, IHS_SIZE, 0, 0); + + //memcpy(&sp.hp,hp,sizeof(HookParam)); + //cmdq->AddRequest(sp, pid); + return 0; +} + +IHFSERVICE DWORD IHFAPI Host_ModifyHook(DWORD pid, HookParam *hp) +{ + ITH_SYNC_HOOK; + + HANDLE hCmd = GetCmdHandleByPID(pid); + if (hCmd == 0) + return -1; + HANDLE hModify = IthCreateEvent(ITH_MODIFYHOOK_EVENT); + SendParam sp; + sp.type = HOST_COMMAND_MODIFY_HOOK; + sp.hp = *hp; + IO_STATUS_BLOCK ios; + if (NT_SUCCESS(NtWriteFile(hCmd, 0,0,0, &ios, &sp, sizeof(SendParam), 0, 0))) + // jichi 9/28/2013: no wait timeout + //const LONGLONG timeout = HOOK_TIMEOUT; + NtWaitForSingleObject(hModify, 0, nullptr); + NtClose(hModify); + man->RemoveSingleHook(pid, sp.hp.address); + return 0; +} + +IHFSERVICE DWORD IHFAPI Host_RemoveHook(DWORD pid, DWORD addr) +{ + ITH_SYNC_HOOK; + + HANDLE hRemoved,hCmd; + hCmd = GetCmdHandleByPID(pid); + if (hCmd == 0) + return -1; + hRemoved = IthCreateEvent(ITH_REMOVEHOOK_EVENT); + SendParam sp = {}; + IO_STATUS_BLOCK ios; + sp.type = HOST_COMMAND_REMOVE_HOOK; + sp.hp.address = addr; + //cmdq -> AddRequest(sp, pid); + NtWriteFile(hCmd, 0,0,0, &ios, &sp, sizeof(SendParam),0,0); + // jichi 10/22/2013: Timeout might crash vnrsrv + //const LONGLONG timeout = HOOK_TIMEOUT; + //NtWaitForSingleObject(hRemoved, 0, (PLARGE_INTEGER)&timeout); + NtWaitForSingleObject(hRemoved, 0, nullptr); + NtClose(hRemoved); + man -> RemoveSingleHook(pid, sp.hp.address); + return 0; +} + +// 4/30/2015: Removed as not needed. Going to change to json +IHFSERVICE DWORD IHFAPI Host_AddLink(DWORD from, DWORD to) +{ + man->AddLink(from & 0xffff, to & 0xffff); + return 0; +} + +IHFSERVICE DWORD IHFAPI Host_UnLink(DWORD from) +{ + man->UnLink(from & 0xffff); + return 0; +} + +IHFSERVICE DWORD IHFAPI Host_UnLinkAll(DWORD from) +{ + man->UnLinkAll(from & 0xffff); + return 0; +} + +// EOF diff --git a/vnr/texthook/host/host.h b/vnr/texthook/host/host.h new file mode 100644 index 0000000..8864ade --- /dev/null +++ b/vnr/texthook/host/host.h @@ -0,0 +1,33 @@ +#pragma once + +// host.h +// 8/23/2013 jichi +// Branch: ITH/IHF.h, rev 105 + +//#include "host/settings.h" +#include "config.h" +#include "host/hookman.h" + +struct Settings; +struct HookParam; + +IHFSERVICE void IHFAPI Host_Init(); +IHFSERVICE void IHFAPI Host_Destroy(); + +IHFSERVICE DWORD IHFAPI Host_Start(); +IHFSERVICE BOOL IHFAPI Host_Open(); +IHFSERVICE DWORD IHFAPI Host_Close(); +IHFSERVICE DWORD IHFAPI Host_GetHookManager(HookManager **hookman); +IHFSERVICE bool IHFAPI Host_GetSettings(Settings **settings); +IHFSERVICE DWORD IHFAPI Host_GetPIDByName(LPCWSTR pwcTarget); +IHFSERVICE bool IHFAPI Host_InjectByPID(DWORD pid); +IHFSERVICE bool IHFAPI Host_ActiveDetachProcess(DWORD pid); +IHFSERVICE bool IHFAPI Host_HijackProcess(DWORD pid); +IHFSERVICE DWORD IHFAPI Host_InsertHook(DWORD pid, HookParam *hp, LPCSTR name = nullptr); +IHFSERVICE DWORD IHFAPI Host_ModifyHook(DWORD pid, HookParam *hp); +IHFSERVICE DWORD IHFAPI Host_RemoveHook(DWORD pid, DWORD addr); +IHFSERVICE DWORD IHFAPI Host_AddLink(DWORD from, DWORD to); +IHFSERVICE DWORD IHFAPI Host_UnLink(DWORD from); +IHFSERVICE DWORD IHFAPI Host_UnLinkAll(DWORD from); + +// EOF diff --git a/vnr/texthook/host/host.pri b/vnr/texthook/host/host.pri new file mode 100644 index 0000000..73ffeb0 --- /dev/null +++ b/vnr/texthook/host/host.pri @@ -0,0 +1,23 @@ +# host.pri +# 8/9/2011 jichi + +DEFINES += WITH_LIB_VNRHOST + +DEPENDPATH += $$PWD + +HEADERS += \ + $$PWD/avl_p.h \ + $$PWD/hookman.h \ + $$PWD/settings.h \ + $$PWD/host.h \ + $$PWD/host_p.h \ + $$PWD/textthread.h \ + $$PWD/textthread_p.h + +SOURCES += \ + $$PWD/hookman.cc \ + $$PWD/host.cc \ + $$PWD/pipe.cc \ + $$PWD/textthread.cc + +# EOF diff --git a/vnr/texthook/host/host_p.h b/vnr/texthook/host/host_p.h new file mode 100644 index 0000000..1645426 --- /dev/null +++ b/vnr/texthook/host/host_p.h @@ -0,0 +1,44 @@ +#pragma once +// host_p.h +// 8/24/2013 jichi +// Branch IHF/main.h, rev 111 +#include + +#define GLOBAL extern +#define SHIFT_JIS 0x3A4 +class HookManager; +//class CommandQueue; +class SettingManager; +class TextHook; +//class BitMap; +//class CustomFilterMultiByte; +//class CustomFilterUnicode; +//#define TextHook Hook +GLOBAL BOOL running; +//GLOBAL BitMap *pid_map; +//GLOBAL CustomFilterMultiByte *mb_filter; +//GLOBAL CustomFilterUnicode *uni_filter; +GLOBAL HookManager *man; +//GLOBAL CommandQueue *cmdq; +GLOBAL SettingManager *setman; +GLOBAL WCHAR recv_pipe[]; +GLOBAL WCHAR command[]; +GLOBAL HANDLE hPipeExist; +GLOBAL DWORD split_time, + cyclic_remove, + clipboard_flag, + global_filter; +GLOBAL CRITICAL_SECTION detach_cs; + +DWORD WINAPI RecvThread(LPVOID lpThreadParameter); +DWORD WINAPI CmdThread(LPVOID lpThreadParameter); + +DWORD GetCurrentPID(); +//DWORD GetProcessIDByPath(LPWSTR str); +HANDLE GetCmdHandleByPID(DWORD pid); +//DWORD Inject(HANDLE hProc); +//DWORD InjectByPID(DWORD pid); +//DWORD PIDByName(LPWSTR target); +//DWORD Hash(LPCWSTR module, int length=-1); + +// EOF diff --git a/vnr/texthook/host/pipe.cc b/vnr/texthook/host/pipe.cc new file mode 100644 index 0000000..eaa1a30 --- /dev/null +++ b/vnr/texthook/host/pipe.cc @@ -0,0 +1,327 @@ +// pipe.cc +// 8/24/2013 jichi +// Branch IHF/pipe.cpp, rev 93 +// 8/24/2013 TODO: Clean up this file + +#include "host_p.h" +#include "hookman.h" +#include "vnrhook/include/defs.h" +#include "vnrhook/include/const.h" +#include "ithsys/ithsys.h" +#include +//#include "CommandQueue.h" +//#include + +#define DEBUG "vnrhost/pipe.cc" +#include "sakurakit/skdebug.h" + +//DWORD WINAPI UpdateWindows(LPVOID lpThreadParameter); + +namespace { // unnamed +enum NamedPipeCommand { + NAMED_PIPE_DISCONNECT = 1 + , NAMED_PIPE_CONNECT = 2 +}; + +bool newline = false; +bool detach = false; + +// jichi 10/27/2013 +// Check if text has leading space +enum { _filter_limit = 0x20 }; // The same as the orignal ITH filter. So, I don't have to check \u3000 +//enum { _filter_limit = 0x19 }; +inline bool has_leading_space(const BYTE *text, int len) +{ + return len == 1 ? *text <= _filter_limit : // 1 byte + *reinterpret_cast(text) <= _filter_limit; // 2 bytes +} + +// jichi 9/28/2013: Skip leading garbage +// Note: +// - Modifying limit will break manual translation. The orignal one is 0x20 +// - Eliminating 0x20 will break English-translated games +const BYTE *Filter(const BYTE *str, int len) +{ +#ifdef ITH_DISABLE_FILTER // jichi 9/28/2013: only for debugging purpose + return str; +#endif // ITH_DISABLE_FILTER +// if (len && *str == 0x10) // jichi 9/28/2013: garbage on wine, data link escape, or ^P +// return nullptr; + //enum { limit = 0x19 }; + while (true) + if (len >= 2) { + if (*(const WORD *)str <= _filter_limit) { // jichi 10/27/2013: two bytes + str += 2; + len -= 2; + } else + break; + } else if (*str <= _filter_limit) { // jichi 10/27/2013: 1 byte + str++; + len--; + } else + break; + return str; +} +} // unnamed namespace + +//WCHAR recv_pipe[] = L"\\??\\pipe\\ITH_PIPE"; +//WCHAR command_pipe[] = L"\\??\\pipe\\ITH_COMMAND"; +wchar_t recv_pipe[] = ITH_TEXT_PIPE; +wchar_t command_pipe[] = ITH_COMMAND_PIPE; + +CRITICAL_SECTION detach_cs; // jichi 9/27/2013: also used in main +//HANDLE hDetachEvent; +extern HANDLE hPipeExist; + +void CreateNewPipe() +{ + static DWORD acl[7] = { + 0x1C0002, + 1, + 0x140000, + GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, + 0x101, + 0x1000000, + 0}; + static SECURITY_DESCRIPTOR sd = {1, 0, 4, 0, 0, 0, (PACL)acl}; + + HANDLE hTextPipe, hCmdPipe, hThread; + IO_STATUS_BLOCK ios; + UNICODE_STRING us; + + OBJECT_ATTRIBUTES oa = {sizeof(oa), 0, &us, OBJ_CASE_INSENSITIVE, &sd, 0}; + LARGE_INTEGER time = {-500000, -1}; + + RtlInitUnicodeString(&us, recv_pipe); + if (!NT_SUCCESS(NtCreateNamedPipeFile( + &hTextPipe, + GENERIC_READ | SYNCHRONIZE, + &oa, + &ios, + FILE_SHARE_WRITE, + FILE_OPEN_IF, + FILE_SYNCHRONOUS_IO_NONALERT, + 1, 1, 0, -1, + 0x1000, + 0x1000, + &time))) { + //ConsoleOutput(ErrorCreatePipe); + DOUT("failed to create recv pipe"); + return; + } + + RtlInitUnicodeString(&us, command_pipe); + if (!NT_SUCCESS(NtCreateNamedPipeFile( + &hCmdPipe, + GENERIC_WRITE | SYNCHRONIZE, + &oa, + &ios, + FILE_SHARE_READ, + FILE_OPEN_IF, + FILE_SYNCHRONOUS_IO_NONALERT, + 1, 1, 0, -1, + 0x1000, + 0x1000, + &time))) { + //ConsoleOutput(ErrorCreatePipe); + DOUT("failed to create cmd pipe"); + return; + } + + hThread = IthCreateThread(RecvThread, (DWORD)hTextPipe); + man->RegisterPipe(hTextPipe, hCmdPipe, hThread); +} + +void DetachFromProcess(DWORD pid) +{ + HANDLE hMutex = INVALID_HANDLE_VALUE, + hEvent = INVALID_HANDLE_VALUE; + //try { + IO_STATUS_BLOCK ios; + ProcessRecord *pr = man->GetProcessRecord(pid); + if (!pr) + return; + //IthBreak(); + hEvent = IthCreateEvent(nullptr); + if (STATUS_PENDING == NtFsControlFile( + man->GetCmdHandleByPID(pid), + hEvent, + 0,0, + &ios, + CTL_CODE(FILE_DEVICE_NAMED_PIPE, NAMED_PIPE_DISCONNECT, 0, 0), + 0,0,0,0)) + NtWaitForSingleObject(hEvent, 0, 0); + NtClose(hEvent); + //hEvent = INVALID_HANDLE_VALUE; + + WCHAR mutex[0x20]; + swprintf(mutex, ITH_DETACH_MUTEX_ L"%d", pid); + hMutex = IthOpenMutex(mutex); + if (hMutex != INVALID_HANDLE_VALUE) { + NtWaitForSingleObject(hMutex, 0, 0); + NtReleaseMutant(hMutex, 0); + NtClose(hMutex); + //hMutex = INVALID_HANDLE_VALUE; + } + + //} catch (...) { + // if (hEvent != INVALID_HANDLE_VALUE) + // NtClose(hEvent); + // else if (hMutex != INVALID_HANDLE_VALUE) { + // NtWaitForSingleObject(hMutex, 0, 0); + // NtReleaseMutant(hMutex, 0); + // NtClose(hMutex); + // } + //} + + //NtSetEvent(hDetachEvent, 0); + if (::running) + NtSetEvent(hPipeExist, 0); +} + +// jichi 9/27/2013: I don't need this +//void OutputDWORD(DWORD d) +//{ +// WCHAR str[0x20]; +// swprintf(str, L"%.8X", d); +// ConsoleOutput(str); +//} + +DWORD WINAPI RecvThread(LPVOID lpThreadParameter) +{ + HANDLE hTextPipe = (HANDLE)lpThreadParameter; + + IO_STATUS_BLOCK ios; + NtFsControlFile(hTextPipe, + 0, 0, 0, + &ios, + CTL_CODE(FILE_DEVICE_NAMED_PIPE, NAMED_PIPE_CONNECT, 0, 0), + 0, 0, 0, 0); + if (!::running) { + NtClose(hTextPipe); + return 0; + } + + BYTE *buff; + + enum { PipeBufferSize = 0x1000 }; + buff = new BYTE[PipeBufferSize]; + ::memset(buff, 0, PipeBufferSize); // jichi 8/27/2013: zero memory, or it will crash wine on start up + + // 10/19/2014 jichi: there are totally three words received + // See: hook/rpc/pipe.cc + // struct { + // DWORD pid; + // TextHook *man; + // DWORD module; + // //DWORD engine; + // } u; + enum { module_struct_size = 12 }; + NtReadFile(hTextPipe, 0, 0, 0, &ios, buff, module_struct_size, 0, 0); + + // jichi 7/2/2015: This must be consistent with the struct declared in vnrhook/pipe.cc + DWORD pid = *(DWORD *)buff, + module = *(DWORD *)(buff + 0x8), + hookman = *(DWORD *)(buff + 0x4); + //engine = *(DWORD *)(buff + 0xc); + man->RegisterProcess(pid, hookman, module); + + // jichi 9/27/2013: why recursion? + CreateNewPipe(); + + //NtClose(IthCreateThread(UpdateWindows,0)); + while (::running) { + if (!NT_SUCCESS(NtReadFile(hTextPipe, + 0, 0, 0, + &ios, + buff, + 0xf80, + 0, 0))) + break; + + enum { data_offset = 0xc }; // jichi 10/27/2013: Seem to be the data offset in the pipe + + DWORD RecvLen = ios.uInformation; + if (RecvLen < data_offset) + break; + DWORD hook = *(DWORD *)buff; + + union { DWORD retn; DWORD cmd_type; }; + union { DWORD split; DWORD new_engine_type; }; + + retn = *(DWORD *)(buff + 4); + split = *(DWORD *)(buff + 8); + + buff[RecvLen] = 0; + buff[RecvLen + 1] = 0; + + if (hook == HOST_NOTIFICATION) { + switch (cmd_type) { + case HOST_NOTIFICATION_NEWHOOK: + { + static long lock; + while (InterlockedExchange(&lock, 1) == 1); + ProcessEventCallback new_hook = man->ProcessNewHook(); + if (new_hook) + new_hook(pid); + lock = 0; + } break; + case HOST_NOTIFICATION_TEXT: + //qDebug() << ((LPCSTR)(buff + 8)); + break; + } + } else { + // jichi 9/28/2013: Debug raw data + //ITH_DEBUG_DWORD9(RecvLen - 0xc, + // buff[0xc], buff[0xd], buff[0xe], buff[0xf], + // buff[0x10], buff[0x11], buff[0x12], buff[0x13]); + + const BYTE *data = buff + data_offset; // th + int len = RecvLen - data_offset; + bool space = ::has_leading_space(data, len); + if (space) { + const BYTE *it = ::Filter(data, len); + len -= it - data; + data = it; + } + if (len >> 31) // jichi 10/27/2013: len is too large, which seldom happens + len = 0; + //man->DispatchText(pid, len ? data : nullptr, hook, retn, split, len, space); + man->DispatchText(pid, data, hook, retn, split, len, space); + } + } + + EnterCriticalSection(&detach_cs); + + HANDLE hDisconnect = IthCreateEvent(nullptr); + + if (STATUS_PENDING == NtFsControlFile( + hTextPipe, + hDisconnect, + 0, 0, + &ios, + CTL_CODE(FILE_DEVICE_NAMED_PIPE, NAMED_PIPE_DISCONNECT, 0, 0), + 0, 0, 0, 0)) + NtWaitForSingleObject(hDisconnect, 0, 0); + + NtClose(hDisconnect); + DetachFromProcess(pid); + man->UnRegisterProcess(pid); + + //NtClearEvent(hDetachEvent); + + LeaveCriticalSection(&detach_cs); + delete[] buff; + + if (::running) + DOUT("detached"); + + //if (::running) { + // swprintf((LPWSTR)buff, FormatDetach, pid); + // ConsoleOutput((LPWSTR)buff); + // NtClose(IthCreateThread(UpdateWindows, 0)); + //} + return 0; +} + +// EOF diff --git a/vnr/texthook/host/settings.h b/vnr/texthook/host/settings.h new file mode 100644 index 0000000..c6919ee --- /dev/null +++ b/vnr/texthook/host/settings.h @@ -0,0 +1,17 @@ +#pragma once + +// settings.h +// 8/24/2013 jichi + +struct Settings { + //bool debug; // whether output debug messages using pipes + int splittingInterval;// time to split text into sentences + bool clipboardFlag; + + Settings() : splittingInterval(200), + clipboardFlag(false) + {} + +}; + +// EOF diff --git a/vnr/texthook/host/textthread.cc b/vnr/texthook/host/textthread.cc new file mode 100644 index 0000000..6f200c2 --- /dev/null +++ b/vnr/texthook/host/textthread.cc @@ -0,0 +1,786 @@ +// textthread.cc +// 8/24/2013 jichi +// Branch IHF/TextThread.cpp, rev 133 +// 8/24/2013 TODO: Clean up this file + +#ifdef _MSC_VER +# pragma warning (disable:4100) // C4100: unreference formal parameter +#endif // _MSC_VER + +#include "settings.h" +#include "textthread.h" +//#include "wintimer/wintimer.h" +#include "vnrhook/include/const.h" +#include "ithsys/ithsys.h" +#include + +MK_BASIC_TYPE(BYTE) +MK_BASIC_TYPE(ThreadParameter) + +static DWORD MIN_DETECT = 0x20; +static DWORD MIN_REDETECT = 0x80; +//#define MIN_DETECT 0x20 +//#define MIN_REDETECT 0x80 +#ifndef CURRENT_SELECT +# define CURRENT_SELECT 0x1000 +#endif +#ifndef REPEAT_NUMBER_DECIDED +# define REPEAT_NUMBER_DECIDED 0x2000 +#endif + +DWORD GetHookName(LPSTR str, DWORD pid, DWORD hook_addr,DWORD max); + +extern Settings *settings; +extern HWND hMainWnd; +void CALLBACK NewLineBuff(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) +{ + KillTimer(hwnd,idEvent); + TextThread *id=(TextThread*)idEvent; + + if (id->Status()&CURRENT_SELECT) + //texts->SetLine(); + id->CopyLastToClipboard(); + id->SetNewLineFlag(); +} + +// jichi 10/27/2013: removed +//void CALLBACK NewLineConsole(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) +//{ +// KillTimer(hwnd,idEvent); +// TextThread *id=(TextThread*)idEvent; +// if (id->Status()&USING_UNICODE) +// id->AddText((BYTE*)L"\r\n",4,true,true); +// if (id->Status()&CURRENT_SELECT) +// { +// //texts->SetLine(); +// } +//} + +// jichi 10/27/2013: removed +//void ReplaceSentence(BYTE* text, int len) +//{ +// __asm int 3 +//} + +TextThread::TextThread(DWORD id, DWORD hook, DWORD retn, DWORD spl, WORD num) : + //,tp + thread_number(num) + // jichi 9/21/2013: zero all fields + , link_number(-1) + , last (0) + , align_space(0) + , repeat_single(0) + , repeat_single_current(0) + , repeat_single_count(0) + , repeat_detect_count(0) + , head(new RepeatCountNode()) + , link(nullptr) + //, filter(nullptr) + , output(nullptr) + , app_data(nullptr) + //, comment(nullptr) + , thread_string(nullptr) + , timer(0) + , status (0) + , repeat_detect_limit(0x80) + , last_sentence(0) + , prev_sentence(0) + , sentence_length(0) + , repeat_index(0) + , last_time(0) +// , tp({id, hook, retn, spl}) +{ + tp.pid = id; + tp.hook = hook; + tp.retn = retn; + tp.spl = spl; + //head = new RepeatCountNode; + //::memset(head, 0, sizeof(RepeatCountNode)); // jichi 9/21/2013: zero memory + //link_number = -1; + //repeat_detect_limit = 0x80; + //filter = nullptr; + //output = nullptr; +} +TextThread::~TextThread() +{ + //KillTimer(hMainWnd,timer); + RepeatCountNode *t = head, + *tt; + while (t) { + tt = t; + t = tt->next; + delete tt; + } + head = nullptr; + //if (comment) { + // delete[] comment; + // comment = nullptr; + //} + if (thread_string) + delete[] thread_string; +} +void TextThread::Reset() +{ + //timer=0; + last_sentence = 0; + //if (comment) { + // delete[] comment; + // comment = nullptr; + //} + MyVector::Reset(); +} +void TextThread::RemoveSingleRepeatAuto(const BYTE *con, int &len) +{ +#ifdef ITH_DISABLE_REPEAT // jichi 9/28/2013: only for debugging purpose + return; +#endif // ITH_DISABLE_REPEAT + WORD *text = (WORD *)con; + if (len <= 2) { + if (repeat_single) { + if (repeat_single_countMIN_REDETECT) { + repeat_detect_count = 0; + status ^= REPEAT_NUMBER_DECIDED; + last = 0; + RepeatCountNode *t = head, + *tt; + while (t) { + tt = t; + t = tt->next; + delete tt; + } + head = new RepeatCountNode; + ::memset(head, 0, sizeof(RepeatCountNode)); // jichi 9/21/2013: zero memory + } + } else { + repeat_detect_count++; + if (last == *text) + repeat_single_current++; + else { + if (last == 0) { + last = *text; + return; + } + if (repeat_single_current == 0) { + status |= REPEAT_NUMBER_DECIDED; + repeat_single = 0; + return; + } + last = *text; + RepeatCountNode *it = head; + if (repeat_detect_count > MIN_DETECT) { + while (it = it->next) + if (it->count>head->count) { + head->count=it->count; + head->repeat=it->repeat; + } + repeat_single = head->repeat; + repeat_single_current = 0; + repeat_detect_count = 0; + status |= REPEAT_NUMBER_DECIDED; + DWORD repeat_sc = repeat_single*4; + if (repeat_sc > MIN_DETECT) { + MIN_DETECT <<= 1; + MIN_REDETECT <<= 1; + } + } else { + bool flag=true; + while (it) { + if (it->repeat == repeat_single_current) { + it->count++; + flag = false; + break; + } + it=it->next; + } + if (flag) { + RepeatCountNode *n = new RepeatCountNode; + n->count = 1; + n->repeat = repeat_single_current; + n->next = head->next; + head->next = n; + } + repeat_single_current = 0; + } //Decide repeat_single + } //Check Repeat + } //repeat_single decided? + } //len + else { + status |= REPEAT_NUMBER_DECIDED; + repeat_single = 0; + } +} + +void TextThread::RemoveSingleRepeatForce(BYTE *con,int &len) +{ + // jichi 9/1/2013: manual repetition count removed + WORD *text = (WORD *)con; + //if (repeat_single_countGetValue(SETTING_REPEAT_COUNT)&&last==*text) { + // len=0; + // repeat_single_count++; + //} + //else + { + last = *text; + repeat_single_count=0; + } +} +void TextThread::RemoveCyclicRepeat(BYTE* &con, int &len) +{ + DWORD current_time = GetTickCount(); + if (status & REPEAT_SUPPRESS) { + if (current_time - last_time < (unsigned)settings->splittingInterval && + ::memcmp(storage + last_sentence + repeat_index, con, len) == 0) { + repeat_index += len; + if (repeat_index>=sentence_length) + repeat_index -= sentence_length; + len = 0; + } else { + repeat_index = 0; + status &= ~REPEAT_SUPPRESS; + } + } else if (status & REPEAT_DETECT) { + if (::memcmp(storage + last_sentence + repeat_index, con, len) == 0) { + int half_length=repeat_index+len; + if (::memcmp(storage + last_sentence, storage + last_sentence + half_length, repeat_index) == 0) { + len=0; + sentence_length=half_length; + status&=~REPEAT_DETECT; + status|=REPEAT_SUPPRESS; + + // jichi 10/27/2013: Not used + //if (status&CURRENT_SELECT) + // ReplaceSentence(storage+last_sentence+half_length,repeat_index); + ClearMemory(last_sentence+half_length,repeat_index); + used-=repeat_index; + repeat_index=0; + } + else + repeat_index += len; + } + else { + repeat_index=0; + status &= ~REPEAT_DETECT; + } + } else { + if (sentence_length == 0) + return; + else if (len <= (int)sentence_length) { + if (memcmp(storage + last_sentence, con, len) == 0) { + status |= REPEAT_DETECT; + repeat_index = len; + if (repeat_index == sentence_length) { + repeat_index = 0; + len = 0; + } + } else if (sentence_length > repeat_detect_limit) { + if (len > 2) { + DWORD u = used; + while (memcmp(storage + u - len, con, len) == 0) + u -= len; + ClearMemory(u, used - u); + used = u; + repeat_index = 0; + // jichi 10/27/2013: Not used + //if (status & CURRENT_SELECT) + // ReplaceSentence(storage + last_sentence, used - u); + status |= REPEAT_SUPPRESS; + len = 0; + } else if (len <= 2) + { + WORD tmp = *(WORD *)(storage + last_sentence); + DWORD index, last_index, tmp_len; + index = used-len; + if (index < last_sentence) + index = last_sentence; + //Locate position of current input. +_again: + *(WORD *)(storage+last_sentence) = *(WORD *)con; + while (*(WORD *)(storage + index) != *(WORD *)con) + index--; + *(WORD *)(storage + last_sentence) = tmp; + if (index > last_sentence) { + tmp_len = used - index; + if (tmp_len <= 2) { + repeat_detect_limit += 0x40; + last_time = current_time; + return; + } + if (index - last_sentence >= tmp_len && + memcmp(storage + index - tmp_len, storage + index, tmp_len) == 0) { + repeat_detect_limit = 0x80; + sentence_length =tmp_len; + index -= tmp_len; + while (memcmp(storage + index - sentence_length, storage + index, sentence_length) == 0) + index -= sentence_length; + repeat_index = 2; + len = 0; + last_index = index; + if (status&USING_UNICODE) { + while (storage[index] == storage[index + sentence_length]) + index -= 2; + index += 2; + while (true) { + tmp = *(WORD *)(storage + index); + if (tmp >= 0x3000 && tmp < 0x3020) + index += 2; + else + break; + } + } else { + DWORD last_char_len; + while (storage[index] == storage[index + sentence_length]) { + last_char_len = LeadByteTable[storage[index]]; + index -= last_char_len; + } + index += last_char_len; + while (storage[index] == 0x81) { + if ((storage[index+1]>>4) == 4) + index += 2; + else + break; + } + } + repeat_index += last_index - index; + status |= REPEAT_SUPPRESS; + last_sentence = index; + + index += sentence_length; + // jichi 10/27/2013: Not used + //if (status&CURRENT_SELECT) + // ReplaceSentence(storage + index, used - index); + + ClearMemory(index, used - index); + //memset(storage + index, 0, used - index); + used = index; + } else { + index--; + goto _again; + } + } + else + repeat_detect_limit += 0x40; + } + } + } + } + last_time = current_time; +} + +void TextThread::ResetRepeatStatus() +{ + last=0; + repeat_single=0; + repeat_single_current=0; + repeat_single_count=0; + repeat_detect_count=0; + RepeatCountNode *t = head->next, + *tt; + while (t) { + tt = t; + t = tt->next; + delete tt; + } + //head=new RepeatCountNode; + head->count = head->repeat = 0; + status &= ~REPEAT_NUMBER_DECIDED; +} +void TextThread::AddLineBreak() +{ + if (sentence_length == 0) return; + if (status&BUFF_NEWLINE) + { + prev_sentence=last_sentence; + sentence_length=0; + if (status & USING_UNICODE) + AddToStore((BYTE *)L"\r\n\r\n", 8); + else + AddToStore((BYTE *)"\r\n\r\n", 4); + if (output) + output(this, 0, 8, TRUE, app_data, false); // jichi 10/27/2013: space is false + last_sentence = used; + status &= ~BUFF_NEWLINE; + } +} +void TextThread::AddText(const BYTE *con, int len, bool new_line, bool space) +{ + if (!con || (len <= 0 && !space)) + return; + if (len && !new_line) { + // jichi 9/1/2013: manual repetition count removed + //if (setman->GetValue(SETTING_REPEAT_COUNT)) { + // status|=REPEAT_NUMBER_DECIDED; + // RemoveSingleRepeatForce(con,len); + //} + //else + RemoveSingleRepeatAuto(con, len); + if (len <= 0 && !space) + return; + } + + // jichi 9/1/2013: manual repetition count removed + //if(setman->GetValue(SETTING_CYCLIC_REMOVE)) { + // //if (status & REPEAT_NUMBER_DECIDED) + // RemoveCyclicRepeat(con,len); + //} + //if (len <= 0) + // return; + + // jichi 10/27/2013: User-defined filter callback is disabled + //if (filter) + // len = filter(this, con,len, new_line, app_data); + //if (len <= 0) + // return; + + if (len && sentence_length == 0) { + if (status & USING_UNICODE) { + if (*(WORD *)con == 0x3000) { // jichi 10/27/2013: why skip unicode space?! + con += 2; + len -= 2; + } + } else if (*(WORD *)con == 0x4081) { + con += 2; + len -= 2; + } + + if (len <= 0 && !space) + return; + } + + if (status & BUFF_NEWLINE) + AddLineBreak(); + + if (len) + if (new_line) { + prev_sentence = last_sentence; + last_sentence = used + 4; + if (status & USING_UNICODE) + last_sentence += 4; + sentence_length = 0; + } else { + SetNewLineTimer(); + if (link) { + const BYTE *send = con; + int l = len; + if (status & USING_UNICODE) { // Although unlikely, a thread and its link may have different encoding. + if ((link->Status() & USING_UNICODE) == 0) { + send = new BYTE[l]; + //::memset(send, 0, l); // jichi 9/26/2013: zero memory + l = WC_MB((LPWSTR)con, (char *)send); + } + link->AddTextDirect(send, l, space); + } else { + if (link->Status() & USING_UNICODE) { + size_t sz = len * 2 + 2; + send = new BYTE[sz]; + //::memset(send, 0, sz); // jichi 9/26/2013: zero memory + l = MB_WC((char *)con, (LPWSTR)send) << 1; + } + link->AddTextDirect(send, l, space); + } + link->SetNewLineTimer(); + if (send != con) + delete[] send; + } + sentence_length += len; + } + + BYTE *data = const_cast(con); // jichi 10/27/2013: TODO: Figure out where con is modified + if (output) + len = output(this, data, len, new_line, app_data, space); + if (AddToStore(data, len)) { + //sentence_length += len; + /*ResetRepeatStatus(); + last_sentence=0; + prev_sentence=0; + sentence_length=len; + repeat_index=0; + status&=~REPEAT_DETECT|REPEAT_SUPPRESS; */ + } +} + +void TextThread::AddTextDirect(const BYTE* con, int len, bool space) // Add to store directly, penetrating repetition filters. +{ + // jichi 10/27/2013: Accordig to the logic, both len and con must be > 0 + if (status & BUFF_NEWLINE) + AddLineBreak(); + //SetNewLineTimer(); + if (link) { + const BYTE *send = con; + int l = len; + if (status & USING_UNICODE) { + if ((link->Status()&USING_UNICODE) == 0) { + send = new BYTE[l]; + //::memset(send, 0, l); // jichi 9/26/2013: zero memory + l = WC_MB((LPWSTR)con,(char*)send); + } + link->AddText(send, l, false, space); // new_line is false + } else { + if (link->Status()&USING_UNICODE) { + size_t sz = len * 2 + 2; + send = new BYTE[sz]; + //::memset(send, 0, sz); // jichi 9/26/2013: zero memory + l = MB_WC((char *)con, (LPWSTR)send) << 1; + } + link->AddText(send, l, false, space); // new_line is false + } + link->SetNewLineTimer(); + if (send != con) + delete[] send; + } + sentence_length += len; + + BYTE *data = const_cast(con); // jichi 10/27/2013: TODO: Figure out where con is modified + if (output) + len = output(this, data, len, false, app_data, space); + AddToStore(data, len); +} + +DWORD TextThread::GetEntryString(LPSTR str, DWORD max) +{ + DWORD len = 0; + if (str && max > 0x40) { + max--; + if (thread_string) { + len = ::strlen(thread_string); + len = len < max ? len : max; + memcpy(str, thread_string, len); + str[len] = 0; + + } else { + len = ::sprintf(str, "%.4X:%.4d:0x%08X:0x%08X:0x%08X:", + thread_number, tp. pid, tp.hook, tp.retn, tp.spl); + + len += GetHookName(str + len, tp.pid, tp.hook, max - len); + thread_string = new char[len + 1]; + //::memset(thread_string, 0, (len+1) * sizeof(wchar_t)); // jichi 9/26/2013: zero memory + thread_string[len] = 0; + ::memcpy(thread_string, str, len); + } + //if (comment) { + // str += len; + // max--; + // DWORD cl = wcslen(comment); + // if (len + cl >= max) + // cl = max - len; + // *str++ = L'-'; + // memcpy(str, comment, cl << 1); + // str[cl] = 0; + // len += cl; + //} + } + return len; +} +// jichi 9/28/2013: removed +//void TextThread::CopyLastSentence(LPWSTR str) +//{ +// int i,j,l; +// if (status&USING_UNICODE) +// { +// if (used>8) +// { +// j=used>0xF0?(used-0xF0):0; +// for (i=used-0xA;i>=j;i-=2) +// { +// if (*(DWORD*)(storage+i)==0xA000D) break; +// } +// if (i>=j) +// { +// l=used-i; +// if (i>j) l-=4; +// j=4; +// } +// else +// { +// i+=2; +// l=used-i; +// j=0; +// } +// memcpy(str,storage+i+j,l); +// str[l>>1]=0; +// } +// else +// { +// memcpy(str,storage,used); +// str[used>>1]=0; +// } +// } +// else +// { +// if (used>4) +// { +// j=used>0x80?(used-0x80):0; +// for (i=used-5;i>=j;i--) +// { +// if (*(DWORD*)(storage+i)==0xA0D0A0D) break; +// } +// if (i>=j) +// { +// l=used-i; +// if (i>j) l-=4; +// j=4; +// } +// else +// { +// i++; +// l=used-i; +// j=0; +// } +// size_t sz = (l|0xF) + 1; +// char *buff = new char[sz]; +// //memset(buff, 0, sz); // jichi 9/26/2013: zero memory +// memcpy(buff, storage + i + j, l); +// buff[l] = 0; +// str[MB_WC(buff, str)] = 0; +// delete[] buff; +// } else { +// storage[used] = 0; +// str[MB_WC((char *)storage, str)] = 0; +// } +// } +//} + +static char clipboard_buffer[0x400]; +// jichi 8/25/2013: clipboard removed +void CopyToClipboard(void* str,bool unicode, int len) +{ + if (settings->clipboardFlag && str && len > 0) + { + int size=(len*2|0xF)+1; + if (len>=1022) return; + memcpy(clipboard_buffer,str,len); + *(WORD*)(clipboard_buffer+len)=0; + HGLOBAL hCopy; + LPWSTR copy; + if (OpenClipboard(0)) + { + if (hCopy=GlobalAlloc(GMEM_MOVEABLE,size)) + { + if (copy=(LPWSTR)GlobalLock(hCopy)) + { + if (unicode) + { + memcpy(copy,clipboard_buffer,len+2); + } + else + copy[MB_WC(clipboard_buffer,copy)]=0; + GlobalUnlock(hCopy); + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT,hCopy); + } + } + CloseClipboard(); + } + } +} +void TextThread::CopyLastToClipboard() +{ + // jichi 8/25/2013: clipboard removed + CopyToClipboard(storage+last_sentence,(status&USING_UNICODE)>0,used-last_sentence); +} + +//void TextThread::ResetEditText() +//{ +// //__asm int 3; +// WCHAR str[0x20]; +// swprintf(str,L"%.8X",_ReturnAddress()); +//} + +// jichi 9/25/2013: Removed +//void TextThread::ExportTextToFile(LPWSTR) //filename) +//{ +// HANDLE hFile=IthCreateFile(filename,FILE_WRITE_DATA,0,FILE_OPEN_IF); +// if (hFile==INVALID_HANDLE_VALUE) return; +// EnterCriticalSection(&cs_store); +// IO_STATUS_BLOCK ios; +// LPVOID buffer=storage; +// DWORD len=used; +// BYTE bom[4]={0xFF,0xFE,0,0}; +// LARGE_INTEGER offset={2,0}; +// if ((status&USING_UNICODE)==0) +// { +// len=MB_WC_count((char*)storage,used); +// buffer = new wchar_t[len+1]; +// MB_WC((char*)storage,(wchar_t*)buffer); +// len<<=1; +// } +// NtWriteFile(hFile,0,0,0,&ios,bom,2,0,0); +// NtWriteFile(hFile,0,0,0,&ios,buffer,len,&offset,0); +// NtFlushBuffersFile(hFile,&ios); +// if (buffer !=storage) +// delete[] buffer; +// NtClose(hFile); +// LeaveCriticalSection(&cs_store); +//} + +//void TextThread::SetComment(LPWSTR str) +//{ +// if (comment) +// delete[] comment; +// size_t sz = wcslen(str); +// comment = new wchar_t[sz + 1]; +// comment[sz] = 0; +// wcscpy(comment, str); +//} + +void TextThread::SetNewLineFlag() { status |= BUFF_NEWLINE; } + +bool TextThread::CheckCycle(TextThread* start) +{ + if (link==start||this==start) return true; + if (link==0) return false; + return link->CheckCycle(start); +} +void TextThread::SetNewLineTimer() +{ + if (thread_number == 0) + // jichi 10/27/2013: Not used + timer = 0; //SetTimer(hMainWnd,(UINT_PTR)this, settings->splittingInterval, NewLineConsole); + else + timer = SetTimer(hMainWnd, (UINT_PTR)this, settings->splittingInterval, NewLineBuff); +} + +DWORD TextThread::GetThreadString(LPSTR str, DWORD max) +{ + DWORD len = 0; + if (max) { + char buffer[0x200]; + char c; + if (thread_string == nullptr) + GetEntryString(buffer, 0x200); //This will allocate thread_string. + LPSTR end = thread_string; + for (; *end; end++); + c = thread_string[0]; + thread_string[0] = ':'; + LPSTR p1 = end; + for (; *p1 != ':'; p1--); + thread_string[0] = c; + if (p1 == thread_string) + return 0; + p1++; + len = end - p1; + if (len >= max) + len = max - 1; + ::memcpy(str, p1, len); + str[len] = 0; + } + + return len; +} +void TextThread::UnLinkAll() +{ + if (link) link->UnLinkAll(); + link = 0; + link_number = -1; +} + +// EOF diff --git a/vnr/texthook/host/textthread.h b/vnr/texthook/host/textthread.h new file mode 100644 index 0000000..4f98838 --- /dev/null +++ b/vnr/texthook/host/textthread.h @@ -0,0 +1,135 @@ +#pragma once + +// textthread.h +// 8/23/2013 jichi +// Branch: ITH/TextThread.h, rev 120 + +#include "host/textthread_p.h" +#include // require _InterlockedExchange + +struct RepeatCountNode { + short repeat; + short count; + RepeatCountNode *next; + + //RepeatCountNode() : repeat(0), count(0), next(nullptr) {} +}; + +struct ThreadParameter { + DWORD pid; // jichi: 5/11/2014: The process ID + DWORD hook; + DWORD retn; // jichi 5/11/2014: The return address of the hook + DWORD spl; // jichi 5/11/2014: the processed split value of the hook parameter +}; + +#define CURRENT_SELECT 0x1000 +#define REPEAT_NUMBER_DECIDED 0x2000 +#define BUFF_NEWLINE 0x4000 +#define CYCLIC_REPEAT 0x8000 +#define COUNT_PER_FOWARD 0x200 +#define REPEAT_DETECT 0x10000 +#define REPEAT_SUPPRESS 0x20000 +#define REPEAT_NEWLINE 0x40000 + +class TextThread; +typedef void (* ConsoleCallback)(LPCSTR text); +typedef void (* ConsoleWCallback)(LPCWSTR text); +typedef DWORD (* ThreadOutputFilterCallback)(TextThread *, BYTE *, DWORD, DWORD, PVOID, bool space); // jichi 10/27/2013: Add space +typedef DWORD (* ThreadEventCallback)(TextThread *); + +//extern DWORD split_time,repeat_count,global_filter,cyclic_remove; + +class TextThread : public MyVector +{ +public: + TextThread(DWORD pid, DWORD hook, DWORD retn, DWORD spl, WORD num); + ~TextThread(); + //virtual void CopyLastSentence(LPWSTR str); + //virtual void SetComment(LPWSTR); + //virtual void ExportTextToFile(LPWSTR filename); + + virtual bool CheckCycle(TextThread *start); + virtual DWORD GetThreadString(LPSTR str, DWORD max); + virtual DWORD GetEntryString(LPSTR str, DWORD max = 0x200); + + void Reset(); + void AddText(const BYTE *con,int len, bool new_line, bool space); // jichi 10/27/2013: add const; remove console; add space + void RemoveSingleRepeatAuto(const BYTE *con, int &len); // jichi 10/27/2013: add const + void RemoveSingleRepeatForce(BYTE *con, int &len); + void RemoveCyclicRepeat(BYTE *&con, int &len); + void ResetRepeatStatus(); + void AddLineBreak(); + //void ResetEditText(); + void ComboSelectCurrent(); + void UnLinkAll(); + void CopyLastToClipboard(); + + //void AdjustPrevRepeat(DWORD len); + //void PrevRepeatLength(DWORD &len); + + //bool AddToCombo(); + bool RemoveFromCombo(); + + void SetNewLineFlag(); + void SetNewLineTimer(); + + BYTE *GetStore(DWORD *len) { if (len) *len = used; return storage; } + DWORD LastSentenceLen() { return used - last_sentence; } + DWORD PID() const { return tp.pid; } + DWORD Addr() const {return tp.hook; } + DWORD &Status() { return status; } + WORD Number() const { return thread_number; } + WORD &Last() { return last; } + WORD &LinkNumber() { return link_number; } + UINT_PTR &Timer() { return timer; } + ThreadParameter *GetThreadParameter() { return &tp; } + TextThread *&Link() { return link; } + //LPCWSTR GetComment() { return comment; } + + ThreadOutputFilterCallback RegisterOutputCallBack(ThreadOutputFilterCallback cb, PVOID data) + { + app_data = data; + return (ThreadOutputFilterCallback)_InterlockedExchange((long*)&output,(long)cb); + } + + ThreadOutputFilterCallback RegisterFilterCallBack(ThreadOutputFilterCallback cb, PVOID data) + { + app_data = data; + return (ThreadOutputFilterCallback)_InterlockedExchange((long*)&filter,(long)cb); + } + + void SetRepeatFlag() { status |= CYCLIC_REPEAT; } + void ClearNewLineFlag() { status &= ~BUFF_NEWLINE; } + void ClearRepeatFlag() { status &= ~CYCLIC_REPEAT; } + +protected: + void AddTextDirect(const BYTE *con, int len, bool space); // jichi 10/27/2013: add const; add space; change to protected + +private: + ThreadParameter tp; + + WORD thread_number, + link_number; + WORD last, + align_space; + WORD repeat_single; + WORD repeat_single_current; + WORD repeat_single_count; + WORD repeat_detect_count; + RepeatCountNode *head; + + TextThread *link; + ThreadOutputFilterCallback filter; // jichi 10/27/2013: Remove filter + ThreadOutputFilterCallback output; + PVOID app_data; + LPSTR thread_string; + UINT_PTR timer; + DWORD status,repeat_detect_limit; + DWORD last_sentence, + prev_sentence, + sentence_length, + repeat_index, + last_time; +}; + +// EOF diff --git a/vnr/texthook/host/textthread_p.h b/vnr/texthook/host/textthread_p.h new file mode 100644 index 0000000..fa93175 --- /dev/null +++ b/vnr/texthook/host/textthread_p.h @@ -0,0 +1,155 @@ +#pragma once +// textthread_p.h +// 8/14/2013 jichi +// Branch: ITH/main_template.h, rev 66 + +#include + +template +void Release(const T &p) { delete p; } + +// Prevent memory release. +// Used when T is basic types and will be automatically released (on stack). +#define MK_BASIC_TYPE(T) \ + template<> \ + void Release(const T &p) {} + +template +struct BinaryEqual { + bool operator ()(const T &a, const T &b, DWORD) { return a == b; } +}; + +template > +class MyVector +{ +public: + MyVector() : size(default_size), used(0) + { + InitializeCriticalSection(&cs_store); + storage = new T[size]; + // jichi 9/21/2013: zero memory + // This would cause trouble if T is not an atomic type + ::memset(storage, 0, sizeof(T) * size); + } + + virtual ~MyVector() + { + if (storage) + delete[] storage; + DeleteCriticalSection(&cs_store); + storage = 0; + } + + void Reset() + { + EnterCriticalSection(&cs_store); + for (int i = 0; i < used; i++) { + Release(storage[i]); + storage[i] = T(); + } + used = 0; + LeaveCriticalSection(&cs_store); + } + void Remove(int index) + { + if (index>=used) + return; + Release(storage[index]); + for (int i = index; i < used; i++) + storage[i] = storage[i+1]; + used--; + } + void ClearMemory(int offset, int clear_size) + { + if (clear_size < 0) + return; + EnterCriticalSection(&cs_store); + if (offset+clear_size <= size) + ::memset(storage+offset, 0, clear_size * sizeof(T)); // jichi 11/30/2013: This is the original code of ITH + LeaveCriticalSection(&cs_store); + //else __asm int 3 + } + int AddToStore(T *con,int amount) + { + if (amount <= 0 || con == 0) + return 0; + int status = 0; + EnterCriticalSection(&cs_store); + if (amount + used + 2 >= size) { + while (amount + used + 2 >= size) + size<<=1; + T *temp; + if (size * sizeof(T) < 0x1000000) { + temp = new T[size]; + if (size > used) + ::memset(temp, 0, (size - used) * sizeof(T)); // jichi 9/25/2013: zero memory + memcpy(temp, storage, used * sizeof(T)); + } else { + size = default_size; + temp = new T[size]; + ::memset(temp, 0, sizeof(T) * size); // jichi 9/25/2013: zero memory + used = 0; + status = 1; + } + delete[] storage; + storage = temp; + } + memcpy(storage+used, con, amount * sizeof(T)); + used += amount; + LeaveCriticalSection(&cs_store); + return status; + } + int Find(const T &item, int start = 0, DWORD control = 0) + { + int c = -1; + for (int i=start; i < used; i++) + if (fCmp(storage[i],item,control)) { + c=i; + break; + } + //if (storage[i]==item) {c=i;break;} + return c; + } + int Used() const { return used; } + T *Storage() const { return storage; } + void LockVector() { EnterCriticalSection(&cs_store); } + void UnlockVector() { LeaveCriticalSection(&cs_store); } +protected: + CRITICAL_SECTION cs_store; + int size, + used; + T *storage; + fComp fCmp; +}; + +// EOF + +/* +#ifndef ITH_STACK +#define ITH_STACK +template +class MyStack +{ +public: + MyStack(): index(0) {} + void push_back(const T& e) + { + if (index JNE SHORT 6F75309F +... +6F753077 PUSH EAX +... +6F75309F MOVE EAX,DWORD PTR SS:[ESP] +6F7530A2 PUSH EAX +6F7530A3 CALL DWORD PTR DS:ntdll.NtClose +... + +# EOF diff --git a/vnr/texthook/ihf_p.cc b/vnr/texthook/ihf_p.cc new file mode 100644 index 0000000..b456c4f --- /dev/null +++ b/vnr/texthook/ihf_p.cc @@ -0,0 +1,583 @@ +// Host_p.cc +// 10/15/2011 jichi + +#include "texthook/ihf_p.h" +#include "texthook/ith_p.h" +#include "texthook/textthread_p.h" +#include "host/host.h" +#include "vnrhook/include/types.h" +#include "ithsys/ithsys.h" +#include "wintimer/wintimer.h" +#include + +#ifdef WITH_LIB_WINMAKER +# include "winmaker/winmaker.h" +#endif // WITH_LIB_WINMAKER + +//#define ITH_RUNNING_EVENT L"ITH_PIPE_EXIST" +//#define ITH_RUNNING_MUTEX L"ITH_RUNNING" +//#define ITH_MUTEX_NAME L"ITH_MAIN_RUNNING" + +//#define DEBUG "ihf_p.cc" +#include "sakurakit/skdebug.h" + +//#define ITH_WITH_LINK + +// - Construction - + +//bool Ihf::debug_ = true; +bool Ihf::enabled_ = true; + +//Settings *Ihf::settings_; +HookManager *Ihf::hookManager_; +qint64 Ihf::messageInterval_ = 250; // 0.25 secs by default, larger than the split_time (0.2sec) in ITH::setman +WId Ihf::parentWindow_; + +QHash Ihf::threadDelegates_; +//QHash Ihf::linkedDelegates_; +QHash Ihf::hookAddresses_; + +char Ihf::keptThreadName_[ITH_THREAD_NAME_CAPACITY]; + +bool Ihf::whitelistEnabled_; +qint32 Ihf::whitelist_[Ihf::WhitelistSize]; + +// Debugging output +//void Ihf::consoleOutput(const char *text) +//{ if (debug_) qDebug() << "texthook:console:" << text; } + +//void Ihf::consoleOutputW(const wchar_t *text) +//{ if (debug_) qDebug() << "texthook:console:" << QString::fromWCharArray(text); } + +void Ihf::init() +{ + IthInitSystemService(); + Host_Init(); +} +void Ihf::destroy() +{ + Host_Destroy(); + IthCloseSystemService(); +} + +// See also: HelloITH/main.cpp +bool Ihf::load() +{ + // 12/20/2013: This would crash the error of failure to create QTimer + //if (!parentWindow_) + + //::wm_register_hidden_class("vnrtexthook.class"); + //parentWindow_ = (WId)::wm_create_hidden_window("vnrtexthook.class", "vnrtexthook"); + + DOUT("enter"); + if (hookManager_) { + DOUT("leave: already loaded"); + return true; + } + + // Single instance protection + //HANDLE hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, ITH_MUTEX_NAME); // in kernel32.dll + //if (hMutex != 0 || ::GetLastError() != ERROR_FILE_NOT_FOUND) { + // ::CloseHandle(hMutex); + // return false; + //} + + // See: ITH/main.cpp + //if (!IthInitSystemService()) { + // DOUT("leave: error: failed to init system service"); + // return false; + //} + + if (::Host_Open()) { +#ifdef WITH_LIB_WINMAKER + if (!parentWindow_) + parentWindow_ = (WId)::wm_create_hidden_window("vnrtexthook"); +#endif // WITH_LIB_WINMAKER + WinTimer::setGlobalWindow(parentWindow_); + ::Host_GetHookManager(&hookManager_); + if (hookManager_) { + //::Host_GetSettings(&settings_); + //settings_->debug = debug_; + + //hookManager_->RegisterConsoleCallback(consoleOutput); + //hookManager_->RegisterConsoleWCallback(consoleOutputW); + //hookManager_->RegisterProcessAttachCallback(processAttach); + //hookManager_->RegisterProcessDetachCallback(processDetach); + //hookManager_->RegisterProcessNewHookCallback(processNewHook); + //hookManager_->RegisterThreadResetCallback(threadReset); + hookManager_->RegisterThreadCreateCallback(threadCreate); + hookManager_->RegisterThreadRemoveCallback(threadRemove); + + ::Host_Start(); + } + } else + ::Host_Close(); + DOUT("leave: hook manager =" << hookManager_); + return hookManager_; +} + +void Ihf::unload() +{ + DOUT("enter: hook manager =" << hookManager_); + if (hookManager_) { + //hookManager_->RegisterProcessAttachCallback(nullptr); + //hookManager_->RegisterProcessDetachCallback(nullptr); + //hookManager_->RegisterProcessNewHookCallback(nullptr); + //hookManager_->RegisterThreadResetCallback(nullptr); + hookManager_->RegisterThreadCreateCallback(nullptr); + hookManager_->RegisterThreadRemoveCallback(nullptr); + // Console output is not unregisterd to avoid segmentation fault + //hookManager_->RegisterConsoleCallback(nullptr); + + ::Host_Close(); + hookManager_ = nullptr; + //settings_ = nullptr; + +#ifdef WITH_LIB_WINMAKER + if (parentWindow_) { + wm_destroy_window(parentWindow_); + parentWindow_ = nullptr; + } +#endif // WITH_LIB_WINMAKER + } + //if (parentWindow_) { + // wm_destroy_window(parentWindow_); + // parentWindow_ = nullptr; + //} + DOUT("leave"); +} + +// - Callbacks - + +//DWORD Ihf::processAttach(DWORD pid) +//{ +// DOUT("enter"); +// Q_UNUSED(pid); +// DOUT("leave"); +// return 0; +//} + +//DWORD Ihf::processDetach(DWORD pid) +//{ +// DOUT("enter"); +// Q_UNUSED(pid); +// DOUT("leave"); +// return 0; +//} + +//DWORD Ihf::processNewHook(DWORD pid) +//{ +// DOUT("enter"); +// Q_UNUSED(pid); +// DOUT("leave"); +// return 0; +//} + +// See: HelloITH/main.cpp +// See: ThreadCreate in ITH/window.cpp +DWORD Ihf::threadCreate(TextThread *t) +{ + Q_ASSERT(t); + DOUT("enter: pid =" << t->PID()); + Q_ASSERT(hookManager_); + + // Propagate UNICODE + // See: ThreadCreate in ITH/window.cpp + //if (ProcessRecord *pr = hookManager_->GetProcessRecord(t->PID())) { + // NtWaitForSingleObject(pr->hookman_mutex, 0, 0); + // Hook *hk = static_cast(pr->hookman_map); + // Q_ASSERT(!hk&&!MAX_HOOK || hk&&MAX_HOOK); + // for (int i = 0; i < MAX_HOOK; i++) { + // if (hk[i].Address() == t->Addr()) { + // if (hk[i].Type() & USING_UNICODE) + // t->Status() |= USING_UNICODE; + // break; + // } + // } + // NtReleaseMutant(pr->hookman_mutex, 0); + //} + auto d = new TextThreadDelegate(t); + bool init = true; + foreach (TextThreadDelegate *it, threadDelegates_) + if (d->signature() == it->signature()) { + TextThreadDelegate::release(d); + d = it; + d->retain(); + init = false; + break; + } + if (init) { + d->setInterval(messageInterval_); + d->setParentWindow(parentWindow_); + updateLinkedDelegate(d); + } + threadDelegates_[t] = d; + t->RegisterOutputCallBack(threadOutput, d); + //t->RegisterFilterCallBack(threadFilter, d); + DOUT("leave"); + return 0; +} + +// See also: HelloITH/main.cpp +DWORD Ihf::threadRemove(TextThread *t) +{ + DOUT("enter"); + Q_ASSERT(t); + + auto p = threadDelegates_.find(t); + if (p != threadDelegates_.end()) { + auto d = p.value(); + //if (!linkedDelegates_.isEmpty()) { + // linkedDelegates_.remove(d); + // while (auto k = linkedDelegates_.key(d)) + // linkedDelegates_.remove(k); + //} + threadDelegates_.erase(p); + TextThreadDelegate::release(d); + } + +#ifdef ITH_WITH_LINK + ::Host_UnLinkAll(t->Number()); +#endif // ITH_WITH_LINK + + DOUT("leave"); + return 0; +} + +// See: HelloITH/main.cpp +DWORD Ihf::threadOutput(TextThread *t, BYTE *data, DWORD dataLength, DWORD newLine, PVOID pUserData, bool space) +{ + DOUT("newLine =" << newLine << ", dataLength =" << dataLength << ", space =" << space); + Q_UNUSED(t) + Q_ASSERT(data); + Q_ASSERT(pUserData); + + auto d = static_cast(pUserData); + //if (TextThreadDelegate *link = findLinkedDelegate(d)) + // d = link; + Q_ASSERT(d); + if (!enabled_ || + whitelistEnabled_ && + !whitelistContains(d->signature()) && + !(keptThreadName_[0] && d->nameEquals(keptThreadName_))) { + DOUT("leave: ignored"); + return dataLength; + } + if (newLine) + d->touch(); + //d->flush(); // new line data are ignored + else if (dataLength || space) + d->append(reinterpret_cast(data), dataLength, space); + //QString text = QString::fromLocal8Bit(reinterpret_cast(data), len); + DOUT("leave"); + return dataLength; +} + +//TextThreadDelegate *Ihf::findLinkedDelegate(TextThreadDelegate *d) +//{ +// Q_ASSERT(d); +// if (!linkedDelegates_.isEmpty()) { +// auto p = linkedDelegates_.find(d); +// if (p != linkedDelegates_.end()) +// return p.value(); +// } +// return nullptr; +//} + +void Ihf::updateLinkedDelegate(TextThreadDelegate *d) +{ +#ifdef ITH_WITH_LINK + Q_ASSERT(t); + foreach (TextThreadDelegate *it, threadDelegates_) + if (it->delegateOf(d)) + ::Host_AddLink(d->threadNumber(), it->threadNumber()); + else if (d->delegateOf(it)) + ::Host_AddLink(it->threadNumber(), d->threadNumber()); +#else + Q_UNUSED(d); +#endif // ITH_WITH_LINK +} + +// - Injection - + +// See: Host_InjectByPID in IHF/main.cpp +// See: InjectThread in ITH/profile.cpp +bool Ihf::attachProcess(DWORD pid) +{ + DOUT("enter: pid =" << pid); + bool ok = ::Host_InjectByPID(pid); + + //enum { AttachDelay = 500 }; // in msec + //::Sleep(AttachDelay); + + DOUT("leave: ret =" << ok); + return ok; +} + +// See: Host_ActiveDetachProcess in IHF/main.cpp +bool Ihf::detachProcess(DWORD pid) { return ::Host_ActiveDetachProcess(pid); } +bool Ihf::hijackProcess(DWORD pid) { return ::Host_HijackProcess(pid); } + +// - Hook - + +// See: Host_ModifyHook in IHF/main.cpp +bool Ihf::updateHook(ulong pid, const QString &code) +{ + DOUT("enter: pid =" << pid << ", code =" << code); + Q_ASSERT(pid); + HookParam hp = {}; + if (!Ith::parseHookCode(code, &hp)) { + DOUT("leave: failed to parse hook code"); + return false; + } + + DWORD hh = ::Host_ModifyHook(pid, &hp); + bool ok = ~hh; + DOUT("leave: ret =" << ok); + return ok; +} + +// See: Host_InsertHook in IHF/main.cpp +bool Ihf::addHook(ulong pid, const QString &code, const QString &name, bool verbose) +{ + DOUT("enter: pid =" << pid << ", name =" << name << ", code =" << code); + Q_ASSERT(pid); + if (hookAddresses_.contains(code)) { + DOUT("leave: already added"); + return false; + } + + HookParam hp = {}; + if (!Ith::parseHookCode(code, &hp, verbose)) { + DOUT("leave: failed to parse hook code"); + return false; + } + + DWORD hh = ::Host_InsertHook(pid, &hp, name.toAscii()); + //DWORD hh = ::NewHook(hp, nameBuf); + bool ok = ~hh; + if (ok && hp.address) { + DOUT("hook address =" << hp.address); + hookAddresses_[code] = hp.address; + } + DOUT("leave: ok =" << ok); + return ok; +} + +// See: Host_RemoveHook in IHF/main.cpp +bool Ihf::removeHook(ulong pid, const QString &code) +{ + DOUT("enter: pid =" << pid << ", code =" << code); + Q_ASSERT(pid); + auto p = hookAddresses_.find(code); + if (p == hookAddresses_.end()) { + DOUT("leave: hook not added"); + return false; + } + DWORD addr = p.value(); + Q_ASSERT(addr); + hookAddresses_.erase(p); + + DWORD hh = ::Host_RemoveHook(pid, addr); + bool ok = ~hh; + DOUT("leave: ret =" << ok); + return ok; +} + +bool Ihf::verifyHookCode(const QString &code) +{ return Ith::verifyHookCode(code); } + +// - Whitelist - + +QList Ihf::whitelist() +{ + QList ret; + const qint32 *p = whitelist_; + while (*p) + ret.append(*p++); + return ret; +} + +void Ihf::clearWhitelist() { *whitelist_= 0; } + +void Ihf::setWhitelist(const QList &l) +{ + qint32 *p = whitelist_; + if (!l.isEmpty()) + foreach (qint32 it, l) { + *p++ = it; + if (p >= whitelist_ + WhitelistSize) + break; + } + whitelist_[qMin(l.size(), WhitelistSize -1)] = 0; +} + +bool Ihf::whitelistContains(qint32 signature) +{ + const qint32 *p = whitelist_; + while (*p) + if (signature == *p++) + return true; + return false; +} + +// EOF + +/* +BYTE LeadByteTable[0x100] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1 +}; + +DWORD Ihf::threadFilter(TextThread *thread, BYTE *out, DWORD len, DWORD new_line, PVOID data) +{ + DWORD status = thread->Status(); + + if (!new_line && thread->Number() != 0) + { + if (status & USING_UNICODE) + { + DWORD i, j; + len >>= 1; + WCHAR c, *str = (LPWSTR)out; + for (i = 0, j = 0; i < len; i++) + { + c = str[i]; + //if (!uni_filter->Check(c)) + str[j++] = c; + + } + memset(str + j, 0, (len - j) << 1); + len = j << 1; + } + else + { + WORD c; + DWORD i, j; + for (i = 0, j = 0; i < len; i++) + { + c = out[i]; + if (LeadByteTable[c] == 1) + { + //if (!mb_filter->Check(c)) + out[j++] = c & 0xFF; + } + else if (i + 1 < len) + { + + c = out[i + 1]; + c <<= 8; + c |= out[i]; + //if (!mb_filter->Check(c)) + { + out[j++] = c & 0xFF; + out[j++] = c >> 8; + } + i++; + } + } + memset(out + j, 0, len - j); + len = j; + } + } + return len; +} +*/ + + +/* +// jichi: 10/15/2011: FIXME: This overload will infect the entire program, +// even source files that exclude this header, which is unexpected. +// No idea what is the trade off of this behavior on performance and liability. +// Lots of Qt stuff doesn't work such as QString::toStdString. +// I have to use dynamic linkage to avoid being polluted by this module. +// +// original author: HEAP_ZERO_MEMORY flag is critical. All new object are assumed with zero initialized. +// jichi: 10/20/2011: I think the only reason to use Rtl heap here is to ensure HEAP_ZERO_MEMORY, +// which is really a bad programming style and incur unstability on heap memory allocation. +// ::RtlFreeHeap crash on DLL debug mode. Replace it with standard malloc/free. +// ::hHeap handle is also removed from ith/sys.c.cc + +inline void * __cdecl operator new(size_t lSize) +{ return ::RtlAllocateHeap(::hHeap, HEAP_ZERO_MEMORY, lSize); } + +inline void * __cdecl operator new[](size_t lSize) +{ return ::RtlAllocateHeap(::hHeap, HEAP_ZERO_MEMORY, lSize); } + +inline void __cdecl operator delete(void *pBlock) +{ ::RtlFreeHeap(::hHeap, 0, pBlock); } + +inline void __cdecl operator delete[](void* pBlock) +{ ::RtlFreeHeap(::hHeap, 0, pBlock); } + + +#include +#include +inline void * __cdecl operator new(size_t size) throw() +{ + if (!size) // When the value of the expression in a direct-new-declarator is zero, + size = 4; // the allocation function is called to allocatean array with no elements.(ISO) + + void *p = malloc(size); + if (p) + memset(p, 0, size); + return p; +} + +inline void * __cdecl operator new[](size_t size) throw() +{ + if (!size) // When the value of the expression in a direct-new-declarator is zero, + size = 4; // the allocation function is called to allocatean array with no elements.(ISO) + + void *p = malloc(size); + if (p) + memset(p, 0, size); + return p; +} + +inline void __cdecl operator delete(void *p) throw() { free(p); } +inline void __cdecl operator delete[](void *p) throw() { free(p); } +*/ + + +//QString +//Ihf::getHookNameById(ulong hookId) +//{ +// QString ret; +// if (hookId) { +// auto p = reinterpret_cast(hookId); +// if (p->good()) +// ret = p->name(); +// } +// return ret; +//} + +//DWORD ProcessAttach(DWORD pid) +//{ +// DOUT("process attached, pid =" << pid); +// return 0; +//} +//DWORD ProcessDetach(DWORD pid) +//{ +// DOUT("process detached, pid =" << pid); +// return 0; +//} +//DWORD ProcessNewHook(DWORD pid) +//{ +// DOUT("process has new hook inserted, pid =" << pid); +// return 0; +//} diff --git a/vnr/texthook/ihf_p.h b/vnr/texthook/ihf_p.h new file mode 100644 index 0000000..2d3e2b8 --- /dev/null +++ b/vnr/texthook/ihf_p.h @@ -0,0 +1,117 @@ +#pragma once + +// ihf_p.h +// 10/15/2011 jichi +// Internal header. +// Wrapper of IHF functions. + +#include +#include +#include +#include // for WId + +//struct Settings; // opaque in ith/host/settings.h +class HookManager; // opaque in ith/host/hookman.h +class TextThread; // opaque in ith/host/textthread.h +class TextThreadDelegate; + +enum { ITH_THREAD_NAME_CAPACITY = 0x200 }; // used internally by ITH + +class Ihf +{ + Ihf() {} // Singleton + + static bool enabled_; + + //static Settings *settings_; + static HookManager *hookManager_; + static qint64 messageInterval_; + static WId parentWindow_; + + static QHash threadDelegates_; + //static QHash linkedDelegates_; + static QHash hookAddresses_; + + enum { WhitelistSize = 0x20 + 1 }; // ITH capacity is 0x20 + static qint32 whitelist_[WhitelistSize]; // List of signatures. The last element is zero. I.e., at most BlackSize-1 threads. + static bool whitelistEnabled_; + static char keptThreadName_[ITH_THREAD_NAME_CAPACITY]; + //static QString userDefinedThreadName_; + +public: + + // - Initialization - + static void init(); + static void destroy(); + + static bool load(); + static bool isLoaded() { return hookManager_; } + static void unload(); + + // - Properties - + + static bool isEnabled() { return enabled_; } + static void setEnabled(bool t) { enabled_ = t; } + + /// A valid window handle is required to make ITH work + static WId parentWindow() { return parentWindow_; } + static void setParentWindow(WId hwnd) { parentWindow_ = hwnd; } + + /// Timeout (msecs) for a text message + static qint64 messageInterval() { return messageInterval_; } + static void setMessageInterval(qint64 msecs) { messageInterval_ = msecs; } + + // - Injection - + static bool attachProcess(ulong pid); + static bool detachProcess(ulong pid); + static bool hijackProcess(ulong pid); + + /// Add hook code + static bool addHook(ulong pid, const QString &code, const QString &name = QString(), bool verbose = true); + static bool updateHook(ulong pid, const QString &code); // not used + static bool removeHook(ulong pid, const QString &code); + static bool verifyHookCode(const QString &code); + + // - Whitelist - + static bool isWhitelistEnabled() { return whitelistEnabled_; } + static void setWhitelistEnabled(bool t) { whitelistEnabled_ = t; } + + static QList whitelist(); + static void setWhitelist(const QList &l); + static void clearWhitelist(); + + //static QString userDefinedThreadName() { return userDefinedThreadName_; } + //static void setUserDefinedThreadName(const QString &val) { userDefinedThreadName_ = val; } + static const char *keptThreadName() { return keptThreadName_; } + + static void setKeptThreadName(const QString &v) + { + if (v.size() < ITH_THREAD_NAME_CAPACITY) + ::strcpy(keptThreadName_, v.toAscii()); + else + setKeptThreadName(v.left(ITH_THREAD_NAME_CAPACITY - 1)); + } + +private: + static bool whitelistContains(qint32 signature); + + // - Callbacks - + //static ulong processAttach(ulong pid); + //static ulong processDetach(ulong pid); + //static ulong processNewHook(ulong pid); + + static ulong threadCreate(_In_ TextThread *t); + static ulong threadRemove(_In_ TextThread *t); + static ulong threadOutput(_In_ TextThread *t, _In_ uchar *data, _In_ ulong dataLength, _In_ ulong bNewLine, _In_ void *pUserData, _In_ bool space); + //static ulong threadFilter(_In_ TextThread *t, _Out_ uchar *data, _In_ ulong dataLength, _In_ ulong bNewLine, _In_ void *pUserData); + //static ulong threadReset(TextThread *t); + //static void consoleOutput(const char *text); + //static void consoleOutputW(const wchar_t *text); + + // - Linked threasds - +private: + //static TextThreadDelegate *findLinkedDelegate(TextThreadDelegate *d); + static void updateLinkedDelegate(TextThreadDelegate *d); +}; + +// EOF diff --git a/vnr/texthook/ith_p.cc b/vnr/texthook/ith_p.cc new file mode 100644 index 0000000..7a7d3d7 --- /dev/null +++ b/vnr/texthook/ith_p.cc @@ -0,0 +1,275 @@ +// ith_p.cc +// 10/15/2011 jichi + +#include "texthook/ith_p.h" +#include "vnrhook/include/const.h" +#include "vnrhook/include/types.h" +#include + +#define DEBUG "ith_p.cc" +#include "sakurakit/skdebug.h" + +// HookParam copied from ITH/common.h: +// struct HookParam // size = 40 (0x24) +// { +// typedef void (*DataFun)(DWORD, HookParam*, DWORD*, DWORD*, DWORD*); +// +// DWORD addr; // 4 +// DWORD off, // 8 +// ind, // 12 +// split, // 16 +// split_ind; // 20 +// DWORD module, // 24 +// function; // 28 +// DataFun text_fun; // 32, jichi: is this the same in x86 and x86_64? +// DWORD type; // 36 +// WORD length_offset; // 38 +// BYTE hook_len, // 39 +// recover_len; // 40 +// }; + + +// - Implementation Details - + +namespace { namespace detail { // unnamed + +// ITH ORIGINAL CODE BEGIN + +// See: ITH/ITH.h +// Revision: 133 +inline DWORD Hash(_In_ LPWSTR module, int length = -1) +{ + bool flag = length == -1; + DWORD hash = 0; + for (; *module && (flag || length--); module++) + hash = _rotr(hash,7) + *module; //hash=((hash>>7)|(hash<<25))+(*module); + return hash; +} + +// See: ITH/command.cpp +// Revision: 133 +// +// jichi note: str[0xF] will be modified and restored. +// So, the buffer of str must be larger than 0xF. +int Convert(_In_ LPWSTR str, _Out_ DWORD *num, _In_ LPWSTR delim) +{ + if (!num) + return -1; + WCHAR t = *str, + tc = *(str + 0xF); + WCHAR temp[0x10] = {}; + LPWSTR it = temp, + istr = str, + id = temp; + if (delim) { + id = wcschr(delim, t); + str[0xF] = delim[0]; // reset str[0xF] in case of out-of-bound iteration + } + else + str[0xF] = 0; // reset str[0xF] in case of out-of-bound iteration + while (!id && t) { + *it = t; + it++; istr++; + t = *istr; + if (delim) + id = wcschr(delim, t); + } + swscanf(temp, L"%x", num); + str[0xF] = tc; // restore the str[0xF] + if (!id || istr - str == 0xF) + return -1; + + if (!t) + return istr - str; // >= 0 + else + return id - delim; // >= 0 +} + +// See: ITH/command.cpp +// Revision: 133 +// +// jichi note: str[0xF] will be modified and restored. +// So, the buffer of cmd must be larger than 0xF*2 = 0x1F. +bool Parse(_In_ LPWSTR cmd, _Out_ HookParam &hp) +{ + ::memset(&hp, 0, sizeof(hp)); + + int t; + bool accept = false; + DWORD *data = &hp.offset; // + LPWSTR offset = cmd + 1; + LPWSTR delim_str = L":*@!"; + LPWSTR delim = delim_str; + if (*offset == L'n' || *offset == 'N') { + offset++; + hp.type |= NO_CONTEXT; + } + // jichi 4/25/2015: Add support for fixing hook + if (*offset == L'f' || *offset == 'F') { + offset++; + hp.type |= FIXING_SPLIT; + } + if (*offset == L'j' || *offset == 'J') { // 11/22/2015: J stands for Japanese only + offset++; + hp.type |= NO_ASCII; + } + while (!accept) { + t = Convert(offset, data, delim); + if (t < 0) + return false; //ConsoleOutput(L"Syntax error."); + offset = ::wcschr(offset , delim[t]); + if (offset) + offset++; // skip the current delim + else //goto _error; + return false; //ConsoleOutput(L"Syntax error."); + switch (delim[t]) { + case L':': + data = &hp.split; + delim = delim_str + 1; + hp.type |= USING_SPLIT; + break; + case L'*': + if (hp.split) { + data = &hp.split_index; + delim = delim_str + 2; + hp.type |= SPLIT_INDIRECT; + } + else { + hp.type |= DATA_INDIRECT; + data = &hp.index; + } + break; + case L'@': + accept = true; + break; + } + } + t = Convert(offset, &hp.address, delim_str); + if (t < 0) + return false; + if (hp.offset & 0x80000000) + hp.offset -= 4; + if (hp.split & 0x80000000) + hp.split -= 4; + LPWSTR temp = offset; + offset = ::wcschr(offset, L':'); + if (offset) { + hp.type |= MODULE_OFFSET; + offset++; + delim = ::wcschr(offset, L':'); + + if (delim) { + *delim = 0; + delim++; + _wcslwr(offset); + hp.function = Hash(delim); + hp.module = Hash(offset, delim - offset - 1); + hp.type |= FUNCTION_OFFSET; + } + else + hp.module = Hash(_wcslwr(offset)); + + } else { + offset = ::wcschr(temp, L'!'); + if (offset) { + hp.type |= MODULE_OFFSET; + swscanf(offset + 1, L"%x", &hp.module); + offset = ::wcschr(offset + 1, L'!'); + if (offset) { + hp.type |= FUNCTION_OFFSET; + swscanf(offset + 1, L"%x", &hp.function); + } + } + } + switch (*cmd) { + case L's': + case L'S': + hp.type |= USING_STRING; + break; + case L'e': + case L'E': + hp.type |= STRING_LAST_CHAR; + case L'a': + case L'A': + hp.type |= BIG_ENDIAN; + hp.length_offset = 1; + break; + case L'b': + case L'B': + hp.length_offset = 1; + break; + // jichi 12/7/2014: Disabled + //case L'h': + //case L'H': + // hp.type |= PRINT_DWORD; + case L'q': + case L'Q': + hp.type |= USING_STRING | USING_UNICODE; + break; + case L'l': + case L'L': + hp.type |= STRING_LAST_CHAR; + case L'w': + case L'W': + hp.type |= USING_UNICODE; + hp.length_offset = 1; + break; + default: ; + } + //ConsoleOutput(L"Try to insert additional hook."); + return true; +} + +// ITH ORIGINAL CODE END + +}} // unnamed detail + +// - ITH API - + +// Sample code: L"/HS-4:-14@4383C0" (WHITE ALBUM 2) +bool Ith::parseHookCode(const QString &code, HookParam *hp, bool verbose) +{ +#define HCODE_PREFIX "/H" + enum { HCODE_PREFIX_LEN = sizeof(HCODE_PREFIX) -1 }; // 2 + if (!hp || !code.startsWith(HCODE_PREFIX)) + return false; + if (verbose) + DOUT("enter: code =" << code); + else + DOUT("enter"); + + size_t bufsize = qMax(0xFF, code.size() + 1); // in case detail::Convert modify the buffer + auto buf = new wchar_t[bufsize]; + code.toWCharArray(buf); + buf[code.size()] = 0; + + bool ret = detail::Parse(buf + HCODE_PREFIX_LEN, *hp); + delete[] buf; +#ifdef DEBUG + if (ret && verbose) + qDebug() + << "addr:" << hp->address + << ", text_fun:" << hp->text_fun + << ", function:"<< hp->function + << ", hook_len:" << hp->hook_len + << ", ind:" << hp->index + << ", length_offset:" << hp->length_offset + << ", module:" << hp->module + << ", off:" <offset + << ", recover_len:" << hp->recover_len + << ", split:" << hp->split + << ", split_ind:" << hp->split_index + << ", type:" << hp->type; +#endif // DEBUG + DOUT("leave: ret =" << ret); + return ret; +#undef HOOK_CODE_PREFIX +} + +bool Ith::verifyHookCode(const QString &code) +{ + HookParam hp = {}; + return parseHookCode(code, &hp); +} + +// EOF diff --git a/vnr/texthook/ith_p.h b/vnr/texthook/ith_p.h new file mode 100644 index 0000000..66fbd64 --- /dev/null +++ b/vnr/texthook/ith_p.h @@ -0,0 +1,20 @@ +#pragma once + +// ith_p.h +// 10/15/2011 jichi +// Internal header. +// Wrapper of functions from ITH. + +#include + +struct HookParam; // opaque, declared in ITH/common.h + +namespace Ith { + +/// Parse hook code, and save the result to hook param if succeeded. +bool parseHookCode(_In_ const QString &code, _Out_ HookParam *hp, bool verbose = true); +bool verifyHookCode(_In_ const QString &code); + +} // namespace Ith + +// EOF diff --git a/vnr/texthook/texthook.cc b/vnr/texthook/texthook.cc new file mode 100644 index 0000000..0d4aae6 --- /dev/null +++ b/vnr/texthook/texthook.cc @@ -0,0 +1,414 @@ +// texthook.cc +// 10/14/2011 jichi + +#include "texthook/texthook.h" +#include "texthook/texthook_p.h" +#include "texthook/ihf_p.h" +#include "texthook/textthread_p.h" +#include "texthook/winapi_p.h" +#include + +//#define DEBUG "texthook.cc" +#include "sakurakit/skdebug.h" + +//#include +//namespace { int _ = IthInitSystemService(); } + +/** Private class */ + +TextHookPrivate *TextHookPrivate::instance_; + +/** Public class */ + +// - Construction - + +//TextHook *TextHook::g_; +//TextHook *TextHook::globalInstance() { static Self g; return &g; } + +//TextHook::TextHook(QObject *parent) +// : Base(parent), d_(new D) +//{} + +TextHook::TextHook(QObject *parent) + : Base(parent), d_(new D(this)) +{ + Ihf::init(); + //Ihf::setUserDefinedThreadName(d_->source); + DOUT("pass"); +} + +TextHook::~TextHook() +{ + DOUT("enter"); + if (isActive()) + stop(); + delete d_; + + Ihf::destroy(); + DOUT("leave"); +} + +// - Properties - + +int TextHook::dataCapacity() const +{ return TextThreadDelegate::capacity(); } + +void TextHook::setDataCapacity(int value) +{ TextThreadDelegate::setCapacity(value); } + +bool TextHook::removesRepeat() const +{ return TextThreadDelegate::removesRepeat(); } + +void TextHook::setRemovesRepeat(bool value) +{ TextThreadDelegate::setRemovesRepeat(value); } + +bool TextHook::keepsSpace() const +{ return TextThreadDelegate::keepsSpace(); } + +void TextHook::setKeepsSpace(bool value) +{ TextThreadDelegate::setKeepsSpace(value); } + +bool TextHook::wideCharacter() const +{ return TextThreadDelegate::wideCharacter(); } + +void TextHook::setWideCharacter(bool value) +{ TextThreadDelegate::setWideCharacter(value); } + +// see: ITH/common.h +int TextHook::capacity() const +{ return 0x20; } + +QString TextHook::defaultHookName() const +{ return d_->source; } + +void TextHook::setDefaultHookName(const QString &name) +{ + d_->source = name; + //Ihf::setUserDefinedThreadName(name); +} + +bool TextHook::isEnabled() const +{ return d_->enabled; } + +void TextHook::setEnabled(bool t) +{ + d_->enabled = t; + Ihf::setEnabled(t); +} + +bool TextHook::isActive() const +{ return Ihf::isLoaded(); } + +void TextHook::start() +{ Ihf::load(); } + +void TextHook::stop() +{ + if (!isEmpty()) + clear(); + Ihf::unload(); +} + +WId TextHook::parentWinId() const { return Ihf::parentWindow(); } +void TextHook::setParentWinId(WId hwnd) { Ihf::setParentWindow(hwnd); } + +int TextHook::interval() const +{ return Ihf::messageInterval(); } + +void TextHook::setInterval(int msecs) +{ Ihf::setMessageInterval(msecs); } + +// - Injection - + +void TextHook::clear() +{ + DOUT("enter"); + foreach (ulong pid, d_->pids) + detachProcess(pid); + if (!d_->hooks.isEmpty()) + d_->hooks.clear(); + clearThreadWhitelist(); + DOUT("leave"); +} + +bool TextHook::containsProcess(ulong pid) const { return d_->pids.contains(pid); } +bool TextHook::isEmpty() const { return d_->pids.isEmpty(); } + +//QList TextHook::attachedProcesses(bool checkActive) const +//{ +// if (isEmpty() || !checkActive) +// return d_->pids; +// +// QList ret; +// foreach (ulong pid, d_->pids) +// if (winapi::IsProcessActiveWithId(pid)) +// ret.append(pid); +// return ret; +//} + +//ulong TextHook::currentProccess() const +//{ return anyAttachedProcess(true); } // check active = true + +//ulong TextHook::anyAttachedProcess(bool checkActive) const +//{ +// if (isEmpty()) +// return 0; +// if (!checkActive) +// return d_->pids.first(); +// +// foreach (ulong pid, d_->pids) +// if (winapi::IsProcessActiveWithId(pid)) +// return pid; +// return 0; +//} + +//bool TextHook::attachOneProcess(ulong pid, bool checkActive) +//{ +// DOUT("enter: pid =" << pid); +// DOUT("isAttached =" << containsProcess(pid)); +// if (!isActive()) +// start(); +// +// if (checkActive && !winapi::IsProcessActiveWithId(pid)) { +// DOUT("leave: ret = false, isActive = false"); +// return false; +// } +// +// if (containsProcess(pid)) { +// DOUT("leave: pid already attached"); +// return true; +// } +// +// detachAllProcesses(); +// +// bool ret = Ihf::attachProcess(pid); +// if (ret && !containsProcess(pid)) { +// d_->pids.removeAll(pid); +// d_->pids.append(pid); +// emit processAttached(pid); +// } +// +// DOUT("leave: ret =" << ret); +// return ret; +//} + +bool TextHook::attachProcess(ulong pid, bool checkActive) +{ + DOUT("enter: pid =" << pid << ", isAttached =" << containsProcess(pid)); + if (!isActive()) + start(); + Q_ASSERT(isActive()); + DOUT("isActive =" << isActive()); + + if (checkActive && !winapi::IsProcessActiveWithId(pid)) { + DOUT("leave: ret = false, isActive = false"); + return false; + } + + bool ret = Ihf::attachProcess(pid); + if (ret) { + d_->pids.insert(pid); + emit processAttached(pid); + } + + DOUT("leave: ret =" << ret); + return ret; +} + +bool TextHook::detachProcess(ulong pid, bool checkActive) +{ + DOUT("enter: pid =" << pid << ", isAttached =" << containsProcess(pid)); + Q_ASSERT(isActive()); + + auto it = d_->pids.find(pid); + if (it == d_->pids.end()) { + DOUT("leave: ret = false, not attached"); + return false; + } + d_->pids.erase(it); + d_->hooks.remove(pid); + + if (checkActive && !winapi::IsProcessActiveWithId(pid)) { + emit processDetached(pid); + DOUT("leave: ret = false, isActive = false"); + return false; + } + + bool ret = Ihf::detachProcess(pid); + //try { + // ret = Ihf::detachProcess(pid); + //} catch (...) { + // DOUT("warning: detach exception"); + //} + + emit processDetached(pid); + DOUT("leave: ret =" << ret); + return ret; +} + +bool TextHook::hijackProcess(ulong pid) +{ + DOUT("enter: pid =" << pid); + Q_ASSERT(isActive()); + + if (!containsProcess(pid)) { + DOUT("leave: aborted, process not attached"); + return false; + } + + // 7/12/2015: Function disabled + return true; + + //bool ret = Ihf::hijackProcess(pid); + //DOUT("leave: ret =" << ret); + //return ret; +} + +//void TextHook::detachAllProcesses() +//{ +// DOUT("enter"); +// foreach (ulong pid, d_->pids) +// detachProcess(pid); +// if (!d_->hooks.isEmpty()) +// d_->hooks.clear(); +// DOUT("leave"); +//} + +// - Hook - + +//bool TextHook::containsHook(ulong pid) const +//{ return d_->hooks.contains(pid); } +// +//bool TextHook::containsHook(ulong pid, const QString &code) const +//{ return processHook(pid) == code; } + +bool TextHook::addHookCode(ulong pid, const QString &code, const QString &name, bool verbose) +{ + DOUT("enter: pid =" << pid << ", code =" << code); + if (isEmpty() || !containsProcess(pid)) { + DOUT("leave: failed, process not attached"); + return false; + } + if (d_->hooks.contains(pid)) { + DOUT("leave: failed, hook already exists"); + return false; + } + bool ok = Ihf::addHook(pid, code, + name.isEmpty() ? defaultHookName() : name, + verbose); + if (ok) + d_->hooks[pid] = code; + DOUT("leave: ret =" << ok); + return ok; +} + +bool TextHook::verifyHookCode(const QString &code) { return Ihf::verifyHookCode(code); } + +bool TextHook::removeHookCode(ulong pid) +{ + DOUT("enter"); + auto p = d_->hooks.find(pid); + if (p == d_->hooks.end()) { + DOUT("leave: not hooked"); + return false; + } + + //DOUT("remove existing hook, THIS SHOULD NOT HAPPEN"); + bool ok = Ihf::removeHook(pid, p.value()); + d_->hooks.erase(p); + DOUT("leave: ret =" << ok); + return ok; +} + +//QString TextHook::processHook(ulong pid) const +//{ +// auto p = d_->hooks.find(pid); +// return p == d_->hooks.end() ? QString() : p.value(); +//} + +bool TextHook::isThreadWhitelistEnabled() const { return Ihf::isWhitelistEnabled(); } + +void TextHook::setThreadWhitelistEnabled(bool t) { Ihf::setWhitelistEnabled(t); } + +QList TextHook::threadWhitelist() const { return Ihf::whitelist(); } + +void TextHook::setThreadWhitelist(const QList &sigs) { Ihf::setWhitelist(sigs); } + +void TextHook::clearThreadWhitelist() { Ihf::clearWhitelist(); } + +QString TextHook::keptThreadName() const { return Ihf::keptThreadName(); } + +void TextHook::setKeptThreadName(const QString &v) { Ihf::setKeptThreadName(v); } + +// EOF + +/* +QString +TextHook::guessEncodingForFile(const QString &fileName) +{ + static QHash db; + if (db.isEmpty()) { + db["malie.exe"] = "UTF-16"; + } + auto p = db.find(fileName); + return p == db.end() ? QString() : p.value(); +} + +// - Helpers - + +bool +TextHook::isStandardHookName(const QString &name) const +{ + static QSet hashes; + if (hashes.isEmpty()) { +#define ADD(_text) hashes.insert(qHash(QString(_text))) + ADD("ConsoleOutput"); + ADD("GetTextExtentPoint32A"); + ADD("GetGlyphOutlineA"); + ADD("ExtTextOutA"); + ADD("TextOutA"); + ADD("GetCharABCWidthsA"); + ADD("DrawTextA"); + ADD("DrawTextExA"); + ADD("GetTextExtentPoint32W"); + ADD("GetGlyphOutlineW"); + ADD("ExtTextOutW"); + ADD("TextOutW"); + ADD("GetCharABCWidthsW"); + ADD("DrawTextW"); + ADD("DrawTextExW"); +#undef ADD + } + uint h = qHash(name); + return hashes.contains(h); +} + +bool +TextHook::isKnownHookForProcess(const QString &hook, const QString &proc) const +{ + // TODO: update database on line periodically + qDebug() << "qth::isKnownHookForProcess: hook =" << hook << ", proc =" << proc; + + static QSet hashes; + if (hashes.isEmpty()) { +#define ADD(_hook, _proc) hashes.insert(qHash(QString(_hook) + "\n" + _proc)) + //ADD("Malie", "malie"); // light + ADD("GetGlyphOutlineA", "STEINSGATE"); + ADD("StuffScriptEngine", "EVOLIMIT"); +#undef ADD + } + uint h = qHash(hook + "\n" + proc); + return hashes.contains(h); +} + +QString +TextHook::hookNameById(ulong hookId) const +{ + //return Ihf::getHookNameById(hookId); + // FIXME: supposed to be the engine name, unimplemented + Q_UNUSED(hookId) + return QString(); +} + + +*/ diff --git a/vnr/texthook/texthook.h b/vnr/texthook/texthook.h new file mode 100644 index 0000000..465040b --- /dev/null +++ b/vnr/texthook/texthook.h @@ -0,0 +1,101 @@ +#pragma once + +// texthook.h +// 10/14/2011 jichi + +#include "texthook_config.h" +#include "sakurakit/skglobal.h" +#include +#include +#include +#include +#include // for WId + +class TextHookPrivate; +/// Singleton class. Only one instance is allowed. +class TEXTHOOK_EXPORT TextHook : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(TextHook) + SK_EXTEND_CLASS(TextHook, QObject) + SK_DECLARE_PRIVATE(TextHookPrivate) + + // - Construction - +public: + explicit TextHook(QObject *parent = nullptr); + ~TextHook(); + +signals: + void dataReceived(QByteArray raw, QByteArray rendered, qint32 signature, QString source); + void processAttached(qint64 pid); + void processDetached(qint64 pid); + + // - Properties - +public: + /// Limited by ITH + int capacity() const; + + bool isEnabled() const; + void setEnabled(bool t); + + WId parentWinId() const; ///< Must be set to a valid window so that ::SetTimer works + void setParentWinId(WId hwnd); + + int interval() const; ///< Time to differentiate sentences + void setInterval(int msecs); + + int dataCapacity() const; ///< Maximum text length + void setDataCapacity(int value); + + bool removesRepeat() const; + void setRemovesRepeat(bool value); + + bool keepsSpace() const; + void setKeepsSpace(bool value); + + bool wideCharacter() const; + void setWideCharacter(bool value); + + QString defaultHookName() const; ///< The default one is "H-code" + void setDefaultHookName(const QString &name); + + bool isActive() const; + void start(); + void stop(); + void clear(); + + // - Injection - +public: + //bool attachOneProcess(ulong pid, bool checkActive = false); + bool attachProcess(ulong pid, bool checkActive = false); + bool detachProcess(ulong pid, bool checkActive = false); + bool hijackProcess(ulong pid); + //void detachAllProcesses(); + //QList attachedProcesses(bool checkActive = false) const; + //ulong anyAttachedProcess(bool checkActive = false) const; + //ulong currentProccess() const; + + bool containsProcess(ulong pid) const; + bool isEmpty() const; ///< Return true if at least one process is attached + + bool addHookCode(ulong pid, const QString &code, const QString &name = QString(), bool verbose = true); + static bool verifyHookCode(const QString &code); ///< Return if hcode is valid + //bool containsHook(ulong pid) const; + //bool containsHook(ulong pid, const QString &code) const; + //QString processHook(ulong pid) const; + //QString currentHook() const { return processHook(currentProccess()); } + bool removeHookCode(ulong pid); ///< Assume atmost one hcode per process + + // - Whitelist - +public: + bool isThreadWhitelistEnabled() const; + void setThreadWhitelistEnabled(bool t); + QList threadWhitelist() const; + void setThreadWhitelist(const QList &signatures); + void clearThreadWhitelist(); + // Note: len(v) must be smaller than 0x200 + void setKeptThreadName(const QString &v); + QString keptThreadName() const; +}; + +// EOF diff --git a/vnr/texthook/texthook.pri b/vnr/texthook/texthook.pri new file mode 100644 index 0000000..064aed9 --- /dev/null +++ b/vnr/texthook/texthook.pri @@ -0,0 +1,20 @@ +# texthook.pri +# 10/13/2011 jichi + +DEFINES += WITH_LIB_TEXTHOOK + +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD + +QT += core +LIBS += -ltexthook + +HEADERS += \ + $$PWD/texthook_config.h + +# texthook.h should not be in HEADERS, or it will be processed by moc +OTHER_FILES += \ + $$PWD/texthook.h \ + $$PWD/texthook.pro + +# EOF diff --git a/vnr/texthook/texthook.pro b/vnr/texthook/texthook.pro new file mode 100644 index 0000000..bc7f05c --- /dev/null +++ b/vnr/texthook/texthook.pro @@ -0,0 +1,61 @@ +# texthook.pro +# 10/13/2011 jichi +# Build ith texthook dll. + +CONFIG += noqtgui dll #eha # eha will catch all exceptions, but does not work on Windows XP +include(../../../config.pri) +include(host/host.pri) +include($$LIBDIR/winmutex/winmutex.pri) +include($$LIBDIR/wintimer/wintimer.pri) +include($$LIBDIR/windbg/windbg.pri) +#include($$LIBDIR/winmaker/winmaker.pri) + +# TODO: Get rid of dependence on ITHSYS and NT APIs +include($$PLUGINDIR/vnrhook/vnrhook.pri) +include($$PLUGINDIR/ithsys/ithsys.pri) +LIBS += -L$$WDK7_HOME/lib/wxp/i386 -lntdll + +DEFINES += ITH_HAS_CRT # Use native CRT + +# TODO: Get rid of dependence on msvc's swprintf +DEFINES += _CRT_NON_CONFORMING_SWPRINTFS + +## Libraries + +QT += core +QT -= gui + +## Sources + +TEMPLATE = lib +TARGET = texthook + +#CONFIG += staticlib +DEFINES += TEXTHOOK_BUILD_LIB + +HEADERS += \ + growl.h \ + ihf_p.h \ + ith_p.h \ + texthook_config.h \ + texthook.h \ + texthook_p.h \ + textthread_p.h \ + winapi_p.h + +SOURCES += \ + ihf_p.cc \ + ith_p.cc \ + texthook.cc \ + textthread_p.cc \ + winapi_p.cc + +#!wince*: LIBS += -lshell32 +#RC_FILE += texthook.rc + +OTHER_FILES += \ + texthook.pri \ + texthook_static.pri \ + texthook.rc + +# EOF diff --git a/vnr/texthook/texthook.rc b/vnr/texthook/texthook.rc new file mode 100644 index 0000000..9344b78 --- /dev/null +++ b/vnr/texthook/texthook.rc @@ -0,0 +1,38 @@ +/* + * texthook.rc + * 10/20/2011 jichi + */ +#if defined(UNDER_CE) +# include +#else +# include +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "Sakuradite\0" + VALUE "FileDescription", "Text Hook\0" + VALUE "FileVersion", "1.0.0.0\0" + VALUE "LegalCopyright", "Copyright (C) 2012.\0" + VALUE "OriginalFilename", "texthook.dll\0" + VALUE "ProductName", "texthook\0" + END + END + END + +/* End of Version info */ diff --git a/vnr/texthook/texthook_config.h b/vnr/texthook/texthook_config.h new file mode 100644 index 0000000..467e8f9 --- /dev/null +++ b/vnr/texthook/texthook_config.h @@ -0,0 +1,20 @@ +#pragma once + +// texthook_config.h +// 10/20/2011 jichi + +//#define TEXTHOOK_EXPORT + +#ifndef TEXTHOOK_EXPORT +# ifdef TEXTHOOK_STATIC_LIB +# define TEXTHOOK_EXPORT +# elif defined(TEXTHOOK_BUILD_LIB) +# define TEXTHOOK_EXPORT Q_DECL_EXPORT +# else +# define TEXTHOOK_EXPORT Q_DECL_IMPORT +# endif +#endif // TEXTHOOK_EXPORT + +#define TEXTHOOK_DEFAULT_NAME "H-code" + +// EOF diff --git a/vnr/texthook/texthook_p.h b/vnr/texthook/texthook_p.h new file mode 100644 index 0000000..44e34de --- /dev/null +++ b/vnr/texthook/texthook_p.h @@ -0,0 +1,39 @@ +#pragma once + +// texthook_p.h +// 10/14/2011 jichi +// Internal header. +// Defines TextHook private data. + +#include "texthook/texthook.h" +#include +#include + +// - Private - + +class TextHookPrivate +{ + SK_CLASS(TextHookPrivate) + SK_DECLARE_PUBLIC(TextHook) + + static Self *instance_; // global instance + + bool enabled; + QString source; + QSet pids; + QHash hooks; // ITH hook code, indexed by pid + + explicit TextHookPrivate(Q *q) + : q_(q), enabled(true), source(TEXTHOOK_DEFAULT_NAME) { instance_ = this; } + + ~TextHookPrivate() { instance_ = nullptr; } + +public: + static void sendData(const QByteArray &rawData, const QByteArray &renderedData, qint32 signature, const QString &name) + { + if (instance_ && instance_->q_->isEnabled()) + emit instance_->q_->dataReceived(rawData, renderedData, signature, name); + } +}; + +// EOF diff --git a/vnr/texthook/texthook_static.pri b/vnr/texthook/texthook_static.pri new file mode 100644 index 0000000..9a95504 --- /dev/null +++ b/vnr/texthook/texthook_static.pri @@ -0,0 +1,73 @@ +# texthook_static.pri +# 10/13/2011 jichi + +include(../../../config.pri) +include($$LIBDIR/wintimer/wintimer.pri) + +## Libraries + +#DEFINES += _ITH_DEBUG_MEMORY + +QT += core + +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD + +#WDK_HOME = c:/winddk +#LIBS += -L$$WDK_HOME/lib +# override folder must come before winddk/inc/api +#INCLUDEPATH += $$PWD/override/wdk/vc10 +#INCLUDEPATH += $$WDK_HOME/include/api +#INCLUDEPATH += $$WDK_HOME/include/crt +#INCLUDEPATH += $$WDK_HOME/include/ddk + +#ITH_HOME = c:/dev/ith +#INCLUDEPATH += $$ITH_HOME/include +#LIBS += -L$$ITH_HOME/lib +#LIBS += -lITH_DLL #-lITH_SYS + +#INCLUDEPATH += $$ITH_HOME/include +#LIBS += -L$$ITH_HOME/lib -lihf + +# Tell IHF not to override new operators, see: ith/mem.h +DEFINES += DEFAULT_MM + +#LIBS += -lmsvcrtd +#LIBS += -lmsvcrt +#QMAKE_LFLAGS += /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:msvcrtd.lib +DEFINES += _CRT_SECURE_NO_WARNINGS _CRT_NON_CONFORMING_SWPRINTFS + +## Sources + +#TEMPLATE = lib +#TARGET = texthook +#CONFIG += dll +#CONFIG += staticlib +#DEFINES += TEXTHOOK_BUILD_LIB +DEFINES += TEXTHOOK_STATIC_LIB + +HEADERS += \ + $$PWD/ihf_p.h \ + $$PWD/ith_p.h \ + $$PWD/texthook_config.h \ + $$PWD/texthook.h \ + $$PWD/texthook_p.h \ + $$PWD/textthread_p.h \ + $$PWD/winapi_p.h + +SOURCES += \ + $$PWD/ihf_p.cc \ + $$PWD/ith_p.cc \ + $$PWD/texthook.cc \ + $$PWD/textthread_p.cc \ + $$PWD/winapi_p.cc + +#!wince*: LIBS += -lshell32 +#RC_FILE += texthook.rc + +OTHER_FILES += \ + $$PWD/texthook.pri \ + $$PWD/texthook.pro \ + $$PWD/texthook.rc + +# EOF diff --git a/vnr/texthook/textthread_p.cc b/vnr/texthook/textthread_p.cc new file mode 100644 index 0000000..9c9454f --- /dev/null +++ b/vnr/texthook/textthread_p.cc @@ -0,0 +1,376 @@ +// textthread_p.cc +// 6/6/2012 jichi + +#include "texthook/textthread_p.h" +#include "texthook/texthook_p.h" +#include "winmutex/winmutex.h" +#include "wintimer/wintimer.h" +#include "host/textthread.h" +#include + +//#define DEBUG "textthread_p.cc" +#include "sakurakit/skdebug.h" + +enum { ITH_THREAD_NAME_CAPACITY = 0x200 }; // used internally by ITH + +#define REPEAT_RX_1 "(.{2,})\\1+$" // The pattern has at least 2 bytes, and repeats at least once + +/** Private class */ + +#define D_LOCK win_mutex_lock d_lock(D::globalMutex) // Synchronized scope for accessing private data + +class TextThreadDelegatePrivate +{ + SK_CLASS(TextThreadDelegatePrivate) + SK_DISABLE_COPY(TextThreadDelegatePrivate) + +public: + typedef win_mutex mutex_type; + + static mutex_type globalMutex; // Used only in public class. Because ITH is running in another single thread + static int globalCapacity; // maximum text size + static bool globalRemovesRepeat; + static bool globalKeepsSpace; + static bool globalWideCharacter; + + TextThread *t; + WinTimer flushTimer; // as QTimer does not work with windows remote thread, use native WM_TIMER instead + + ulong signature; // buffered + char sourceBuffer[ITH_THREAD_NAME_CAPACITY]; // buffered + QString source; // buffered + + int bufferSize; + int bufferCapacity; + char *buffer; + bool removesRepeat; + + QByteArray spaceBuffer; + int spaceCount; + + struct Repeat + { + QRegExp rx; // cached + char *buffer; // repeated string + int size; + int pos; // >= 0, current pos of repeating string + int offset; // offset of repeated string + + Repeat() : rx(REPEAT_RX_1), buffer(nullptr), size(0), pos(0), offset(-1) {} + ~Repeat() { if (buffer) delete[] buffer; } + + void clear() + { + size = pos = 0; + offset = -1; + } + + bool isRepeating(const char *data, int len) const + { + if (!size || !buffer) + return false; + switch (len) { + case 1: return pos < size && buffer[pos] == *data; + case 2: return pos < size + 1 && buffer[pos] == data[0] && buffer[pos +1] == data[1]; + default: + if (pos + len >= size) + return false; + for (int i = 0; i < len; i++) + if (buffer[pos + i] != data[i]) + return false; + return true; + } + } + } repeat; + + // - Construction - +public: + explicit TextThreadDelegatePrivate(TextThread *thread) : t(thread), + bufferSize(0), bufferCapacity(globalCapacity), buffer(new char[globalCapacity]), + spaceCount(0), + removesRepeat(false) + { + signature = signatureOf(t); + + //size_t size = + t->GetThreadString(sourceBuffer, ITH_THREAD_NAME_CAPACITY); + source = sourceBuffer; + } + + ~TextThreadDelegatePrivate() { delete[] buffer; } + + // - Properties - +public: + //QString text() const { return QString::fromLocal8Bit(buffer); } + //ulong context() const { return t->GetThreadParameter()->retn; } + //ulong subcontext() const { return t->GetThreadParameter()->spl; } + + //ulong processId() const { return t->PID(); } + + // - Actions - +public: + void flush() + { + if (flushTimer.isActive()) + flushTimer.stop(); + if (bufferSize) { + send(); + bufferSize = 0; + } + if (!spaceBuffer.isEmpty()) + spaceBuffer.clear(); + spaceCount = 0; + } + + void syncGlobal() + { + if (bufferCapacity < globalCapacity) { + delete[] buffer; + bufferCapacity = globalCapacity; + buffer = new char[bufferCapacity]; + if (repeat.buffer) { + delete[] repeat.buffer; + if (!removesRepeat) + repeat.buffer = nullptr; + else { + char *largerBuffer = new char[bufferCapacity]; + if (repeat.size) + qMemCopy(largerBuffer, repeat.buffer, repeat.size); + repeat.buffer = largerBuffer; + } + } + //bufferSize = repeatOffset = 0; // already reset in flush + //if (removesRepeat) + // repeat.reset(); + } + if (removesRepeat != globalRemovesRepeat) { + removesRepeat = globalRemovesRepeat; + if (removesRepeat) + repeat.clear(); + } + } + + void appendSpace() + { + flushTimer.start(); + spaceCount++; + if (spaceBuffer.isEmpty()) + spaceBuffer.append(buffer, bufferSize); + spaceBuffer.append(' '); + if (globalWideCharacter) + spaceBuffer.append('\0'); // L' ' = {'\x20', '\0'}; + } + + void append(const char *data, int len) + { + flushTimer.start(); + if (bufferSize < qMin(bufferCapacity, globalCapacity)) + switch (len) { + case 1: buffer[bufferSize++] = *data; break; + case 2: buffer[bufferSize++] = *data; + if (bufferSize < bufferCapacity) + buffer[bufferSize++] = data[1]; + break; + default: + { + int diff = qMin(len, bufferCapacity - bufferSize); + qMemCopy(buffer + bufferSize, data, diff); + bufferSize += diff; + } + } + if (!spaceBuffer.isEmpty()) + spaceBuffer.append(data, len); + } + + void appendRepeat(const char *data, int len) + { + if (bufferSize + len >= qMin(bufferCapacity, globalCapacity)) // overflow + return; + if (repeat.isRepeating(data, len)) { + repeat.pos += len; + if (repeat.pos >= repeat.size) + repeat.pos = 0; + return; + } + repeat.clear(); + + append(data, len); + + if (bufferSize >= 6) { // at least 2 characters + // Use fromLatin1 to prevent the data from being decoded + QString t = QString::fromLatin1(buffer, bufferSize); + repeat.offset = repeat.rx.indexIn(t); + if (repeat.offset >= 0) { + repeat.size = repeat.rx.cap(1).size(); + if (!repeat.buffer) + repeat.buffer = new char[bufferCapacity]; + qMemCopy(repeat.buffer, buffer + repeat.offset, repeat.size); + //bufferSize = repeat.offset repeat.size; + } + } + } + +private: + void send() + { + int size; + if (removesRepeat && repeat.offset >= 0 && repeat.size) + size = repeat.offset + repeat.size; + else + size = bufferSize; + if (!spaceBuffer.isEmpty() && spaceBuffer.size() != size) + spaceBuffer.truncate(size + spaceCount); + TextHookPrivate::sendData( + QByteArray(buffer, size), spaceBuffer, + signature, source); + } + + static qint32 signatureOf(TextThread *t) + { + qint32 ret = + (t->GetThreadParameter()->retn & 0xffff) | // context + (t->GetThreadParameter()->spl & 0xffff) << 16; // subcontext + return ret ? ret : t->Addr(); + } + + //static QString sourceOf(TextThread *t); + +public: + static ulong contextOf(TextThread *t) + { return t->GetThreadParameter()->retn; } + + static ulong subcontextOf(TextThread *t) + { return t->GetThreadParameter()->spl; } +}; + +TextThreadDelegatePrivate::mutex_type TextThreadDelegatePrivate::globalMutex; +int TextThreadDelegatePrivate::globalCapacity = 512; +bool TextThreadDelegatePrivate::globalRemovesRepeat = false; +bool TextThreadDelegatePrivate::globalKeepsSpace = false; +bool TextThreadDelegatePrivate::globalWideCharacter = false; + +//QString TextThreadDelegatePrivate::sourceOf(TextThread *t) +//{ +// Q_ASSERT(t); +// QString ret; +// enum { buf_size = 0x200 }; // 0x200 is used by ITH internally +// wchar_t buf[buf_size]; +// ulong len = t->GetThreadString(buf, buf_size); +// if (len) +// ret = QString::fromWCharArray(buf, len); +// return ret; +//} + +/** Public class */ + +// - Constructions - + +TextThreadDelegate::TextThreadDelegate(TextThread *t) + : d_(new D(t)) +{ + d_->flushTimer.setMethod(this, &Self::flush); + d_->flushTimer.setSingleShot(true); +} + +TextThreadDelegate::~TextThreadDelegate() +{ + if (d_->flushTimer.isActive()) + d_->flushTimer.stop(); + delete d_; +} + +bool TextThreadDelegate::delegateOf(const Self *that) const +{ + Q_ASSERT(t); + // Both have no context, and my subcontext is smaller + return that + && !D::contextOf(that->d_->t) && !D::contextOf(d_->t) + && D::subcontextOf(that->d_->t) >= D::subcontextOf(d_->t) + && ::strcmp(d_->sourceBuffer, that->d_->sourceBuffer) == 0 + && nameEquals("Malie"); +} + +// - Properties - + +//TextThread *TextThreadDelegate::t() const { return d_->t; } +int TextThreadDelegate::threadNumber() const +{ return d_->t->Number(); } + +qint32 TextThreadDelegate::signature() const +{ return d_->signature; } + +QString TextThreadDelegate::name() const +{ return d_->source; } + +bool TextThreadDelegate::nameEquals(const char *that) const +{ return !::strcmp(d_->sourceBuffer, that); } + +int TextThreadDelegate::capacity() { return D::globalCapacity; } +void TextThreadDelegate::setCapacity(int value) { D::globalCapacity = value; } + +bool TextThreadDelegate::removesRepeat() { return D::globalRemovesRepeat; } +void TextThreadDelegate::setRemovesRepeat(bool value) { D::globalRemovesRepeat = value; } + +bool TextThreadDelegate::wideCharacter() { return D::globalWideCharacter; } +void TextThreadDelegate::setWideCharacter(bool value) { D::globalWideCharacter = value; } + +bool TextThreadDelegate::keepsSpace() { return D::globalKeepsSpace; } +void TextThreadDelegate::setKeepsSpace(bool value) { D::globalKeepsSpace = value; } + +void TextThreadDelegate::setInterval(int msecs) +{ d_->flushTimer.setInterval(msecs); } + +void TextThreadDelegate::setParentWindow(WId winId) +{ d_->flushTimer.setParentWindow(winId); } + +// - Actions - + +void TextThreadDelegate::flush() +{ + D_LOCK; + d_->flush(); + d_->syncGlobal(); +} + +void TextThreadDelegate::touch() +{ + D_LOCK; + d_->flushTimer.start(); +} + +void TextThreadDelegate::append(const char *data, int len, bool space) +{ + D_LOCK; + if (space && D::globalKeepsSpace) + d_->appendSpace(); + if (data && len) { + if (d_->removesRepeat) + d_->appendRepeat(data, len); + else + d_->append(data, len); + } +} + +// EOF +/* +void TextThreadDelegate::append(const QByteArray &data) +{ + D::mutex_lock_type locker(D::mutex); + + d_->flushTimer.start(); + if (d_->buffer.size() <= D::capacity) + d_->buffer.append(data); +} +void TextThreadDelegatePrivate::send() +{ +#ifdef DEBUG + qDebug()<< source() + << t->Number() + << t->PID() + << QString::number(t->Addr(), 16) + << QString::number(t->GetThreadParameter()->retn, 16) + << QString::number(t->GetThreadParameter()->spl, 16) + << QTextCodec::codecForName("SHIFT-JIS")->makeDecoder()->toUnicode(buffer); +#endif // DEBUG +} +*/ diff --git a/vnr/texthook/textthread_p.h b/vnr/texthook/textthread_p.h new file mode 100644 index 0000000..6e02939 --- /dev/null +++ b/vnr/texthook/textthread_p.h @@ -0,0 +1,81 @@ +#pragma once + +// textthread_p.h +// 6/6/2012 jichi +// Internal header. +// Defines TextHook delegate class. + +#include "sakurakit/skglobal.h" +#include + +//QT_FORWARD_DECLARE_CLASS(QByteArray) + +class SharedRef +{ + SK_CLASS(SharedRef) + int count_; +public: + SharedRef(): count_(1) {} + int retainCount() const { return count_; } + void retain() { count_++; } + //void release() { count_--; } + static void release(Self *x) { if (--x->count_ <= 0) delete x; } +}; + +// FIXME: This class is not thread-safe! +class TextThread; +class TextThreadDelegatePrivate; +class TextThreadDelegate : public SharedRef +{ + SK_EXTEND_CLASS(TextThreadDelegate, SharedRef) + SK_DISABLE_COPY(TextThreadDelegate) + SK_DECLARE_PRIVATE(TextThreadDelegatePrivate) +public: + explicit TextThreadDelegate(TextThread *t); + ~TextThreadDelegate(); + + bool delegateOf(const Self *t) const; + + // - Properties - + + //TextThread *t() const; + int threadNumber() const; + qint32 signature() const; + QString name() const; + bool nameEquals(const char *that) const; // optimized + + // Maximum text size + static int capacity(); + static void setCapacity(int value); + + static bool wideCharacter(); + static void setWideCharacter(bool value); + + static bool removesRepeat(); + static void setRemovesRepeat(bool value); + + static bool keepsSpace(); + static void setKeepsSpace(bool value); + + //TextThread *t() const; + + //int interval() const; + void setInterval(int msecs); + + //WId parentWindow() const; + void setParentWindow(WId winId); + + // - Actions - + + //void append(const QByteArray &data); + /** Add data to the text thread + * @param data raw data + * @param len length of the data + * @param space Whether have LEADING space + */ + void append(const char *data, int len, bool space=false); + void flush(); + void touch(); // keep timer running +}; + +// EOF diff --git a/vnr/texthook/winapi_p.cc b/vnr/texthook/winapi_p.cc new file mode 100644 index 0000000..11ca6e2 --- /dev/null +++ b/vnr/texthook/winapi_p.cc @@ -0,0 +1,21 @@ +// apiwin_p.cc +// 10/6/2012 jichi +#include "texthook/winapi_p.h" +#include + +WINAPI_BEGIN_NAMESPACE + +bool IsProcessActiveWithId(DWORD dwProcessId) +{ + bool ret = false; + if (HANDLE hProc = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessId)) { + DWORD dwExitCode; + ret = ::GetExitCodeProcess(hProc, &dwExitCode) && (dwExitCode == STILL_ACTIVE); + ::CloseHandle(hProc); + } + return ret; +} + +WINAPI_END_NAMESPACE + +// EOF diff --git a/vnr/texthook/winapi_p.h b/vnr/texthook/winapi_p.h new file mode 100644 index 0000000..6e35a27 --- /dev/null +++ b/vnr/texthook/winapi_p.h @@ -0,0 +1,18 @@ +#pragma once +// winapi_p.h +// 10/5/2012 jichi +// Internal header. +// Wrapper of + +#ifndef WINAPI_BEGIN_NAMESPACE +# define WINAPI_BEGIN_NAMESPACE namespace winapi { +#endif +#ifndef WINAPI_END_NAMESPACE +# define WINAPI_END_NAMESPACE } // namespace winapi +#endif + +WINAPI_BEGIN_NAMESPACE +bool IsProcessActiveWithId(unsigned long dwProcessId); +WINAPI_END_NAMESPACE + +// EOF diff --git a/vnr/vnrhook/CMakeLists.txt b/vnr/vnrhook/CMakeLists.txt new file mode 100644 index 0000000..b1c1986 --- /dev/null +++ b/vnr/vnrhook/CMakeLists.txt @@ -0,0 +1,112 @@ +# # hook.pro +# # Exception handler to catch all exceptions +# CONFIG += dll noqt eh eha # noeh nosafeseh + +# DEFINES += ITH_HAS_CRT ITH_HAS_SEH +# DEFINES += MEMDBG_NO_STL NTINSPECT_NO_STL # disabled as not used + +# # jichi 11/13/2011: disable swprinf warning +# DEFINES += _CRT_NON_CONFORMING_SWPRINTFS + +# config.pri +# CONFIG(eha) { +# message(CONFIG eha) +# QMAKE_CXXFLAGS_STL_ON -= /EHsc +# QMAKE_CXXFLAGS_EXCEPTIONS_ON -= /EHsc +# QMAKE_CXXFLAGS_STL_ON += /EHa +# QMAKE_CXXFLAGS_EXCEPTIONS_ON += /EHa +# } + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +set(vnrhook_src + include/const.h + include/defs.h + include/types.h + src/except.h + src/main.cc + src/main.h + src/pipe.cc + src/engine/engine.cc + src/engine/engine.h + src/engine/hookdefs.h + src/engine/match.cc + src/engine/match.h + src/engine/pchooks.cc + src/engine/pchooks.h + src/engine/mono/funcinfo.h + src/engine/mono/types.h + src/engine/ppsspp/funcinfo.h + src/hijack/texthook.cc + src/hijack/texthook.h + src/tree/avl.h + src/util/growl.h + src/util/util.cc + src/util/util.h + ${PROJECT_SOURCE_DIR}/ccutil/ccmacro.h + ${PROJECT_SOURCE_DIR}/cpputil/cpplocale.h + ${PROJECT_SOURCE_DIR}/cpputil/cppmarshal.h + ${PROJECT_SOURCE_DIR}/cpputil/cppmath.h + ${PROJECT_SOURCE_DIR}/cpputil/cpppath.h + ${PROJECT_SOURCE_DIR}/cpputil/cppstring.h + ${PROJECT_SOURCE_DIR}/cpputil/cpptype.h + ${PROJECT_SOURCE_DIR}/cpputil/cppunicode.h + ${PROJECT_SOURCE_DIR}/disasm/disasm.cc + ${PROJECT_SOURCE_DIR}/hashutil/hashstr.h + ${PROJECT_SOURCE_DIR}/hashutil/hashutil.h + ${PROJECT_SOURCE_DIR}/memdbg/memdbg.h + ${PROJECT_SOURCE_DIR}/memdbg/memsearch.cc + ${PROJECT_SOURCE_DIR}/memdbg/memsearch.h + ${PROJECT_SOURCE_DIR}/ntinspect/ntinspect.cc + ${PROJECT_SOURCE_DIR}/ntinspect/ntinspect.h + ${PROJECT_SOURCE_DIR}/winkey/winkey.h + ${PROJECT_SOURCE_DIR}/winversion/winversion.cc + ${PROJECT_SOURCE_DIR}/winversion/winversion.h + ${PROJECT_SOURCE_DIR}/winseh/winseh.h + ${PROJECT_SOURCE_DIR}/winseh/winseh.cc + ${PROJECT_SOURCE_DIR}/winseh/winseh_safe.cc + ${PROJECT_SOURCE_DIR}/winseh/safeseh.asm + ${PROJECT_SOURCE_DIR}/mono/monoobject.h + ${PROJECT_SOURCE_DIR}/mono/monotype.h +) + +add_library(vnrhook SHARED ${vnrhook_src}) + +enable_language(ASM_MASM) + +set_source_files_properties( + ${PROJECT_SOURCE_DIR}/winseh/safeseh.asm + PROPERTIES + # CMAKE_ASM_MASM_FLAGS /safeseh # CMake bug 14711: http://www.cmake.org/Bug/view.php?id=14711 + COMPILE_FLAGS /safeseh +) + +set_target_properties(vnrhook PROPERTIES + LINK_FLAGS "/SUBSYSTEM:WINDOWS /MANIFEST:NO" +) + +target_compile_options(vnrhook PRIVATE + /EHa + $<$:> + $<$:> +) + +set(vnrhook_libs + ithsys + ${WDK_HOME}/lib/wxp/i386/ntdll.lib + Version.lib +) + +target_link_libraries(vnrhook ${vnrhook_libs}) + +target_compile_definitions(vnrhook + PRIVATE + ITH_HAS_CRT + ITH_HAS_SEH + _CRT_NON_CONFORMING_SWPRINTFS +) + +install(TARGETS vnrhook RUNTIME + DESTINATION . + CONFIGURATIONS Release +) diff --git a/vnr/vnrhook/TRASH/dllconfig.pri b/vnr/vnrhook/TRASH/dllconfig.pri new file mode 100644 index 0000000..601be70 --- /dev/null +++ b/vnr/vnrhook/TRASH/dllconfig.pri @@ -0,0 +1,35 @@ +# dllconfig.pri +# 8/9/2013 jichi +# For linking ITH injectable dlls. +# The dll is self-contained and Windows-independent. + +CONFIG += dll noqt #noeh nosafeseh +CONFIG -= embed_manifest_dll # dynamically load dlls +win32 { + CONFIG(eh): DEFINES += ITH_HAS_SEH # Do have exception handler in msvcrt.dll on Windows Vista and later + CONFIG(noeh): DEFINES -= ITH_HAS_SEH # Do not have exception handler in msvcrt.dll on Windows XP and before +} +include(../../../config.pri) +#win32 { +# CONFIG(noeh): include($$LIBDIR/winseh/winseh_safe.pri) +#} + +# jichi 11/24/2013: Disable manual heap +DEFINES -= ITH_HAS_HEAP + +# jichi 11/13/2011: disable swprinf warning +DEFINES += _CRT_NON_CONFORMING_SWPRINTFS + +## Libraries + +#LIBS += -lkernel32 -luser32 -lgdi32 +LIBS += -L$$WDK7_HOME/lib/wxp/i386 -lntdll +LIBS += $$WDK7_HOME/lib/crt/i386/msvcrt.lib # Override msvcrt10 +#LIBS += -L$$WDK7_HOME/lib/crt/i386 -lmsvcrt +#QMAKE_LFLAGS += $$WDK7_HOME/lib/crt/i386/msvcrt.lib # This will leave runtime flags in the dll + +#LIBS += -L$$WDK8_HOME/lib/winv6.3/um/x86 -lntdll + +HEADERS += $$PWD/dllconfig.h + +# EOF diff --git a/vnr/vnrhook/TRASH/hookxp.pro b/vnr/vnrhook/TRASH/hookxp.pro new file mode 100644 index 0000000..40896f5 --- /dev/null +++ b/vnr/vnrhook/TRASH/hookxp.pro @@ -0,0 +1,61 @@ +# hookxp.pro +# 8/9/2013 jichi +# Build vnrhookxp.dll for Windows XP + +CONFIG += noeh # msvcrt on Windows XP does not has exception handler +include(../dllconfig.pri) +include(../sys/sys.pri) +include($$LIBDIR/disasm/disasm.pri) +include($$LIBDIR/memdbg/memdbg.pri) +include($$LIBDIR/ntinspect/ntinspect.pri) +include($$LIBDIR/winkey/winkey.pri) +include($$LIBDIR/winseh/winseh_safe.pri) +include($$LIBDIR/winversion/winversion.pri) + +VPATH += ../hook +INCLUDEPATH += ../hook + +# 9/27/2013: disable ITH this game engine, only for debugging purpose +#DEFINES += ITH_DISABLE_ENGINE + +# jichi 9/22/2013: When ITH is on wine, mutex is needed to protect NtWriteFile +#DEFINES += ITH_WINE +#DEFINES += ITH_SYNC_PIPE + +DEFINES += MEMDBG_NO_STL NTINSPECT_NO_STL + +## Libraries + +LIBS += -lkernel32 -luser32 -lgdi32 #-lgdiplus + +## Sources + +TEMPLATE = lib +TARGET = vnrhookxp + +#CONFIG += staticlib + +HEADERS += \ + config.h \ + cli.h \ + hook.h \ + engine/engine.h \ + engine/hookdefs.h \ + engine/match.h \ + engine/pchooks.h \ + engine/util.h \ + tree/avl.h + +SOURCES += \ + main.cc \ + rpc/pipe.cc \ + hijack/texthook.cc \ + engine/engine.cc \ + engine/match.cc \ + engine/pchooks.cc \ + engine/util.cc + +#RC_FILE += vnrhook.rc +#OTHER_FILES += vnrhook.rc + +# EOF diff --git a/vnr/vnrhook/TRASH/memory.h b/vnr/vnrhook/TRASH/memory.h new file mode 100644 index 0000000..bedea9b --- /dev/null +++ b/vnr/vnrhook/TRASH/memory.h @@ -0,0 +1,46 @@ +#pragma once + +// ith/common/memory.h +// 8/23/2013 jichi +// Branch: ITH/mem.h, revision 66 + +#ifndef ITH_HAS_HEAP +# define ITH_MEMSET_HEAP(...) ::memset(__VA_ARGS__) +#else +# define ITH_MEMSET_HEAP(...) (void)0 + +// Defined in kernel32.lilb +extern "C" { +// PVOID RtlAllocateHeap( _In_ PVOID HeapHandle, _In_opt_ ULONG Flags, _In_ SIZE_T Size); +__declspec(dllimport) void * __stdcall RtlAllocateHeap(void *HeapHandle, unsigned long Flags, unsigned long Size); + +// BOOLEAN RtlFreeHeap( _In_ PVOID HeapHandle, _In_opt_ ULONG Flags, _In_ PVOID HeapBase); +__declspec(dllimport) int __stdcall RtlFreeHeap(void *HeapHandle, unsigned long Flags, void *HeapBase); +} // extern "C" + +//NTSYSAPI +//BOOL +//NTAPI +//RtlFreeHeap( +// _In_ HANDLE hHeap, +// _In_ DWORD dwFlags, +// _In_ LPVOID lpMem +//); + +extern void *hHeap; // defined in ith/sys.cc + +inline void * __cdecl operator new(size_t lSize) +{ + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa366597%28v=vs.85%29.aspx + // HEAP_ZERO_MEMORY flag is critical. All new objects are assumed with zero initialized. + enum { HEAP_ZERO_MEMORY = 0x00000008 }; + return RtlAllocateHeap(::hHeap, HEAP_ZERO_MEMORY, lSize); +} + +inline void __cdecl operator delete(void *pBlock) +{ RtlFreeHeap(::hHeap, 0, pBlock); } + +inline void __cdecl operator delete[](void *pBlock) +{ RtlFreeHeap(::hHeap, 0, pBlock); } + +#endif // ITH_HAS_HEAP diff --git a/vnr/vnrhook/TRASH/string.h b/vnr/vnrhook/TRASH/string.h new file mode 100644 index 0000000..a77196c --- /dev/null +++ b/vnr/vnrhook/TRASH/string.h @@ -0,0 +1,36 @@ +#pragma once + +// ith/common/string.h +// 8/9/2013 jichi +// Branch: ITH/string.h, rev 66 + +#ifdef ITH_HAS_CRT // ITH is linked with msvcrt dlls +# include +# include + +#else +# define _INC_SWPRINTF_INL_ +# define CRT_IMPORT __declspec(dllimport) + +#include // for wchar_t +extern "C" { +CRT_IMPORT int swprintf(wchar_t *src, const wchar_t *fmt, ...); +CRT_IMPORT int sprintf(char *src, const char *fmt, ...); +CRT_IMPORT int swscanf(const wchar_t *src, const wchar_t *fmt, ...); +CRT_IMPORT int sscanf(const char *src, const char *fmt, ...); +CRT_IMPORT int wprintf(const wchar_t *fmt, ...); +CRT_IMPORT int printf(const char *fmt, ...); +CRT_IMPORT int _wputs(const wchar_t *src); +CRT_IMPORT int puts(const char *src); +CRT_IMPORT int _stricmp(const char *x, const char *y); +CRT_IMPORT int _wcsicmp(const wchar_t *x, const wchar_t *y); +//CRT_IMPORT size_t strlen(const char *); +//CRT_IMPORT size_t wcslen(const wchar_t *); +//CRT_IMPORT char *strcpy(char *,const char *); +//CRT_IMPORT wchar_t *wcscpy(wchar_t *,const wchar_t *); +CRT_IMPORT void *memmove(void *dst, const void *src, size_t sz); +CRT_IMPORT const char *strchr(const char *src, int val); +CRT_IMPORT int strncmp(const char *x, const char *y, size_t sz); +} // extern "C" + +#endif // ITH_HAS_CRT diff --git a/vnr/vnrhook/TRASH/xp.txt b/vnr/vnrhook/TRASH/xp.txt new file mode 100644 index 0000000..150c61f --- /dev/null +++ b/vnr/vnrhook/TRASH/xp.txt @@ -0,0 +1,11 @@ +12/16/2013 + +Differences between xp.dll and non-xp.dll for vnrhook. + +non-xp: + CONFIG += eh + +xp: + CONFIG += noeh + CONFIG -= embed_manifest_dll # Pure dynamic determined. The manifest would break Windows XP support + include($$LIBDIR/winseh/winseh_safe.pri) diff --git a/vnr/vnrhook/include/const.h b/vnr/vnrhook/include/const.h new file mode 100644 index 0000000..8a766dc --- /dev/null +++ b/vnr/vnrhook/include/const.h @@ -0,0 +1,249 @@ +#pragma once + +// vnrhook/const.h +// 8/23/2013 jichi +// Branch: ITH/common.h, rev 128 + +// jichi 9/9/2013: Another importnat function is lstrcatA, which is already handled by +// Debonosu hooks. Wait until it is really needed by certain games. +// The order of the functions is used in several place. +// I need to recompile all of the dlls to modify the order. + +// jichi 10/14/2014 +#define HOOK_GDI_FUNCTION_LIST \ + GetTextExtentPoint32A \ + , GetTextExtentExPointA \ + , GetTabbedTextExtentA \ + , GetCharacterPlacementA \ + , GetGlyphIndicesA \ + , GetGlyphOutlineA \ + , ExtTextOutA \ + , TextOutA \ + , TabbedTextOutA \ + , GetCharABCWidthsA \ + , GetCharABCWidthsFloatA \ + , GetCharWidth32A \ + , GetCharWidthFloatA \ + , GetTextExtentPoint32W \ + , GetTextExtentExPointW \ + , GetTabbedTextExtentW \ + , GetCharacterPlacementW \ + , GetGlyphIndicesW \ + , GetGlyphOutlineW \ + , ExtTextOutW \ + , TextOutW \ + , TabbedTextOutW \ + , GetCharABCWidthsW \ + , GetCharABCWidthsFloatW \ + , GetCharWidth32W \ + , GetCharWidthFloatW \ + , DrawTextA \ + , DrawTextExA \ + , DrawTextW \ + , DrawTextExW + //, CharNextA + //, CharPrevA +//enum { HOOK_FUN_COUNT = 30 }; // total number of GDI hooks +// jichi 1/16/2015: Though called max hook, it means max number of text threads +enum { MAX_HOOK = 64 }; // must be larger than HOOK_FUN_COUNT + +//enum { HOOK_SECTION_SIZE = 0x2000 }; // default ITH value +// jichi 1/16/2015: Change to a very large number to prevent crash +//enum { MAX_HOOK = 0x100 }; // must be larger than HookFunCount +enum { HOOK_SECTION_SIZE = MAX_HOOK * 0x100 }; // default ITH value is 0x2000 for 32 hook (0x100 per hook) + +// jichi 375/2014: Add offset of pusha/pushad +// http://faydoc.tripod.com/cpu/pushad.htm +// http://agth.wikia.com/wiki/Cheat_Engine_AGTH_Tutorial +// +// Warning: The offset in ITH has -4 offset comparing to pusha and AGTH +enum pusha_off { + pusha_eax_off = -0x4 + , pusha_ecx_off = -0x8 + , pusha_edx_off = -0xc + , pusha_ebx_off = -0x10 + , pusha_esp_off = -0x14 + , pusha_ebp_off = -0x18 + , pusha_esi_off = -0x1c + , pusha_edi_off = -0x20 + , pusha_off = -0x24 // pushad offset +}; + +enum HostCommandType { + HOST_COMMAND = -1 // null type + , HOST_COMMAND_NEW_HOOK = 0 + , HOST_COMMAND_REMOVE_HOOK = 1 + , HOST_COMMAND_MODIFY_HOOK = 2 + , HOST_COMMAND_HIJACK_PROCESS = 3 + , HOST_COMMAND_DETACH = 4 +}; + +enum HostNotificationType { + HOST_NOTIFICATION = -1 // null type + , HOST_NOTIFICATION_TEXT = 0 + , HOST_NOTIFICATION_NEWHOOK = 1 +}; + +// jichi 9/8/2013: The meaning are guessed +// Values must be within DWORD +// Unused values are as follows: +// - 0x100 +enum HookParamType : unsigned long { + USING_STRING = 0x1 // type(data) is char* or wchar_t* and has length + , USING_UTF8 = USING_STRING // jichi 10/21/2014: temporarily handled the same way as USING_STRING + , USING_UNICODE = 0x2 // type(data) is wchar_t or wchar_t* + , BIG_ENDIAN = 0x4 // type(data) is char + , DATA_INDIRECT = 0x8 + , USING_SPLIT = 0x10 // aware of split time? + , SPLIT_INDIRECT = 0x20 + , MODULE_OFFSET = 0x40 // do hash module, and the address is relative to module + , FUNCTION_OFFSET = 0x80 // do hash function, and the address is relative to funccion + , PRINT_DWORD = 0x100 // jichi 12/7/2014: Removed + , NO_ASCII = 0x100 // jichi 1l/22/2015: Skip ascii characters + , STRING_LAST_CHAR = 0x200 + , NO_CONTEXT = 0x400 + , HOOK_EMPTY = 0x800 + , FIXING_SPLIT = 0x1000 + //, HOOK_AUXILIARY = 0x2000 // jichi 12/13/2013: None of known hooks are auxiliary + , RELATIVE_SPLIT = 0x2000 // jichi 10/24/2014: relative split return address + , HOOK_ENGINE = 0x4000 + , HOOK_ADDITIONAL = 0x8000 +}; + +// 6/1/2014: Fixed split value for hok parameter +// Fuse all threads, and prevent floating +enum { FIXED_SPLIT_VALUE = 0x10001 }; + +// jichi 12/18/2013: +// These dlls are used to guess the range for non-NO_CONTEXT hooks. +// +// Disabling uxtheme.dll would crash certain system: http://tieba.baidu.com/p/2764436254 +#define IHF_FILTER_DLL_LIST \ + /* ITH original filters */ \ + L"gdiplus.dll" /* Graphics functions like TextOutA */ \ + , L"lpk.dll" /* Language package scripts and fonts */ \ + , L"msctf.dll" /* Text service */ \ + , L"psapi.dll" /* Processes */ \ + , L"usp10.dll" /* UNICODE rendering */ \ + , L"user32.dll" /* Non-graphics functions like lstrlenA */ \ + , L"uxtheme.dll" /* Theme */ \ + \ + /* Windows DLLs */ \ + , L"advapi32.dll" /* Advanced services */ \ + , L"apphelp.dll" /* Appliation help */ \ + , L"audioses.dll" /* Audios */ \ + , L"avrt.dll" /* Audio video runtime */ \ + , L"cfgmgr32.dll" /* Configuration manager */ \ + , L"clbcatq.dll" /* COM query service */ \ + , L"comctl32.dll" /* Common control library */ \ + , L"comdlg32.dll" /* Common dialogs */ \ + , L"crypt32.dll" /* Security cryption */ \ + , L"cryptbase.dll"/* Security cryption */ \ + , L"cryptsp.dll" /* Security cryption */ \ + , L"d3d8thk.dll" /* Direct3D 8 */ \ + , L"d3d9.dll" /* Direct3D 9 */ \ + , L"dbghelp.dll" /* Debug help */ \ + , L"dciman32.dll" /* Display cotrol */ \ + , L"devobj.dll" /* Device object */ \ + , L"ddraw.dll" /* Direct draw */ \ + , L"dinput.dll" /* Diret input */ \ + , L"dsound.dll" /* Direct sound */ \ + , L"DShowRdpFilter.dll" /* Direct show */ \ + , L"dwmapi.dll" /* Windows manager */ \ + , L"gdi32.dll" /* GDI32 */ \ + , L"hid.dll" /* HID user library */ \ + , L"iertutil.dll" /* IE runtime */ \ + , L"imagehlp.dll" /* Image help */ \ + , L"imm32.dll" /* Input method */ \ + , L"ksuser.dll" /* Kernel service */ \ + , L"ole32.dll" /* COM OLE */ \ + , L"oleacc.dll" /* OLE access */ \ + , L"oleaut32.dll" /* COM OLE */ \ + , L"kernel.dll" /* Kernel functions */ \ + , L"kernelbase.dll" /* Kernel functions */ \ + , L"midimap.dll" /* MIDI */ \ + , L"mmdevapi.dll" /* Audio device */ \ + , L"mpr.dll" /* Winnet */ \ + , L"msacm32.dll" /* MS ACM */ \ + , L"msacm32.drv" /* MS ACM */ \ + , L"msasn1.dll" /* Encoding/decoding */ \ + , L"msimg32.dll" /* Image */ \ + , L"msvfw32.dll" /* Media play */ \ + , L"netapi32.dll" /* Network service */ \ + , L"normaliz.dll" /* Normalize */ \ + , L"nsi.dll" /* NSI */ \ + , L"ntdll.dll" /* NT functions */ \ + , L"ntmarta.dll" /* NT MARTA */ \ + , L"nvd3dum.dll" /* Direct 3D */ \ + , L"powerprof.dll"/* Power profile */ \ + , L"profapi.dll" /* Profile API */ \ + , L"propsys.dll" /* System properties */ \ + , L"quartz.dll" /* OpenGL */ \ + , L"rpcrt4.dll" /* RPC runtime */ \ + , L"rpcrtremote.dll" /* RPC runtime */ \ + , L"rsabase.dll" /* RSA cryption */ \ + , L"rsaenh.dll" /* RSA cryption */ \ + , L"schannel.dll" /* Security channel */ \ + , L"sechost.dll" /* Service host */ \ + , L"setupapi.dll" /* Setup service */ \ + , L"shell32.dll" /* Windows shell */ \ + , L"shlwapi.dll" /* Light-weighted shell */ \ + , L"slc.dll" /* SLC */ \ + , L"srvcli.dll" /* Service client */ \ + , L"version.dll" /* Windows version */ \ + , L"wdmaud.drv" /* Wave output */ \ + , L"wldap32.dll" /* Wireless */ \ + , L"wininet.dll" /* Internet access */ \ + , L"winmm.dll" /* Windows sound */ \ + , L"winsta.dll" /* Connection system */ \ + , L"wtsapi32.dll" /* Windows terminal server */ \ + , L"wintrust.dll" /* Windows trust */ \ + , L"wsock32.dll" /* Windows sock */ \ + , L"ws2_32.dll" /* Terminal server */ \ + , L"wkscli.dll" /* ACIS */ \ + \ + /* MSVCRT */ \ + , L"msvcrt.dll" /* VC rutime */ \ + , L"msvcr80.dll" /* VC rutime 8 */ \ + , L"msvcp80.dll" /* VC rutime 8 */ \ + , L"msvcr90.dll" /* VC rutime 9 */ \ + , L"msvcp90.dll" /* VC rutime 9 */ \ + , L"msvcr100.dll" /* VC rutime 10 */ \ + , L"msvcp100.dll" /* VC rutime 10 */ \ + , L"msvcr110.dll" /* VC rutime 11 */ \ + , L"msvcp110.dll" /* VC rutime 11 */ \ + \ + /* VNR */ \ + , L"vnrhook.dll" \ + , L"vnrhookxp.dll" \ + \ + /* Sogou IME */ \ + , L"sogoupy.ime" \ + , L"PicFace.dll" \ + , L"AddressSearch.dll" \ + \ + /* QQ IME */ \ + , L"QQPINYIN.IME" \ + \ + /* AlphaROM */ \ + , L"kDays.dll" \ + \ + /* 360Safe */ \ + , L"safemon.dll" \ + \ + /* Locale changers */ \ + , L"AlLayer.dll" /* AppLocale */ \ + , L"LocaleEmulator.dll" /* Locale Emulator */ \ + , L"LSH.dll" /* LocaleSwitch */ \ + , L"ntleah.dll" /* NTLEA */ + + // Google Japanese IME + //, L"GoogleIMEJaTIP32.dll" + +enum { + //IHF_FILTER_COUNT = 7 + IHF_FILTER_COUNT = 7 + 72 + 9 + 4 + 3 + 1 + 1 + 1 + 4 // count of total dlls to filter + , IHF_FILTER_CAPACITY = IHF_FILTER_COUNT + 1 // one more than the dll count +}; + +// EOF diff --git a/vnr/vnrhook/include/defs.h b/vnr/vnrhook/include/defs.h new file mode 100644 index 0000000..0e65f91 --- /dev/null +++ b/vnr/vnrhook/include/defs.h @@ -0,0 +1,52 @@ +#pragma once + +// vnrhook/defs.h +// 8/23/2013 jichi + +// DLL files + +//#define ITH_SERVER_DLL L"vnrsrv.dll" +//#define ITH_CLIENT_DLL L"vnrcli.dll" +//#define ITH_CLIENT_XP_DLL L"vnrclixp.dll" +////#define ITH_CLIENT_UX_DLL L"vnrcliux.dll" +//#define ITH_ENGINE_DLL L"vnreng.dll" +//#define ITH_ENGINE_XP_DLL L"vnrengxp.dll" +//#define ITH_ENGINE_UX_DLL L"vnrengux.dll" + +#define ITH_DLL L"vnrhook.dll" +#define ITH_DLL_XP L"vnrhookxp.dll" + +// Pipes + +#define ITH_TEXT_PIPE L"\\??\\pipe\\VNR_TEXT" +#define ITH_COMMAND_PIPE L"\\??\\pipe\\VNR_COMMAND" + +// Sections + +#define ITH_SECTION_ L"VNR_SECTION_" // _%d + +// Mutex + +// jichi 7/12/2015: +// ITH IO name prefix, needed by Windows 10 for NT event and mutex APIs +// Otherwise, NT functions will return status = STATUS_OBJECT_PATH_SYNTAX_BAD +//#define ITH_PATH_ L"\\BaseNamedObjects\\" +#define ITH_PATH_ L"" + +#define ITH_PROCESS_MUTEX_ ITH_PATH_ L"VNR_PROCESS_" // ITH_%d +#define ITH_HOOKMAN_MUTEX_ ITH_PATH_ L"VNR_HOOKMAN_" // ITH_HOOKMAN_%d +#define ITH_DETACH_MUTEX_ ITH_PATH_ L"VNR_DETACH_" // ITH_DETACH_%d + +#define ITH_GRANTPIPE_MUTEX ITH_PATH_ L"VNR_GRANT_PIPE" // ITH_GRANT_PIPE + +#define ITH_CLIENT_MUTEX ITH_PATH_ L"VNR_CLIENT" // ITH_DLL_RUNNING +#define ITH_SERVER_MUTEX ITH_PATH_ L"VNR_SERVER" // ITH_RUNNING +#define ITH_SERVER_HOOK_MUTEX ITH_PATH_ L"VNR_SERVER_HOOK" // original + +// Events + +#define ITH_REMOVEHOOK_EVENT ITH_PATH_ L"VNR_REMOVE_HOOK" // ITH_REMOVE_HOOK +#define ITH_MODIFYHOOK_EVENT ITH_PATH_ L"VNR_MODIFY_HOOK" // ITH_MODIFY_HOOK +#define ITH_PIPEEXISTS_EVENT ITH_PATH_ L"VNR_PIPE_EXISTS" // ITH_PIPE_EXIST + +// EOF diff --git a/vnr/vnrhook/include/types.h b/vnr/vnrhook/include/types.h new file mode 100644 index 0000000..442b766 --- /dev/null +++ b/vnr/vnrhook/include/types.h @@ -0,0 +1,91 @@ +#pragma once + +// vnrhook/types.h +// 8/23/2013 jichi +// Branch: ITH/common.h, rev 128 + +#include // needed for windef types + + /** jichi 3/7/2014: Add guessed comment + * + * DWORD addr absolute or relative address + * DWORD split esp offset of the split character + * + * http://faydoc.tripod.com/cpu/pushad.htm + * http://agth.wikia.com/wiki/Cheat_Engine_AGTH_Tutorial + * The order is the same as pushd + * EAX, ECX, EDX, EBX, ESP (original value), EBP, ESI, and EDI (if the current operand-size attribute is 32) and AX, CX, DX, BX, SP + * Negative values of 'data_offset' and 'sub_offset' refer to registers:-4 for EAX, -8 for ECX, -C for EDX, -10 for EBX, -14 for ESP, -18 for EBP, -1C for ESI, -20 for EDI + */ +struct HookParam { + // jichi 8/24/2013: For special hooks. + typedef void (*text_fun_t)(DWORD esp, HookParam *hp, BYTE index, DWORD *data, DWORD *split, DWORD *len); + + // jichi 10/24/2014: Add filter function. Return the if skip the text + typedef bool (*filter_fun_t)(LPVOID str, DWORD *len, HookParam *hp, BYTE index); + + // jichi 10/24/2014: Add generic hook function, return false if stop execution. + typedef bool (*hook_fun_t)(DWORD esp, HookParam *hp); + + DWORD address; // absolute or relative address + DWORD offset, // offset of the data in the memory + index, // ? + split, // esp offset of the split character = pusha offset - 4 + split_index; // ? + DWORD module, // hash of the module + function; + text_fun_t text_fun; + filter_fun_t filter_fun; + hook_fun_t hook_fun; + DWORD type; // flags + WORD length_offset; // index of the string length + BYTE hook_len, // ? + recover_len; // ? + + // 2/2/2015: jichi number of times - 1 to run the hook + BYTE extra_text_count; + BYTE _unused; // jichi 2/2/2015: add a BYTE type to make to total sizeof(HookParam) even. + + // 7/20/2014: jichi additional parameters for PSP games + DWORD user_flags, + user_value; +}; + +// jichi 6/1/2014: Structure of the esp for extern functions +struct HookStack +{ + // pushad + DWORD edi, // -0x24 + esi, // -0x20 + ebp, // -0x1c + esp, // -0x18 + ebx, // -0x14 + edx, // -0x10 + ecx, // -0xc + eax; // -0x8 + // pushfd + DWORD eflags; // -0x4 + DWORD retaddr; // 0 + DWORD args[1]; // 0x4 +}; + +struct SendParam { + DWORD type; + HookParam hp; +}; + +struct Hook { // size: 0x80 + HookParam hp; + LPSTR hook_name; + int name_length; + BYTE recover[0x68 - sizeof(HookParam)]; + BYTE original[0x10]; + + DWORD Address() const { return hp.address; } + DWORD Type() const { return hp.type; } + WORD Length() const { return hp.hook_len; } + LPSTR Name() const { return hook_name; } + int NameLength() const { return name_length; } +}; + +// EOF diff --git a/vnr/vnrhook/src/engine/engine.cc b/vnr/vnrhook/src/engine/engine.cc new file mode 100644 index 0000000..d9fb7d4 --- /dev/null +++ b/vnr/vnrhook/src/engine/engine.cc @@ -0,0 +1,21158 @@ +// engine.cc +// 8/9/2013 jichi +// Branch: ITH_Engine/engine.cpp, revision 133 + +#ifdef _MSC_VER +# pragma warning (disable:4100) // C4100: unreference formal parameter +# pragma warning (disable:4819) +#endif // _MSC_VER + +#include "src/engine/engine.h" +#include "src/engine/match.h" +#include "src/engine/hookdefs.h" +#include "src/util/util.h" +#include "src/main.h" +#include "src/engine/mono/funcinfo.h" +#include "src/engine/ppsspp/funcinfo.h" +#include "src/except.h" +#include "ithsys/ithsys.h" +#include "memdbg/memsearch.h" +#include "ntinspect/ntinspect.h" +#include "disasm/disasm.h" +#include "winversion/winversion.h" +#include "hashutil/hashstr.h" +#include "cpputil/cppcstring.h" +#include "ccutil/ccmacro.h" +#include "mono/monoobject.h" +//#include +#include +#include + +#define hashstr hashutil::djb2 + +// jichi 7/6/2014: read esp_base +#define retof(esp_base) *(DWORD *)(esp_base) // return address +#define regof(name, esp_base) *(DWORD *)((esp_base) + pusha_##name##_off - 4) +#define argof(count, esp_base) *(DWORD *)((esp_base) + 4 * (count)) // starts from 1 instead of 0 + +//#define ConsoleOutput(...) (void)0 // jichi 8/18/2013: I don't need ConsoleOutput + +//#define DEBUG "engine.h" + +enum { VNR_TEXT_CAPACITY = 1500 }; // estimated max number of bytes allowed in VNR, slightly larger than VNR's text limit (1000) + +#ifdef DEBUG +# include "src/util/growl.h" +//# include "uniquemap.h" +//# include "uniquemap.cc" +namespace { // unnamed debug functions +// jichi 12/17/2013: Copied from int TextHook::GetLength(DWORD base, DWORD in) +int GetHookDataLength(const HookParam &hp, DWORD base, DWORD in) +{ + if (CC_UNLIKELY(!base)) + return 0; + int len; + switch (hp.length_offset) { + default: // jichi 12/26/2013: I should not put this default branch to the end + len = *((int *)base + hp.length_offset); + if (len >= 0) { + if (hp.type & USING_UNICODE) + len <<= 1; + break; + } + else if (len != -1) + break; + //len == -1 then continue to case 0. + case 0: + if (hp.type & USING_UNICODE) + len = wcslen((LPCWSTR)in) << 1; + else + len = strlen((LPCSTR)in); + break; + case 1: + if (hp.type & USING_UNICODE) + len = 2; + else { + if (hp.type & BIG_ENDIAN) + in >>= 8; + len = LeadByteTable[in&0xff]; //Slightly faster than IsDBCSLeadByte + } + break; + } + // jichi 12/25/2013: This function originally return -1 if failed + //return len; + return max(0, len); +} +} // unnamed +#endif // DEBUG + +namespace { // unnamed helpers + +int PPSSPP_VERSION[4] = { 0, 9, 8, 0 }; // 0.9.8 by default + +enum : DWORD { + PPSSPP_MEMORY_SEARCH_STEP_98 = 0x01000000 + , PPSSPP_MEMORY_SEARCH_STEP_99 = 0x00050000 + //, step = 0x1000 // step must be at least 0x1000 (offset in SearchPattern) + //, step = 0x00010000 // crash otoboku PSP on 0.9.9 since 5pb is wrongly inserted +}; + +enum : BYTE { XX = MemDbg::WidecardByte }; // 0x11 +#define XX2 XX,XX // WORD +#define XX4 XX2,XX2 // DWORD +#define XX8 XX4,XX4 // QWORD + +// jichi 8/18/2013: Original maximum relative address in ITH +//enum { MAX_REL_ADDR = 0x200000 }; + +// jichi 10/1/2013: Increase relative address limit. Certain game engine like Artemis has larger code region +enum : DWORD { MAX_REL_ADDR = 0x00300000 }; + +static union { + char text_buffer[0x1000]; + wchar_t wc_buffer[0x800]; + + struct { // CodeSection + DWORD base; + DWORD size; + } code_section[0x200]; +}; + +char text_buffer_prev[0x1000]; +DWORD buffer_index, + buffer_length; + +BOOL SafeFillRange(LPCWSTR dll, DWORD *lower, DWORD *upper) +{ + BOOL r = FALSE; + ITH_WITH_SEH(r = FillRange(dll, lower, upper)); + return r; +} + +// jichi 3/11/2014: The original FindEntryAligned function could raise exceptions without admin priv +DWORD SafeFindEntryAligned(DWORD start, DWORD back_range) +{ + DWORD r = 0; + ITH_WITH_SEH(r = Util::FindEntryAligned(start, back_range)); + return r; +} + +ULONG SafeFindEnclosingAlignedFunction(DWORD addr, DWORD range) +{ + ULONG r = 0; + ITH_WITH_SEH(r = MemDbg::findEnclosingAlignedFunction(addr, range)); // this function might raise if failed + return r; +} + +ULONG SafeFindBytes(LPCVOID pattern, DWORD patternSize, DWORD lowerBound, DWORD upperBound) +{ + ULONG r = 0; + ITH_WITH_SEH(r = MemDbg::findBytes(pattern, patternSize, lowerBound, upperBound)); + return r; +} + +ULONG SafeMatchBytes(LPCVOID pattern, DWORD patternSize, DWORD lowerBound, DWORD upperBound, BYTE wildcard = XX) +{ + ULONG r = 0; + ITH_WITH_SEH(r = MemDbg::matchBytes(pattern, patternSize, lowerBound, upperBound, wildcard)); + return r; +} + +// jichi 7/17/2014: Search mapped memory for emulators +ULONG _SafeMatchBytesInMappedMemory(LPCVOID pattern, DWORD patternSize, BYTE wildcard, + ULONG start, ULONG stop, ULONG step) +{ + for (ULONG i = start; i < stop; i += step) // + patternSize to avoid overlap + if (ULONG r = SafeMatchBytes(pattern, patternSize, i, i + step + patternSize + 1, wildcard)) + return r; + return 0; +} + +inline ULONG SafeMatchBytesInGCMemory(LPCVOID pattern, DWORD patternSize) +{ + enum : ULONG { + start = MemDbg::MappedMemoryStartAddress // 0x01000000 + , stop = MemDbg::MemoryStopAddress // 0x7ffeffff + , step = start + }; + return _SafeMatchBytesInMappedMemory(pattern, patternSize, XX, start, stop, step); +} + +inline ULONG SafeMatchBytesInPSPMemory(LPCVOID pattern, DWORD patternSize, DWORD start = MemDbg::MappedMemoryStartAddress, DWORD stop = MemDbg::MemoryStopAddress) +{ + ULONG step = PPSSPP_VERSION[1] == 9 && PPSSPP_VERSION[2] == 8 ? PPSSPP_MEMORY_SEARCH_STEP_98 : PPSSPP_MEMORY_SEARCH_STEP_99; + return _SafeMatchBytesInMappedMemory(pattern, patternSize, XX, start, stop, step); +} + +inline ULONG SafeMatchBytesInPS2Memory(LPCVOID pattern, DWORD patternSize) +{ + // PCSX2 memory range + // ds: begin from 0x20000000 + // cs: begin from 0x30000000 + enum : ULONG { + //start = MemDbg::MappedMemoryStartAddress // 0x01000000 + start = 0x30000000 // larger than PSP to skip the garbage memory + , stop = 0x40000000 // larger than PSP as PS2 has larger memory + , step = 0x00010000 // smaller than PPS + //, step = 0x00050000 // the same as PPS + //, step = 0x1000 // step must be at least 0x1000 (offset in SearchPattern) + }; + return _SafeMatchBytesInMappedMemory(pattern, patternSize, XX, start, stop, step); +} + +// 7/29/2014 jichi: I should move these functions to different files +// String utilities +// Return the address of the first non-zero address +LPCSTR reverse_search_begin(const char *s, int maxsize = VNR_TEXT_CAPACITY) +{ + if (*s) + for (int i = 0; i < maxsize; i++, s--) + if (!*s) + return s + 1; + return nullptr; +} + +bool all_ascii(const char *s, int maxsize = VNR_TEXT_CAPACITY) +{ + if (s) + for (int i = 0; i < maxsize && *s; i++, s++) + if ((BYTE)*s > 127) // unsigned char + return false; + return true; +} + +bool all_ascii(const wchar_t *s, int maxsize = VNR_TEXT_CAPACITY) +{ + if (s) + for (int i = 0; i < maxsize && *s; i++, s++) + if (*s > 127) // unsigned char + return false; + return true; +} + +// String filters + +void CharReplacer(char *str, size_t *size, char fr, char to) +{ + size_t len = *size; + for (size_t i = 0; i < len; i++) + if (str[i] == fr) + str[i] = to; +} + +void WideCharReplacer(wchar_t *str, size_t *size, wchar_t fr, wchar_t to) +{ + size_t len = *size / 2; + for (size_t i = 0; i < len; i++) + if (str[i] == fr) + str[i] = to; +} + +void CharFilter(char *str, size_t *size, char ch) +{ + size_t len = *size, + curlen; + for (char *cur = (char *)::memchr(str, ch, len); + (cur && --len && (curlen = len - (cur - str))); + cur = (char *)::memchr(cur, ch, curlen)) + ::memmove(cur, cur + 1, curlen); + *size = len; +} + +void WideCharFilter(wchar_t *str, size_t *size, wchar_t ch) +{ + size_t len = *size / 2, + curlen; + for (wchar_t *cur = cpp_wcsnchr(str, ch, len); + (cur && --len && (curlen = len - (cur - str))); + cur = cpp_wcsnchr(cur, ch, curlen)) + ::memmove(cur, cur + 1, 2 * curlen); + *size = len * 2; +} + +void CharsFilter(char *str, size_t *size, const char *chars) +{ + size_t len = *size, + curlen; + for (char *cur = cpp_strnpbrk(str, chars, len); + (cur && --len && (curlen = len - (cur - str))); + cur = cpp_strnpbrk(cur, chars, curlen)) + ::memmove(cur, cur + 1, curlen); + *size = len; +} + +void WideCharsFilter(wchar_t *str, size_t *size, const wchar_t *chars) +{ + size_t len = *size / 2, + curlen; + for (wchar_t *cur = cpp_wcsnpbrk(str, chars, len); + (cur && --len && (curlen = len - (cur - str))); + cur = cpp_wcsnpbrk(cur, chars, curlen)) + ::memmove(cur, cur + 1, 2 * curlen); + *size = len * 2; +} + +void StringFilter(char *str, size_t *size, const char *remove, size_t removelen) +{ + size_t len = *size, + curlen; + for (char *cur = cpp_strnstr(str, remove, len); + (cur && (len -= removelen) && (curlen = len - (cur - str))); + cur = cpp_strnstr(cur, remove, curlen)) + ::memmove(cur, cur + removelen, curlen); + *size = len; +} + +void WideStringFilter(wchar_t *str, size_t *size, const wchar_t *remove, size_t removelen) +{ + size_t len = *size / 2, + curlen; + for (wchar_t *cur = cpp_wcsnstr(str, remove, len); + (cur && (len -= removelen) && (curlen = len - (cur - str))); + cur = cpp_wcsnstr(cur, remove, curlen)) + ::memmove(cur, cur + removelen, 2 * curlen); + *size = len * 2; +} + +void StringFilterBetween(char *str, size_t *size, const char *fr, size_t frlen, const char *to, size_t tolen) +{ + size_t len = *size, + curlen; + for (char *cur = cpp_strnstr(str, fr, len); + cur; + cur = cpp_strnstr(cur, fr, curlen)) { + curlen = (len - frlen) - (cur - str); + auto end = cpp_strnstr(cur + frlen, to, curlen); + if (!end) + break; + curlen = len - (end - str) - tolen; + ::memmove(cur, end + tolen, curlen); + len -= tolen + (end - cur); + } + *size = len; +} + +void WideStringFilterBetween(wchar_t *str, size_t *size, const wchar_t *fr, size_t frlen, const wchar_t *to, size_t tolen) +{ + size_t len = *size / 2, + curlen; + for (wchar_t *cur = cpp_wcsnstr(str, fr, len); + cur; + cur = cpp_wcsnstr(cur, fr, curlen)) { + curlen = (len - frlen) - (cur - str); + auto end = cpp_wcsnstr(cur + frlen, to, curlen); + if (!end) + break; + curlen = len - (end - str) - tolen; + ::memmove(cur, end + tolen, 2 * curlen); + len -= tolen + (end - cur); + } + *size = len * 2; +} + +void StringCharReplacer(char *str, size_t *size, const char *src, size_t srclen, char ch) +{ + size_t len = *size, + curlen; + for (char *cur = cpp_strnstr(str, src, len); + cur && len; + cur = cpp_strnstr(cur, src, curlen)) { + *cur++ = ch; + len -= srclen - 1; + curlen = len - (cur - str); + if (curlen == 0) + break; + ::memmove(cur, cur + srclen - 1, curlen); + } + *size = len; +} + +void WideStringCharReplacer(wchar_t *str, size_t *size, const wchar_t *src, size_t srclen, wchar_t ch) +{ + size_t len = *size / 2, + curlen; + for (wchar_t *cur = cpp_wcsnstr(str, src, len); + cur && len; + cur = cpp_wcsnstr(cur, src, curlen)) { + *cur++ = ch; + len -= srclen - 1; + curlen = len - (cur - str); + if (curlen == 0) + break; + ::memmove(cur, cur + srclen, 2 * curlen); + } + *size = len * 2; +} + +// NOTE: I assume srclen >= dstlen +void StringReplacer(char *str, size_t *size, const char *src, size_t srclen, const char *dst, size_t dstlen) +{ + size_t len = *size, + curlen; + for (char *cur = cpp_strnstr(str, src, len); + cur && len; + cur = cpp_strnstr(cur, src, curlen)) { + ::memcpy(cur, dst, dstlen); + cur += dstlen; + len -= srclen - dstlen; + curlen = len - (cur - str); + if (curlen == 0) + break; + if (srclen > dstlen) + ::memmove(cur, cur + srclen - dstlen, curlen); + } + *size = len; +} + +void WideStringReplacer(wchar_t *str, size_t *size, const wchar_t *src, size_t srclen, const wchar_t *dst, size_t dstlen) +{ + size_t len = *size / 2, + curlen; + for (wchar_t *cur = cpp_wcsnstr(str, src, len); + cur && len; + cur = cpp_wcsnstr(cur, src, curlen)) { + ::memcpy(cur, dst, 2 * dstlen); + cur += dstlen; + len -= srclen - dstlen; + curlen = len - (cur - str); + if (curlen == 0) + break; + if (srclen > dstlen) + ::memmove(cur, cur + srclen - dstlen, 2 * curlen); + } + *size = len * 2; +} + +bool NewLineCharFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + CharFilter(reinterpret_cast(data), reinterpret_cast(size), + '\n'); + return true; +} +bool NewLineWideCharFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + CharFilter(reinterpret_cast(data), reinterpret_cast(size), + L'\n'); + return true; +} +bool NewLineStringFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + StringFilter(reinterpret_cast(data), reinterpret_cast(size), + "\\n", 2); + return true; +} +bool NewLineWideStringFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + WideStringFilter(reinterpret_cast(data), reinterpret_cast(size), + L"\\n", 2); + return true; +} +bool NewLineCharToSpaceFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + CharReplacer(reinterpret_cast(data), reinterpret_cast(size), '\n', ' '); + return true; +} +bool NewLineWideCharToSpaceFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + WideCharReplacer(reinterpret_cast(data), reinterpret_cast(size), L'\n', L' '); + return true; +} + +// Remove every characters <= 0x1f (i.e. before space ' ') except 0xa and 0xd. +bool IllegalCharsFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + CharsFilter(reinterpret_cast(data), reinterpret_cast(size), + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10\x11\x12\x12\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"); + return true; +} +bool IllegalWideCharsFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + WideCharsFilter(reinterpret_cast(data), reinterpret_cast(size), + L"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10\x11\x12\x12\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"); + return true; +} + +} // unnamed namespace + +namespace Engine { + +/******************************************************************************************** +KiriKiri hook: + Usually there are xp3 files in the game folder but also exceptions. + Find TVP(KIRIKIRI) in the version description is a much more precise way. + + KiriKiri1 correspond to AGTH KiriKiri hook, but this doesn't always work well. + Find call to GetGlyphOutlineW and go to function header. EAX will point to a + structure contains character (at 0x14, [EAX+0x14]) we want. To split names into + different threads AGTH uses [EAX], seems that this value stands for font size. + Since KiriKiri is compiled by BCC and BCC fastcall uses EAX to pass the first + parameter. Here we choose EAX is reasonable. + KiriKiri2 is a redundant hook to catch text when 1 doesn't work. When this happens, + usually there is a single GetTextExtentPoint32W contains irregular repetitions which + is out of the scope of KS or KF. This time we find a point and split them into clean + text threads. First find call to GetTextExtentPoint32W and step out of this function. + Usually there is a small loop. It is this small loop messed up the text. We can find + one ADD EBX,2 in this loop. It's clear that EBX is a string pointer goes through the + string. After the loop EBX will point to the end of the string. So EBX-2 is the last + char and we insert hook here to extract it. +********************************************************************************************/ +#if 0 // jichi 11/12/2013: not used +static void SpecialHookKiriKiri(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD p1 = *(DWORD *)(esp_base - 0x14), + p2 = *(DWORD *)(esp_base - 0x18); + if ((p1>>16) == (p2>>16)) { + if (DWORD p3 = *(DWORD *)p1) { + p3 += 8; + for (p2 = p3 + 2; *(WORD *)p2; p2 += 2); + *len = p2 - p3; + *data = p3; + p1 = *(DWORD *)(esp_base - 0x20); + p1 = *(DWORD *)(p1 + 0x74); + *split = p1 | *(DWORD *)(esp_base + 0x48); + } else + *len = 0; + } else + *len=0; +} +#endif // 0 + +bool FindKiriKiriHook(DWORD fun, DWORD size, DWORD pt, DWORD flag) // jichi 10/20/2014: change return value to bool +{ + enum : DWORD { + // jichi 10/20/2014: mov ebp,esp, sub esp,* + kirikiri1_sig = 0xec8b55, + + // jichi 10/20/2014: + // 00e01542 53 push ebx + // 00e01543 56 push esi + // 00e01544 57 push edi + kirikiri2_sig = 0x575653 + }; + enum : DWORD { StartAddress = 0x1000 }; + enum : DWORD { StartRange = 0x6000, StopRange = 0x8000 }; // jichi 10/20/2014: ITH original pattern range + + // jichi 10/20/2014: The KiriKiri patterns exist in multiple places of the game. + //enum : DWORD { StartRange = 0x8000, StopRange = 0x9000 }; // jichi 10/20/2014: change to a different range + + //WCHAR str[0x40]; + DWORD sig = flag ? kirikiri2_sig : kirikiri1_sig; + DWORD t = 0; + for (DWORD i = StartAddress; i < size - 4; i++) + if (*(WORD *)(pt + i) == 0x15ff) { // jichi 10/20/2014: call dword ptr ds + DWORD addr = *(DWORD *)(pt + i + 2); + + // jichi 10/20/2014: There are multiple function calls. The flag+1 one is selected. + // i.e. KiriKiri1: The first call to GetGlyphOutlineW is selected + // KiriKiri2: The second call to GetTextExtentPoint32W is selected + if (addr >= pt && addr <= pt + size - 4 + && *(DWORD *)addr == fun) + t++; + if (t == flag + 1) // We find call to GetGlyphOutlineW or GetTextExtentPoint32W. + //swprintf(str, L"CALL addr:0x%.8X",i+pt); + //ConsoleOutput(str); + for (DWORD j = i; j > i - StartAddress; j--) + if (((*(DWORD *)(pt + j)) & 0xffffff) == sig) { + if (flag) { // We find the function entry. flag indicate 2 hooks. + t = 0; // KiriKiri2, we need to find call to this function. + for (DWORD k = j + StartRange; k < j + StopRange; k++) // Empirical range. + if (*(BYTE *)(pt + k) == 0xe8) { + if (k + 5 + *(DWORD *)(pt + k + 1) == j) + t++; + if (t == 2) { + //for (k+=pt+0x14; *(WORD*)(k)!=0xC483;k++); + //swprintf(str, L"Hook addr: 0x%.8X",pt+k); + //ConsoleOutput(str); + HookParam hp = {}; + hp.address = pt + k + 0x14; + hp.offset = -0x14; + hp.index = -0x2; + hp.split = -0xc; + hp.length_offset = 1; + hp.type = USING_UNICODE|NO_CONTEXT|USING_SPLIT|DATA_INDIRECT; + ConsoleOutput("vnreng: INSERT KiriKiri2"); + NewHook(hp, "KiriKiri2"); + return true; + } + } + } else { + //swprintf(str, L"Hook addr: 0x%.8X",pt+j); + //ConsoleOutput(str); + HookParam hp = {}; + hp.address = (DWORD)pt + j; + hp.offset = -0x8; + hp.index = 0x14; + hp.split = -0x8; + hp.length_offset = 1; + hp.type = USING_UNICODE|DATA_INDIRECT|USING_SPLIT|SPLIT_INDIRECT; + ConsoleOutput("vnreng: INSERT KiriKiri1"); + NewHook(hp, "KiriKiri1"); + return true; + } + return false; + } + //ConsoleOutput("vnreng:KiriKiri: FAILED to find function entry"); + } + if (flag) + ConsoleOutput("vnreng:KiriKiri2: failed"); + else + ConsoleOutput("vnreng:KiriKiri1: failed"); + return false; +} + +bool InsertKiriKiriHook() // 9/20/2014 jichi: change return type to bool +{ + bool k1 = FindKiriKiriHook((DWORD)GetGlyphOutlineW, module_limit_ - module_base_, module_base_, 0), // KiriKiri1 + k2 = FindKiriKiriHook((DWORD)GetTextExtentPoint32W, module_limit_ - module_base_, module_base_, 1); // KiriKiri2 + //RegisterEngineType(ENGINE_KIRIKIRI); + if (k1 && k2) { + ConsoleOutput("vnreng:KiriKiri1: disable GDI hooks"); + DisableGDIHooks(); + } + return k1 || k2; +} + +/** 10/20/2014 jichi: KAGParser + * Sample game: [141128] Venus Blood -HYPNO- ヴィーナスブラッ�・ヒュプノ 体験版 + * + * drawText and drawGlyph seem to be the right function to look at. + * However, the latest source code does not match VenusBlood. + * + * Debug method: + * Pre-compute: hexstr 視界のきかな�utf16, got: 96894c756e304d304b306a304430 + * Use ollydbg to insert hardware break point before the scene is entered. + * It found several places either in game or KAGParser, and the last one is as follows. + * It tries to find "[" (0x5b) in the memory. + * + * 1. It cannot find character name. + * 2. It will extract [r]. + * + * 6e562270 75 0a jnz short kagparse.6e56227c + * 6e562272 c705 00000000 00>mov dword ptr ds:[0],0x0 + * 6e56227c ffb424 24010000 push dword ptr ss:[esp+0x124] + * 6e562283 ff9424 24010000 call dword ptr ss:[esp+0x124] + * 6e56228a 8b8c24 20010000 mov ecx,dword ptr ss:[esp+0x120] + * 6e562291 890d 14ed576e mov dword ptr ds:[0x6e57ed14],ecx + * 6e562297 68 3090576e push kagparse.6e579030 ; unicode "[r]" + * 6e56229c 8d46 74 lea eax,dword ptr ds:[esi+0x74] + * 6e56229f 50 push eax + * 6e5622a0 ffd1 call ecx + * 6e5622a2 8b4e 50 mov ecx,dword ptr ds:[esi+0x50] + * 6e5622a5 8b46 54 mov eax,dword ptr ds:[esi+0x54] + * 6e5622a8 66:833c48 5b cmp word ptr ds:[eax+ecx*2],0x5b ; jichi: hook here + * 6e5622ad 75 06 jnz short kagparse.6e5622b5 + * 6e5622af 8d41 01 lea eax,dword ptr ds:[ecx+0x1] + * 6e5622b2 8946 50 mov dword ptr ds:[esi+0x50],eax + * 6e5622b5 ff46 50 inc dword ptr ds:[esi+0x50] + * 6e5622b8 ^e9 aebcffff jmp kagparse.6e55df6b + * 6e5622bd 8d8c24 88030000 lea ecx,dword ptr ss:[esp+0x388] + * 6e5622c4 e8 b707ffff call kagparse.6e552a80 + * 6e5622c9 84c0 test al,al + * 6e5622cb 75 0f jnz short kagparse.6e5622dc + * 6e5622cd 8d8424 88030000 lea eax,dword ptr ss:[esp+0x388] + * 6e5622d4 50 push eax + * 6e5622d5 8bce mov ecx,esi + * 6e5622d7 e8 149bffff call kagparse.6e55bdf0 + * 6e5622dc 8d8c24 80030000 lea ecx,dword ptr ss:[esp+0x380] + * 6e5622e3 e8 9807ffff call kagparse.6e552a80 + * 6e5622e8 84c0 test al,al + * 6e5622ea 75 0f jnz short kagparse.6e5622fb + * 6e5622ec 8d8424 80030000 lea eax,dword ptr ss:[esp+0x380] + * 6e5622f3 50 push eax + * 6e5622f4 8bce mov ecx,esi + * 6e5622f6 e8 35a0ffff call kagparse.6e55c330 + * 6e5622fb 8d8c24 c0030000 lea ecx,dword ptr ss:[esp+0x3c0] + * 6e562302 c68424 c0040000 >mov byte ptr ss:[esp+0x4c0],0x3c + * 6e56230a e8 81edfeff call kagparse.6e551090 + * 6e56230f 8d8c24 80030000 lea ecx,dword ptr ss:[esp+0x380] + * 6e562316 c68424 c0040000 >mov byte ptr ss:[esp+0x4c0],0x3b + * 6e56231e e8 8deefeff call kagparse.6e5511b0 + * 6e562323 8d8c24 88030000 lea ecx,dword ptr ss:[esp+0x388] + * 6e56232a e9 d7000000 jmp kagparse.6e562406 + * 6e56232f 66:837c24 20 00 cmp word ptr ss:[esp+0x20],0x0 + * 6e562335 75 10 jnz short kagparse.6e562347 + * 6e562337 ff46 4c inc dword ptr ds:[esi+0x4c] + * 6e56233a c746 50 00000000 mov dword ptr ds:[esi+0x50],0x0 + * 6e562341 c646 5c 00 mov byte ptr ds:[esi+0x5c],0x0 + * + * Runtime regisers: + * EAX 09C1A626 text address + * ECX 00000000 0 or other offset + * EDX 025F1368 this value seems does not change. it is always pointed to 0 + * EBX 0000300C + * ESP 0029EB7C + * EBP 0029F044 + * ESI 04EE4150 + * EDI 0029F020 + * + * とな�KAGParserEx.dll + * 10013948 68 14830210 push _3.10028314 ; UNICODE "[r]" + * 1001394d 83c2 7c add edx,0x7c + * 10013950 52 push edx + * 10013951 ffd0 call eax + * 10013953 8b75 08 mov esi,dword ptr ss:[ebp+0x8] + * 10013956 eb 02 jmp short _3.1001395a + * 10013958 8bf2 mov esi,edx + * 1001395a 8b46 58 mov eax,dword ptr ds:[esi+0x58] + * 1001395d 8b4e 5c mov ecx,dword ptr ds:[esi+0x5c] + * 10013960 66:833c41 5b cmp word ptr ds:[ecx+eax*2],0x5b ; jichi: hook here + * 10013965 75 06 jnz short _3.1001396d + * 10013967 83c0 01 add eax,0x1 + * 1001396a 8946 58 mov dword ptr ds:[esi+0x58],eax + * 1001396d 8346 58 01 add dword ptr ds:[esi+0x58],0x1 + * 10013971 807e 7a 00 cmp byte ptr ds:[esi+0x7a],0x0 + * 10013975 ^0f85 b5a7ffff jnz _3.1000e130 + * 1001397b 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 1001397e 83b8 90000000 ff cmp dword ptr ds:[eax+0x90],-0x1 + * 10013985 0f84 68040000 je _3.10013df3 + * 1001398b 8bd8 mov ebx,eax + * 1001398d ^e9 a1a7ffff jmp _3.1000e133 + * 10013992 8d7c24 78 lea edi,dword ptr ss:[esp+0x78] + * 10013996 8d7424 54 lea esi,dword ptr ss:[esp+0x54] + * 1001399a e8 e16fffff call _3.1000a980 + */ + +#if 0 // not used, as KiriKiriZ is sufficient, and most KiriKiriZ games use KAGParserEx instead of KAGParser. +namespace { // unnamed + +bool KAGParserFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + WideStringFilter(reinterpret_cast(data), reinterpret_cast(size), L"[r]", 3); + return true; +} + +void SpecialHookKAGParser(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + // 6e5622a8 66:833c48 5b cmp word ptr ds:[eax+ecx*2],0x5b + DWORD eax = regof(eax, esp_base), + ecx = regof(ecx, esp_base); + if (eax && !ecx) { // skip string when ecx is not zero + *data = eax; + *len = ::wcslen((LPCWSTR)eax) * 2; // 2 == sizeof(wchar_t) + *split = FIXED_SPLIT_VALUE; // merge all threads + } +} + +void SpecialHookKAGParserEx(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + // 10013960 66:833c41 5b cmp word ptr ds:[ecx+eax*2],0x5b + DWORD eax = regof(eax, esp_base), + ecx = regof(ecx, esp_base); + if (ecx && !eax) { // skip string when ecx is not zero + *data = ecx; + *len = ::wcslen((LPCWSTR)ecx) * 2; // 2 == sizeof(wchar_t) + *split = FIXED_SPLIT_VALUE; // merge all threads + } +} +} // unnamed namespace +bool InsertKAGParserHook() +{ + ULONG startAddress, stopAddress; + if (!NtInspect::getModuleMemoryRange(L"KAGParser.dll", &startAddress, &stopAddress)) { + ConsoleOutput("vnreng:KAGParser: failed to get memory range"); + return false; + } + const wchar_t *patternString = L"[r]"; + const size_t patternStringSize = ::wcslen(patternString) * 2; + ULONG addr = MemDbg::findBytes(patternString, patternStringSize, startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:KAGParser: [r] global string not found"); + return false; + } + // Find where it is used as function parameter + addr = MemDbg::findPushAddress(addr, startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:KAGParser: push address not found"); + return false; + } + + const BYTE ins[] = { + 0x66,0x83,0x3c,0x48, 0x5b // 6e5622a8 66:833c48 5b cmp word ptr ds:[eax+ecx*2],0x5b ; jichi: hook here + }; + enum { range = 0x20 }; // 0x6e5622a8 - 0x6e562297 = 17 + addr = MemDbg::findBytes(ins, sizeof(ins), addr, addr + range); + if (!addr) { + ConsoleOutput("vnreng:KAGParser: instruction pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.text_fun = SpecialHookKAGParser; + hp.filter_fun = KAGParserFilter; + hp.type = USING_UNICODE|FIXING_SPLIT|NO_CONTEXT; // Fix the split value to merge all threads + ConsoleOutput("vnreng: INSERT KAGParser"); + NewHook(hp, "KAGParser"); + return true; +} +bool InsertKAGParserExHook() +{ + ULONG startAddress, stopAddress; + if (!NtInspect::getModuleMemoryRange(L"KAGParserEx.dll", &startAddress, &stopAddress)) { + ConsoleOutput("vnreng:KAGParserEx: failed to get memory range"); + return false; + } + const wchar_t *patternString = L"[r]"; + const size_t patternStringSize = ::wcslen(patternString) * 2; + ULONG addr = MemDbg::findBytes(patternString, patternStringSize, startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:KAGParserEx: [r] global string not found"); + return false; + } + // Find where it is used as function parameter + addr = MemDbg::findPushAddress(addr, startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:KAGParserEx: push address not found"); + return false; + } + + const BYTE ins[] = { + 0x66,0x83,0x3c,0x41, 0x5b // 10013960 66:833c41 5b cmp word ptr ds:[ecx+eax*2],0x5b ; jichi: hook here + }; + enum { range = 0x20 }; // 0x10013960 - 0x10013948 = 24 + addr = MemDbg::findBytes(ins, sizeof(ins), addr, addr + range); + if (!addr) { + ConsoleOutput("vnreng:KAGParserEx: instruction pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.text_fun = SpecialHookKAGParserEx; + hp.filter_fun = KAGParserFilter; + hp.type = USING_UNICODE|FIXING_SPLIT|NO_CONTEXT; // Fix the split value to merge all threads + ConsoleOutput("vnreng: INSERT KAGParserEx"); + NewHook(hp, "KAGParserEx"); + return true; +} +#endif // 0 + +/** 10/24/2014 jichi: New KiriKiri hook + * Sample game: [141128] Venus Blood -HYPNO- ヴィーナスブラッ�・ヒュプノ 体験版 + * + * This engine will hook to the caller of caller of the first GetGlyphOutlineW (totally three). + * The logic is quite similar to KiriKiri1 except it backtrack twice to get the function call. + * + * 1/31/2015: If the game no longer invoke GDI functions by default, one way to find the hook + * is to click the フォン�in the menu to force triggering GetGlyphOutlineW function. + * + * KiriKiriZ: + * https://github.com/krkrz/krkrz + * http://krkrz.github.io + * + * KiriKiri API: http://devdoc.kikyou.info/tvp/docs/kr2doc/contents/f_Layer.html + * + * See: krkrz/src/core/visual/LayerIntf.cpp + * API: http://devdoc.kikyou.info/tvp/docs/kr2doc/contents/f_Layer_drawText.html + * + * Debug method: + * Backtrack from GetGlyphOutlineW, and find the first function that is invoked more + * times than (cached) GetGlyphOutlineW. + * + * - Find function calls to GetGlyphOutlineW (totally three) + * + * - Find the caller of the first GetGlyphOutlineW + * Using MemDbg::findCallerAddressAfterInt3() + * + * - Find the caller of the above caller + * Since the function address is dynamic, the function is found using KiriKiriZHook + * + * 00377c44 8b01 mov eax,dword ptr ds:[ecx] + * 00377c46 ff75 10 push dword ptr ss:[ebp+0x10] + * 00377c49 ff75 0c push dword ptr ss:[ebp+0xc] + * 00377c4c 53 push ebx + * 00377c4d ff50 1c call dword ptr ds:[eax+0x1c] ; jichi: called here + * 00377c50 8bf0 mov esi,eax + * 00377c52 8975 e4 mov dword ptr ss:[ebp-0x1c],esi + * 00377c55 ff46 04 inc dword ptr ds:[esi+0x4] + * 00377c58 c745 fc 04000000 mov dword ptr ss:[ebp-0x4],0x4 + * + * Then, the UTF8 two-byte character is at [ecx]+0x14 + * 0017E950 16 00 00 00 00 02 00 00 00 00 00 00 98 D2 76 02 + * 0017E960 E0 8E 90 D9 42 7D 00 00 00 02 00 00 01 00 00 00 + * up: text here + * 0017E970 01 00 01 FF 00 00 00 00 00 00 00 00 C8 + * + * 1/30/2015: + * The hooked function in Venus Blood -HYPNO- is as follows. + * Since サノバウィッ� (150226), KiriKiriZ no longer invokes GetGlyphOutlineW. + * Try to extract instruction patterns from the following function instead. + * + * 011a7a3c cc int3 + * 011a7a3d cc int3 + * 011a7a3e cc int3 + * 011a7a3f cc int3 + * 011a7a40 55 push ebp + * 011a7a41 8bec mov ebp,esp + * 011a7a43 6a ff push -0x1 + * 011a7a45 68 dbaa3101 push .0131aadb + * 011a7a4a 64:a1 00000000 mov eax,dword ptr fs:[0] + * 011a7a50 50 push eax + * 011a7a51 83ec 14 sub esp,0x14 + * 011a7a54 53 push ebx + * 011a7a55 56 push esi + * 011a7a56 57 push edi + * 011a7a57 a1 00593d01 mov eax,dword ptr ds:[0x13d5900] + * 011a7a5c 33c5 xor eax,ebp + * 011a7a5e 50 push eax + * 011a7a5f 8d45 f4 lea eax,dword ptr ss:[ebp-0xc] + * 011a7a62 64:a3 00000000 mov dword ptr fs:[0],eax + * 011a7a68 8965 f0 mov dword ptr ss:[ebp-0x10],esp + * 011a7a6b 8bd9 mov ebx,ecx + * 011a7a6d 803d 00113e01 00 cmp byte ptr ds:[0x13e1100],0x0 + * 011a7a74 75 17 jnz short .011a7a8d + * 011a7a76 c745 e8 1cb83d01 mov dword ptr ss:[ebp-0x18],.013db81c + * 011a7a7d 8d45 e8 lea eax,dword ptr ss:[ebp-0x18] + * 011a7a80 50 push eax + * 011a7a81 e8 4ae2f0ff call .010b5cd0 + * 011a7a86 c605 00113e01 01 mov byte ptr ds:[0x13e1100],0x1 + * 011a7a8d 33c9 xor ecx,ecx + * 011a7a8f 384b 21 cmp byte ptr ds:[ebx+0x21],cl + * 011a7a92 0f95c1 setne cl + * 011a7a95 33c0 xor eax,eax + * 011a7a97 3843 20 cmp byte ptr ds:[ebx+0x20],al + * 011a7a9a 0f95c0 setne al + * 011a7a9d 33c8 xor ecx,eax + * 011a7a9f 334b 10 xor ecx,dword ptr ds:[ebx+0x10] + * 011a7aa2 0fb743 14 movzx eax,word ptr ds:[ebx+0x14] + * 011a7aa6 33c8 xor ecx,eax + * 011a7aa8 8b7b 1c mov edi,dword ptr ds:[ebx+0x1c] + * 011a7aab 33f9 xor edi,ecx + * 011a7aad 337b 18 xor edi,dword ptr ds:[ebx+0x18] + * 011a7ab0 897d e4 mov dword ptr ss:[ebp-0x1c],edi + * 011a7ab3 57 push edi + * 011a7ab4 53 push ebx + * 011a7ab5 e8 06330000 call .011aadc0 + * 011a7aba 8bf0 mov esi,eax + * 011a7abc 85f6 test esi,esi + * 011a7abe 74 26 je short .011a7ae6 + * 011a7ac0 56 push esi + * 011a7ac1 e8 ba330000 call .011aae80 + * 011a7ac6 8d46 2c lea eax,dword ptr ds:[esi+0x2c] + * 011a7ac9 85c0 test eax,eax + * 011a7acb 74 19 je short .011a7ae6 + * 011a7acd 8b08 mov ecx,dword ptr ds:[eax] + * 011a7acf ff41 04 inc dword ptr ds:[ecx+0x4] + * 011a7ad2 8b00 mov eax,dword ptr ds:[eax] + * 011a7ad4 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 011a7ad7 64:890d 00000000 mov dword ptr fs:[0],ecx + * 011a7ade 59 pop ecx + * 011a7adf 5f pop edi + * 011a7ae0 5e pop esi + * 011a7ae1 5b pop ebx + * 011a7ae2 8be5 mov esp,ebp + * 011a7ae4 5d pop ebp + * 011a7ae5 c3 retn + * 011a7ae6 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + * 011a7ae9 85c9 test ecx,ecx + * 011a7aeb 0f84 47010000 je .011a7c38 + * 011a7af1 0fb743 14 movzx eax,word ptr ds:[ebx+0x14] + * 011a7af5 50 push eax + * 011a7af6 e8 b5090300 call .011d84b0 + * 011a7afb 8bf0 mov esi,eax + * 011a7afd 8975 ec mov dword ptr ss:[ebp-0x14],esi + * 011a7b00 85f6 test esi,esi + * 011a7b02 0f84 30010000 je .011a7c38 + * 011a7b08 6a 34 push 0x34 + * 011a7b0a e8 29621300 call .012ddd38 + * 011a7b0f 83c4 04 add esp,0x4 + * 011a7b12 8bf8 mov edi,eax + * 011a7b14 897d e0 mov dword ptr ss:[ebp-0x20],edi + * 011a7b17 c745 fc 00000000 mov dword ptr ss:[ebp-0x4],0x0 + * 011a7b1e 85ff test edi,edi + * 011a7b20 74 1d je short .011a7b3f + * 011a7b22 c747 2c 41000000 mov dword ptr ds:[edi+0x2c],0x41 + * 011a7b29 c647 32 00 mov byte ptr ds:[edi+0x32],0x0 + * 011a7b2d c747 04 01000000 mov dword ptr ds:[edi+0x4],0x1 + * 011a7b34 c707 00000000 mov dword ptr ds:[edi],0x0 + * 011a7b3a 8945 e8 mov dword ptr ss:[ebp-0x18],eax + * 011a7b3d eb 05 jmp short .011a7b44 + * 011a7b3f 33ff xor edi,edi + * 011a7b41 897d e8 mov dword ptr ss:[ebp-0x18],edi + * 011a7b44 c745 fc ffffffff mov dword ptr ss:[ebp-0x4],-0x1 + * 011a7b4b 0fb746 04 movzx eax,word ptr ds:[esi+0x4] + * 011a7b4f 8947 1c mov dword ptr ds:[edi+0x1c],eax + * 011a7b52 0fb746 06 movzx eax,word ptr ds:[esi+0x6] + * 011a7b56 8947 20 mov dword ptr ds:[edi+0x20],eax + * 011a7b59 0fbf46 0c movsx eax,word ptr ds:[esi+0xc] + * 011a7b5d 8947 10 mov dword ptr ds:[edi+0x10],eax + * 011a7b60 0fbf46 0e movsx eax,word ptr ds:[esi+0xe] + * 011a7b64 8947 14 mov dword ptr ds:[edi+0x14],eax + * 011a7b67 0fbf46 08 movsx eax,word ptr ds:[esi+0x8] + * 011a7b6b 0345 0c add eax,dword ptr ss:[ebp+0xc] + * 011a7b6e 8947 08 mov dword ptr ds:[edi+0x8],eax + * 011a7b71 0fbf46 0a movsx eax,word ptr ds:[esi+0xa] + * 011a7b75 8b4d 10 mov ecx,dword ptr ss:[ebp+0x10] + * 011a7b78 2bc8 sub ecx,eax + * 011a7b7a 894f 0c mov dword ptr ds:[edi+0xc],ecx + * 011a7b7d 0fb643 20 movzx eax,byte ptr ds:[ebx+0x20] + * 011a7b81 8847 30 mov byte ptr ds:[edi+0x30],al + * 011a7b84 c647 32 00 mov byte ptr ds:[edi+0x32],0x0 + * 011a7b88 0fb643 21 movzx eax,byte ptr ds:[ebx+0x21] + * 011a7b8c 8847 31 mov byte ptr ds:[edi+0x31],al + * 011a7b8f 8b43 1c mov eax,dword ptr ds:[ebx+0x1c] + * 011a7b92 8947 28 mov dword ptr ds:[edi+0x28],eax + * 011a7b95 8b43 18 mov eax,dword ptr ds:[ebx+0x18] + * 011a7b98 8947 24 mov dword ptr ds:[edi+0x24],eax + * 011a7b9b c745 fc 01000000 mov dword ptr ss:[ebp-0x4],0x1 + * 011a7ba2 837f 1c 00 cmp dword ptr ds:[edi+0x1c],0x0 + * 011a7ba6 74 64 je short .011a7c0c + * 011a7ba8 8b47 20 mov eax,dword ptr ds:[edi+0x20] + * 011a7bab 85c0 test eax,eax + * 011a7bad 74 5d je short .011a7c0c + * 011a7baf 0fb776 04 movzx esi,word ptr ds:[esi+0x4] + * 011a7bb3 4e dec esi + * 011a7bb4 83e6 fc and esi,0xfffffffc + * 011a7bb7 83c6 04 add esi,0x4 + * 011a7bba 8977 18 mov dword ptr ds:[edi+0x18],esi + * 011a7bbd 0fafc6 imul eax,esi + * 011a7bc0 50 push eax + * 011a7bc1 8bcf mov ecx,edi + * 011a7bc3 e8 b8f6ffff call .011a7280 + * 011a7bc8 56 push esi + * 011a7bc9 ff37 push dword ptr ds:[edi] + * 011a7bcb ff75 ec push dword ptr ss:[ebp-0x14] + * 011a7bce 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + * 011a7bd1 e8 3a090300 call .011d8510 + * 011a7bd6 807b 21 00 cmp byte ptr ds:[ebx+0x21],0x0 + * 011a7bda 74 0d je short .011a7be9 + * 011a7bdc ff77 28 push dword ptr ds:[edi+0x28] + * 011a7bdf ff77 24 push dword ptr ds:[edi+0x24] + * 011a7be2 8bcf mov ecx,edi + * 011a7be4 e8 d70affff call .011986c0 + * 011a7be9 897d ec mov dword ptr ss:[ebp-0x14],edi + * 011a7bec ff47 04 inc dword ptr ds:[edi+0x4] + * 011a7bef c645 fc 02 mov byte ptr ss:[ebp-0x4],0x2 + * 011a7bf3 8d45 ec lea eax,dword ptr ss:[ebp-0x14] + * 011a7bf6 50 push eax + * 011a7bf7 ff75 e4 push dword ptr ss:[ebp-0x1c] + * 011a7bfa 53 push ebx + * 011a7bfb e8 50280000 call .011aa450 + * 011a7c00 c645 fc 01 mov byte ptr ss:[ebp-0x4],0x1 + * 011a7c04 8d4d ec lea ecx,dword ptr ss:[ebp-0x14] + * 011a7c07 e8 84280000 call .011aa490 + * 011a7c0c c745 fc ffffffff mov dword ptr ss:[ebp-0x4],-0x1 + * 011a7c13 8bc7 mov eax,edi + * 011a7c15 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 011a7c18 64:890d 00000000 mov dword ptr fs:[0],ecx + * 011a7c1f 59 pop ecx + * 011a7c20 5f pop edi + * 011a7c21 5e pop esi + * 011a7c22 5b pop ebx + * 011a7c23 8be5 mov esp,ebp + * 011a7c25 5d pop ebp + * 011a7c26 c3 retn + * 011a7c27 8b4d e8 mov ecx,dword ptr ss:[ebp-0x18] + * 011a7c2a e8 81f6ffff call .011a72b0 + * 011a7c2f 6a 00 push 0x0 + * 011a7c31 6a 00 push 0x0 + * 011a7c33 e8 93cb1300 call .012e47cb + * 011a7c38 a1 dc8a3d01 mov eax,dword ptr ds:[0x13d8adc] + * 011a7c3d 8b0c85 88b93f01 mov ecx,dword ptr ds:[eax*4+0x13fb988] + * 011a7c44 8b01 mov eax,dword ptr ds:[ecx] + * 011a7c46 ff75 10 push dword ptr ss:[ebp+0x10] + * 011a7c49 ff75 0c push dword ptr ss:[ebp+0xc] + * 011a7c4c 53 push ebx + * 011a7c4d ff50 1c call dword ptr ds:[eax+0x1c] + * 011a7c50 8bf0 mov esi,eax + * 011a7c52 8975 e4 mov dword ptr ss:[ebp-0x1c],esi + * 011a7c55 ff46 04 inc dword ptr ds:[esi+0x4] + * 011a7c58 c745 fc 04000000 mov dword ptr ss:[ebp-0x4],0x4 + * 011a7c5f 8d45 e4 lea eax,dword ptr ss:[ebp-0x1c] + * 011a7c62 50 push eax + * 011a7c63 57 push edi + * 011a7c64 53 push ebx + * 011a7c65 e8 a62c0000 call .011aa910 + * 011a7c6a a1 388b3f01 mov eax,dword ptr ds:[0x13f8b38] + * 011a7c6f 8b0d 448b3f01 mov ecx,dword ptr ds:[0x13f8b44] + * 011a7c75 3bc1 cmp eax,ecx + * 011a7c77 76 08 jbe short .011a7c81 + * 011a7c79 2bc1 sub eax,ecx + * 011a7c7b 50 push eax + * 011a7c7c e8 1f2e0000 call .011aaaa0 + * 011a7c81 c745 fc ffffffff mov dword ptr ss:[ebp-0x4],-0x1 + * 011a7c88 8b46 04 mov eax,dword ptr ds:[esi+0x4] + * 011a7c8b 83f8 01 cmp eax,0x1 + * 011a7c8e 75 2c jnz short .011a7cbc + * 011a7c90 8b06 mov eax,dword ptr ds:[esi] + * 011a7c92 85c0 test eax,eax + * 011a7c94 74 09 je short .011a7c9f + * 011a7c96 50 push eax + * 011a7c97 e8 3b621300 call .012dded7 + * 011a7c9c 83c4 04 add esp,0x4 + * 011a7c9f 56 push esi + * 011a7ca0 e8 335e1300 call .012ddad8 + * 011a7ca5 83c4 04 add esp,0x4 + * 011a7ca8 8bc6 mov eax,esi + * 011a7caa 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 011a7cad 64:890d 00000000 mov dword ptr fs:[0],ecx + * 011a7cb4 59 pop ecx + * 011a7cb5 5f pop edi + * 011a7cb6 5e pop esi + * 011a7cb7 5b pop ebx + * 011a7cb8 8be5 mov esp,ebp + * 011a7cba 5d pop ebp + * 011a7cbb c3 retn + * 011a7cbc 48 dec eax + * 011a7cbd 8946 04 mov dword ptr ds:[esi+0x4],eax + * 011a7cc0 8bc6 mov eax,esi + * 011a7cc2 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 011a7cc5 64:890d 00000000 mov dword ptr fs:[0],ecx + * 011a7ccc 59 pop ecx + * 011a7ccd 5f pop edi + * 011a7cce 5e pop esi + * 011a7ccf 5b pop ebx + * 011a7cd0 8be5 mov esp,ebp + * 011a7cd2 5d pop ebp + * 011a7cd3 c3 retn + * 011a7cd4 cc int3 + * 011a7cd5 cc int3 + * 011a7cd6 cc int3 + * 011a7cd7 cc int3 + * 011a7cd8 cc int3 + * + * Here's the hooked function in サノバウィッ� (150226). + * I randomly picked a pattern from VBH: + * + * 011a7a95 33c0 xor eax,eax + * 011a7a97 3843 20 cmp byte ptr ds:[ebx+0x20],al + * 011a7a9a 0f95c0 setne al + * 011a7a9d 33c8 xor ecx,eax + * 011a7a9f 334b 10 xor ecx,dword ptr ds:[ebx+0x10] + * 011a7aa2 0fb743 14 movzx eax,word ptr ds:[ebx+0x14] + * + * i.e: 33c03843200f95c033c8334b100fb74314 + * + * The new hooked function in サノバウィッ� is as follows. + * + * 012280dc cc int3 + * 012280dd cc int3 + * 012280de cc int3 + * 012280df cc int3 + * 012280e0 55 push ebp + * 012280e1 8bec mov ebp,esp + * 012280e3 6a ff push -0x1 + * 012280e5 68 3b813d01 push .013d813b + * 012280ea 64:a1 00000000 mov eax,dword ptr fs:[0] + * 012280f0 50 push eax + * 012280f1 83ec 14 sub esp,0x14 + * 012280f4 53 push ebx + * 012280f5 56 push esi + * 012280f6 57 push edi + * 012280f7 a1 00694901 mov eax,dword ptr ds:[0x1496900] + * 012280fc 33c5 xor eax,ebp + * 012280fe 50 push eax + * 012280ff 8d45 f4 lea eax,dword ptr ss:[ebp-0xc] + * 01228102 64:a3 00000000 mov dword ptr fs:[0],eax + * 01228108 8965 f0 mov dword ptr ss:[ebp-0x10],esp + * 0122810b 8bd9 mov ebx,ecx + * 0122810d 803d e82d4a01 00 cmp byte ptr ds:[0x14a2de8],0x0 + * 01228114 75 17 jnz short .0122812d + * 01228116 c745 e8 d8d44901 mov dword ptr ss:[ebp-0x18],.0149d4d8 + * 0122811d 8d45 e8 lea eax,dword ptr ss:[ebp-0x18] + * 01228120 50 push eax + * 01228121 e8 aadbf0ff call .01135cd0 + * 01228126 c605 e82d4a01 01 mov byte ptr ds:[0x14a2de8],0x1 + * 0122812d 33c9 xor ecx,ecx + * 0122812f 384b 21 cmp byte ptr ds:[ebx+0x21],cl + * 01228132 0f95c1 setne cl + * 01228135 33c0 xor eax,eax + * 01228137 3843 20 cmp byte ptr ds:[ebx+0x20],al + * 0122813a 0f95c0 setne al + * 0122813d 33c8 xor ecx,eax + * 0122813f 334b 10 xor ecx,dword ptr ds:[ebx+0x10] + * 01228142 0fb743 14 movzx eax,word ptr ds:[ebx+0x14] + * 01228146 33c8 xor ecx,eax + * 01228148 8b7b 1c mov edi,dword ptr ds:[ebx+0x1c] + * 0122814b 33f9 xor edi,ecx + * 0122814d 337b 18 xor edi,dword ptr ds:[ebx+0x18] + * 01228150 897d e4 mov dword ptr ss:[ebp-0x1c],edi + * 01228153 57 push edi + * 01228154 53 push ebx + * 01228155 e8 06330000 call .0122b460 + * 0122815a 8bf0 mov esi,eax + * 0122815c 85f6 test esi,esi + * 0122815e 74 26 je short .01228186 + * 01228160 56 push esi + * 01228161 e8 ba330000 call .0122b520 + * 01228166 8d46 2c lea eax,dword ptr ds:[esi+0x2c] + * 01228169 85c0 test eax,eax + * 0122816b 74 19 je short .01228186 + * 0122816d 8b08 mov ecx,dword ptr ds:[eax] + * 0122816f ff41 04 inc dword ptr ds:[ecx+0x4] + * 01228172 8b00 mov eax,dword ptr ds:[eax] + * 01228174 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 01228177 64:890d 00000000 mov dword ptr fs:[0],ecx + * 0122817e 59 pop ecx + * 0122817f 5f pop edi + * 01228180 5e pop esi + * 01228181 5b pop ebx + * 01228182 8be5 mov esp,ebp + * 01228184 5d pop ebp + * 01228185 c3 retn + * 01228186 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + * 01228189 85c9 test ecx,ecx + * 0122818b 0f84 47010000 je .012282d8 + * 01228191 0fb743 14 movzx eax,word ptr ds:[ebx+0x14] + * 01228195 50 push eax + * 01228196 e8 950f0300 call .01259130 + * 0122819b 8bf0 mov esi,eax + * 0122819d 8975 ec mov dword ptr ss:[ebp-0x14],esi + * 012281a0 85f6 test esi,esi + * 012281a2 0f84 30010000 je .012282d8 + * 012281a8 6a 34 push 0x34 + * 012281aa e8 297c1300 call .0135fdd8 + * 012281af 83c4 04 add esp,0x4 + * 012281b2 8bf8 mov edi,eax + * 012281b4 897d e0 mov dword ptr ss:[ebp-0x20],edi + * 012281b7 c745 fc 00000000 mov dword ptr ss:[ebp-0x4],0x0 + * 012281be 85ff test edi,edi + * 012281c0 74 1d je short .012281df + * 012281c2 c747 2c 41000000 mov dword ptr ds:[edi+0x2c],0x41 + * 012281c9 c647 32 00 mov byte ptr ds:[edi+0x32],0x0 + * 012281cd c747 04 01000000 mov dword ptr ds:[edi+0x4],0x1 + * 012281d4 c707 00000000 mov dword ptr ds:[edi],0x0 + * 012281da 8945 e8 mov dword ptr ss:[ebp-0x18],eax + * 012281dd eb 05 jmp short .012281e4 + * 012281df 33ff xor edi,edi + * 012281e1 897d e8 mov dword ptr ss:[ebp-0x18],edi + * 012281e4 c745 fc ffffffff mov dword ptr ss:[ebp-0x4],-0x1 + * 012281eb 0fb746 04 movzx eax,word ptr ds:[esi+0x4] + * 012281ef 8947 1c mov dword ptr ds:[edi+0x1c],eax + * 012281f2 0fb746 06 movzx eax,word ptr ds:[esi+0x6] + * 012281f6 8947 20 mov dword ptr ds:[edi+0x20],eax + * 012281f9 0fbf46 0c movsx eax,word ptr ds:[esi+0xc] + * 012281fd 8947 10 mov dword ptr ds:[edi+0x10],eax + * 01228200 0fbf46 0e movsx eax,word ptr ds:[esi+0xe] + * 01228204 8947 14 mov dword ptr ds:[edi+0x14],eax + * 01228207 0fbf46 08 movsx eax,word ptr ds:[esi+0x8] + * 0122820b 0345 0c add eax,dword ptr ss:[ebp+0xc] + * 0122820e 8947 08 mov dword ptr ds:[edi+0x8],eax + * 01228211 0fbf46 0a movsx eax,word ptr ds:[esi+0xa] + * 01228215 8b4d 10 mov ecx,dword ptr ss:[ebp+0x10] + * 01228218 2bc8 sub ecx,eax + * 0122821a 894f 0c mov dword ptr ds:[edi+0xc],ecx + * 0122821d 0fb643 20 movzx eax,byte ptr ds:[ebx+0x20] + * 01228221 8847 30 mov byte ptr ds:[edi+0x30],al + * 01228224 c647 32 00 mov byte ptr ds:[edi+0x32],0x0 + * 01228228 0fb643 21 movzx eax,byte ptr ds:[ebx+0x21] + * 0122822c 8847 31 mov byte ptr ds:[edi+0x31],al + * 0122822f 8b43 1c mov eax,dword ptr ds:[ebx+0x1c] + * 01228232 8947 28 mov dword ptr ds:[edi+0x28],eax + * 01228235 8b43 18 mov eax,dword ptr ds:[ebx+0x18] + * 01228238 8947 24 mov dword ptr ds:[edi+0x24],eax + * 0122823b c745 fc 01000000 mov dword ptr ss:[ebp-0x4],0x1 + * 01228242 837f 1c 00 cmp dword ptr ds:[edi+0x1c],0x0 + * 01228246 74 64 je short .012282ac + * 01228248 8b47 20 mov eax,dword ptr ds:[edi+0x20] + * 0122824b 85c0 test eax,eax + * 0122824d 74 5d je short .012282ac + * 0122824f 0fb776 04 movzx esi,word ptr ds:[esi+0x4] + * 01228253 4e dec esi + * 01228254 83e6 fc and esi,0xfffffffc + * 01228257 83c6 04 add esi,0x4 + * 0122825a 8977 18 mov dword ptr ds:[edi+0x18],esi + * 0122825d 0fafc6 imul eax,esi + * 01228260 50 push eax + * 01228261 8bcf mov ecx,edi + * 01228263 e8 a8f6ffff call .01227910 + * 01228268 56 push esi + * 01228269 ff37 push dword ptr ds:[edi] + * 0122826b ff75 ec push dword ptr ss:[ebp-0x14] + * 0122826e 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + * 01228271 e8 1a0f0300 call .01259190 + * 01228276 807b 21 00 cmp byte ptr ds:[ebx+0x21],0x0 + * 0122827a 74 0d je short .01228289 + * 0122827c ff77 28 push dword ptr ds:[edi+0x28] + * 0122827f ff77 24 push dword ptr ds:[edi+0x24] + * 01228282 8bcf mov ecx,edi + * 01228284 e8 870affff call .01218d10 + * 01228289 897d ec mov dword ptr ss:[ebp-0x14],edi + * 0122828c ff47 04 inc dword ptr ds:[edi+0x4] + * 0122828f c645 fc 02 mov byte ptr ss:[ebp-0x4],0x2 + * 01228293 8d45 ec lea eax,dword ptr ss:[ebp-0x14] + * 01228296 50 push eax + * 01228297 ff75 e4 push dword ptr ss:[ebp-0x1c] + * 0122829a 53 push ebx + * 0122829b e8 50280000 call .0122aaf0 + * 012282a0 c645 fc 01 mov byte ptr ss:[ebp-0x4],0x1 + * 012282a4 8d4d ec lea ecx,dword ptr ss:[ebp-0x14] + * 012282a7 e8 84280000 call .0122ab30 + * 012282ac c745 fc ffffffff mov dword ptr ss:[ebp-0x4],-0x1 + * 012282b3 8bc7 mov eax,edi + * 012282b5 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 012282b8 64:890d 00000000 mov dword ptr fs:[0],ecx + * 012282bf 59 pop ecx + * 012282c0 5f pop edi + * 012282c1 5e pop esi + * 012282c2 5b pop ebx + * 012282c3 8be5 mov esp,ebp + * 012282c5 5d pop ebp + * 012282c6 c3 retn + * 012282c7 8b4d e8 mov ecx,dword ptr ss:[ebp-0x18] + * 012282ca e8 71f6ffff call .01227940 + * 012282cf 6a 00 push 0x0 + * 012282d1 6a 00 push 0x0 + * 012282d3 e8 83eb1300 call .01366e5b + * 012282d8 a1 e89a4901 mov eax,dword ptr ds:[0x1499ae8] + * 012282dd 8b0c85 f0d64b01 mov ecx,dword ptr ds:[eax*4+0x14bd6f0] + * 012282e4 8b01 mov eax,dword ptr ds:[ecx] + * 012282e6 ff75 10 push dword ptr ss:[ebp+0x10] + * 012282e9 ff75 0c push dword ptr ss:[ebp+0xc] + * 012282ec 53 push ebx + * 012282ed ff50 1c call dword ptr ds:[eax+0x1c] + * 012282f0 8bf0 mov esi,eax + * 012282f2 8975 e4 mov dword ptr ss:[ebp-0x1c],esi + * 012282f5 ff46 04 inc dword ptr ds:[esi+0x4] + * 012282f8 c745 fc 04000000 mov dword ptr ss:[ebp-0x4],0x4 + * 012282ff 8d45 e4 lea eax,dword ptr ss:[ebp-0x1c] + * 01228302 50 push eax + * 01228303 57 push edi + * 01228304 53 push ebx + * 01228305 e8 a62c0000 call .0122afb0 + * 0122830a a1 a0a84b01 mov eax,dword ptr ds:[0x14ba8a0] + * 0122830f 8b0d aca84b01 mov ecx,dword ptr ds:[0x14ba8ac] + * 01228315 3bc1 cmp eax,ecx + * 01228317 76 08 jbe short .01228321 + * 01228319 2bc1 sub eax,ecx + * 0122831b 50 push eax + * 0122831c e8 1f2e0000 call .0122b140 + * 01228321 c745 fc ffffffff mov dword ptr ss:[ebp-0x4],-0x1 + * 01228328 8b46 04 mov eax,dword ptr ds:[esi+0x4] + * 0122832b 83f8 01 cmp eax,0x1 + * 0122832e 75 2c jnz short .0122835c + * 01228330 8b06 mov eax,dword ptr ds:[esi] + * 01228332 85c0 test eax,eax + * 01228334 74 09 je short .0122833f + * 01228336 50 push eax + * 01228337 e8 3b7c1300 call .0135ff77 + * 0122833c 83c4 04 add esp,0x4 + * 0122833f 56 push esi + * 01228340 e8 33781300 call .0135fb78 + * 01228345 83c4 04 add esp,0x4 + * 01228348 8bc6 mov eax,esi + * 0122834a 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 0122834d 64:890d 00000000 mov dword ptr fs:[0],ecx + * 01228354 59 pop ecx + * 01228355 5f pop edi + * 01228356 5e pop esi + * 01228357 5b pop ebx + * 01228358 8be5 mov esp,ebp + * 0122835a 5d pop ebp + * 0122835b c3 retn + * 0122835c 48 dec eax + * 0122835d 8946 04 mov dword ptr ds:[esi+0x4],eax + * 01228360 8bc6 mov eax,esi + * 01228362 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 01228365 64:890d 00000000 mov dword ptr fs:[0],ecx + * 0122836c 59 pop ecx + * 0122836d 5f pop edi + * 0122836e 5e pop esi + * 0122836f 5b pop ebx + * 01228370 8be5 mov esp,ebp + * 01228372 5d pop ebp + * 01228373 c3 retn + * 01228374 cc int3 + * 01228375 cc int3 + * 01228376 cc int3 + * 01228377 cc int3 + * 01228378 cc int3 + */ + +namespace { // unnamed + +// Skip individual L'\n' which might cause repetition. +//bool NewLineWideCharSkipper(LPVOID data, DWORD *size, HookParam *) +//{ +// LPCWSTR text = (LPCWSTR)data; +// if (*size == 2 && *text == L'\n') +// return false; +// return true; +//} +// + +void NewKiriKiriZHook(DWORD addr) +{ + HookParam hp = {}; + hp.address = addr; + hp.offset = pusha_ecx_off - 4; + hp.split = hp.offset; // the same logic but diff value as KiriKiri1, use [ecx] as split + hp.index = 0x14; // the same as KiriKiri1 + hp.length_offset = 1; // the same as KiriKiri1 + hp.type = USING_UNICODE|DATA_INDIRECT|USING_SPLIT|SPLIT_INDIRECT; + //hp.filter_fun = NewLineWideCharFilter; + ConsoleOutput("vnreng: INSERT KiriKiriZ"); + NewHook(hp, "KiriKiriZ"); + + ConsoleOutput("vnreng:KiriKiriZ: disable GDI hooks"); + DisableGDIHooks(); +} + +bool KiriKiriZHook1(DWORD esp_base, HookParam *) +{ + DWORD addr = retof(esp_base); // retaddr + addr = MemDbg::findEnclosingAlignedFunction(addr, 0x400); // range is around 0x377c50 - 0x377a40 = 0x210 + if (!addr) { + ConsoleOutput("vnreng:KiriKiriZ: failed to find enclosing function"); + return false; // stop looking + } + NewKiriKiriZHook(addr); + ConsoleOutput("vnreng: KiriKiriZ1 inserted"); + return false; // stop looking +} + +bool InsertKiriKiriZHook1() +{ + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng:KiriKiriZ1: failed to get memory range"); + return false; + } + + ULONG addr = MemDbg::findCallerAddressAfterInt3((DWORD)::GetGlyphOutlineW, startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:KiriKiriZ1: could not find caller of GetGlyphOutlineW"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.type = HOOK_EMPTY; + hp.hook_fun = KiriKiriZHook1; + ConsoleOutput("vnreng: INSERT KiriKiriZ1 empty hook"); + NewHook(hp, "KiriKiriZ Hook"); + return true; +} + +// jichi 1/30/2015: Add KiriKiriZ2 for サノバウィッ� +// It inserts to the same location as the old KiriKiriZ, but use a different way to find it. +bool InsertKiriKiriZHook2() +{ + const BYTE bytes[] = { + 0x38,0x4b, 0x21, // 0122812f 384b 21 cmp byte ptr ds:[ebx+0x21],cl + 0x0f,0x95,0xc1, // 01228132 0f95c1 setne cl + 0x33,0xc0, // 01228135 33c0 xor eax,eax + 0x38,0x43, 0x20, // 01228137 3843 20 cmp byte ptr ds:[ebx+0x20],al + 0x0f,0x95,0xc0, // 0122813a 0f95c0 setne al + 0x33,0xc8, // 0122813d 33c8 xor ecx,eax + 0x33,0x4b, 0x10, // 0122813f 334b 10 xor ecx,dword ptr ds:[ebx+0x10] + 0x0f,0xb7,0x43, 0x14 // 01228142 0fb743 14 movzx eax,word ptr ds:[ebx+0x14] + }; + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //GROWL_DWORD(addr); + if (!addr) { + ConsoleOutput("vnreng:KiriKiriZ2: pattern not found"); + return false; + } + + // 012280e0 55 push ebp + // 012280e1 8bec mov ebp,esp + addr = MemDbg::findEnclosingAlignedFunction(addr, 0x100); // 0x0122812f - 0x 012280dc = 83 + enum : BYTE { push_ebp = 0x55 }; // 011d4c80 /$ 55 push ebp + if (!addr || *(BYTE *)addr != push_ebp) { + ConsoleOutput("vnreng:KiriKiriZ2: pattern found but the function offset is invalid"); + return false; + } + + NewKiriKiriZHook(addr); + ConsoleOutput("vnreng: KiriKiriZ2 inserted"); + return true; +} + +} // unnamed namespace + +// jichi 1/30/2015: Do KiriKiriZ2 first, which might insert to the same location as KiriKiri1. +bool InsertKiriKiriZHook() +{ return InsertKiriKiriZHook2() || InsertKiriKiriZHook1(); } + +/******************************************************************************************** +BGI hook: + Usually game folder contains BGI.*. After first run BGI.gdb appears. + + BGI engine has font caching issue so the strategy is simple. + First find call to TextOutA or TextOutW then reverse to function entry point, + until full text is caught. + After 2 tries we will get to the right place. Use ESP value to split text since + it's likely to be different for different calls. +********************************************************************************************/ +namespace { // unnamed +#if 0 // jichi 12/28/2013: dynamic BGI is not used +static bool FindBGIHook(DWORD fun, DWORD size, DWORD pt, WORD sig) +{ + if (!fun) { + ConsoleOutput("vnreng:BGI: cannot find BGI hook"); + //swprintf(str, L"Can't find BGI hook: %.8X.",fun); + //ConsoleOutput(str); + return false; + } + //WCHAR str[0x40]; + //i=FindCallBoth(fun,size,pt); + + //swprintf(str, L"CALL addr: 0x%.8X",pt+i); + //ConsoleOutput(str); + for (DWORD i = fun, j = fun; j > i - 0x100; j--) + if ((*(WORD *)(pt + j)) == sig) { // Fun entry 1. + //swprintf(str, L"Entry 1: 0x%.8X",pt+j); + //ConsoleOutput(str); + for (DWORD k = i + 0x100; k < i+0x800; k++) + if (*(BYTE *)(pt + k) == 0xe8) + if (k + 5 + *(DWORD *)(pt + k + 1) == j) { // Find call to fun1. + //swprintf(str, L"CALL to entry 1: 0x%.8X",pt+k); + //ConsoleOutput(str); + for (DWORD l = k; l > k - 0x100;l--) + if ((*(WORD *)(pt + l)) == 0xec83) { // Fun entry 2. + //swprintf(str, L"Entry 2(final): 0x%.8X",pt+l); + //ConsoleOutput(str); + HookParam hp = {}; + hp.address = (DWORD)pt + l; + hp.offset = 0x8; + hp.split = -0x18; + hp.length_offset = 1; + hp.type = BIG_ENDIAN|USING_SPLIT; + ConsoleOutput("vnreng:INSERT DynamicBGI"); + NewHook(hp, "BGI"); + return true; + } + } + } + ConsoleOutput("vnreng:DynamicBGI: failed"); + return false; +} +bool InsertBGIDynamicHook(LPVOID addr, DWORD frame, DWORD stack) +{ + if (addr != TextOutA && addr != TextOutW) { + //ConsoleOutput("vnreng:DynamicBGI: failed"); + return false; + } + + DWORD i = *(DWORD *)(stack + 4) - module_base_; + return FindBGIHook(i, module_limit_ - module_base_, module_base_, 0xec83); +} +#endif // 0 + +/** jichi 5/12/2014 + * Sample game: FORTUNE ARTERIAL, case 2 at 0x41ebd0 + * + * sub_41EBD0 proc near, seems to take 5 parameters + * + * 0041ebd0 /$ 83ec 28 sub esp,0x28 ; jichi: hook here, beginning of the function + * 0041ebd3 |. 55 push ebp + * 0041ebd4 |. 8b6c24 38 mov ebp,dword ptr ss:[esp+0x38] + * 0041ebd8 |. 81fd 00ff0000 cmp ebp,0xff00 + * 0041ebde |. 0f82 e1000000 jb bgi.0041ecc5 + * 0041ebe4 |. 81fd ffff0000 cmp ebp,0xffff + * 0041ebea |. 0f87 d5000000 ja bgi.0041ecc5 + * 0041ebf0 |. a1 54634900 mov eax,dword ptr ds:[0x496354] + * 0041ebf5 |. 8bd5 mov edx,ebp + * 0041ebf7 |. 81e2 ff000000 and edx,0xff + * 0041ebfd |. 53 push ebx + * 0041ebfe |. 4a dec edx + * 0041ebff |. 33db xor ebx,ebx + * 0041ec01 |. 3bd0 cmp edx,eax + * 0041ec03 |. 56 push esi + * 0041ec04 |. 0f8d 8a000000 jge bgi.0041ec94 + * 0041ec0a |. 57 push edi + * 0041ec0b |. b9 06000000 mov ecx,0x6 + * 0041ec10 |. be 5c634900 mov esi,bgi.0049635c + * 0041ec15 |. 8d7c24 20 lea edi,dword ptr ss:[esp+0x20] + * 0041ec19 |. f3:a5 rep movs dword ptr es:[edi],dword ptr ds> + * 0041ec1b |. 8b0d 58634900 mov ecx,dword ptr ds:[0x496358] + * 0041ec21 |. 8b7424 3c mov esi,dword ptr ss:[esp+0x3c] + * 0041ec25 |. 8bc1 mov eax,ecx + * 0041ec27 |. 5f pop edi + * 0041ec28 |. 0fafc2 imul eax,edx + * 0041ec2b |. 8b56 08 mov edx,dword ptr ds:[esi+0x8] + * 0041ec2e |. 894424 0c mov dword ptr ss:[esp+0xc],eax + * 0041ec32 |. 3bca cmp ecx,edx + * 0041ec34 |. 7e 02 jle short bgi.0041ec38 + * 0041ec36 |. 8bca mov ecx,edx + * 0041ec38 |> 8d4401 ff lea eax,dword ptr ds:[ecx+eax-0x1] + * 0041ec3c |. 8b4c24 28 mov ecx,dword ptr ss:[esp+0x28] + * 0041ec40 |. 894424 14 mov dword ptr ss:[esp+0x14],eax + * 0041ec44 |. 8b46 0c mov eax,dword ptr ds:[esi+0xc] + * 0041ec47 |. 3bc8 cmp ecx,eax + * 0041ec49 |. 895c24 10 mov dword ptr ss:[esp+0x10],ebx + * 0041ec4d |. 77 02 ja short bgi.0041ec51 + * 0041ec4f |. 8bc1 mov eax,ecx + * 0041ec51 |> 8d4c24 0c lea ecx,dword ptr ss:[esp+0xc] + * 0041ec55 |. 8d5424 1c lea edx,dword ptr ss:[esp+0x1c] + * 0041ec59 |. 48 dec eax + * 0041ec5a |. 51 push ecx + * 0041ec5b |. 52 push edx + * 0041ec5c |. 894424 20 mov dword ptr ss:[esp+0x20],eax + * 0041ec60 |. e8 7b62feff call bgi.00404ee0 + * 0041ec65 |. 8b4424 34 mov eax,dword ptr ss:[esp+0x34] + * 0041ec69 |. 83c4 08 add esp,0x8 + * 0041ec6c |. 83f8 03 cmp eax,0x3 + * 0041ec6f |. 75 15 jnz short bgi.0041ec86 + * 0041ec71 |. 8b4424 48 mov eax,dword ptr ss:[esp+0x48] + * 0041ec75 |. 8d4c24 1c lea ecx,dword ptr ss:[esp+0x1c] + * 0041ec79 |. 50 push eax + * 0041ec7a |. 51 push ecx + * 0041ec7b |. 56 push esi + * 0041ec7c |. e8 1fa0feff call bgi.00408ca0 + */ +bool InsertBGI1Hook() +{ + union { + DWORD i; + DWORD *id; + BYTE *ib; + }; + HookParam hp = {}; + for (i = module_base_ + 0x1000; i < module_limit_; i++) { + if (ib[0] == 0x3d) { + i++; + if (id[0] == 0xffff) { //cmp eax,0xffff + hp.address = SafeFindEntryAligned(i, 0x40); + if (hp.address) { + hp.offset = 0xc; + hp.split = -0x18; + hp.type = BIG_ENDIAN|USING_SPLIT; + hp.length_offset = 1; + ConsoleOutput("vnreng:INSERT BGI#1"); + NewHook(hp, "BGI"); + //RegisterEngineType(ENGINE_BGI); + return true; + } + } + } + if (ib[0] == 0x81 && ((ib[1] & 0xf8) == 0xf8)) { + i += 2; + if (id[0] == 0xffff) { //cmp reg,0xffff + hp.address = SafeFindEntryAligned(i, 0x40); + if (hp.address) { + hp.offset = 0xc; + hp.split = -0x18; + hp.type = BIG_ENDIAN|USING_SPLIT; + hp.length_offset = 1; + ConsoleOutput("vnreng: INSERT BGI#2"); + NewHook(hp, "BGI"); + //RegisterEngineType(ENGINE_BGI); + return true; + } + } + } + } + //ConsoleOutput("Unknown BGI engine."); + + //ConsoleOutput("Probably BGI. Wait for text."); + //SwitchTrigger(true); + //trigger_fun_=InsertBGIDynamicHook; + ConsoleOutput("vnreng:BGI: failed"); + return false; +} + +/** + * jichi 2/5/2014: Add an alternative BGI hook + * + * Issue: This hook cannot extract character name for コトバの消えた日 + * + * See: http://tieba.baidu.com/p/2845113296 + * 世界と世界の真ん中で + * - /HSN4@349E0:sekachu.exe // Disabled BGI3, floating split char + * - /HS-1C:-4@68E56 // Not used, cannot detect character name + * - /HSC@34C80:sekachu.exe // BGI2, extract both scenario and character names + * + * [Lump of Sugar] 世界と世界の真ん中で + * /HSC@34C80:sekachu.exe + * - addr: 216192 = 0x34c80 + * - module: 3599131534 + * - off: 12 = 0xc + * - type: 65 = 0x41 + * + * base: 0x11a0000 + * hook_addr = base + addr = 0x11d4c80 + * + * 011d4c7e cc int3 + * 011d4c7f cc int3 + * 011d4c80 /$ 55 push ebp ; jichi: hook here + * 011d4c81 |. 8bec mov ebp,esp + * 011d4c83 |. 6a ff push -0x1 + * 011d4c85 |. 68 e6592601 push sekachu.012659e6 + * 011d4c8a |. 64:a1 00000000 mov eax,dword ptr fs:[0] + * 011d4c90 |. 50 push eax + * 011d4c91 |. 81ec 300d0000 sub esp,0xd30 + * 011d4c97 |. a1 d8c82801 mov eax,dword ptr ds:[0x128c8d8] + * 011d4c9c |. 33c5 xor eax,ebp + * 011d4c9e |. 8945 f0 mov dword ptr ss:[ebp-0x10],eax + * 011d4ca1 |. 53 push ebx + * 011d4ca2 |. 56 push esi + * 011d4ca3 |. 57 push edi + * 011d4ca4 |. 50 push eax + * 011d4ca5 |. 8d45 f4 lea eax,dword ptr ss:[ebp-0xc] + * 011d4ca8 |. 64:a3 00000000 mov dword ptr fs:[0],eax + * 011d4cae |. 8b4d 0c mov ecx,dword ptr ss:[ebp+0xc] + * 011d4cb1 |. 8b55 18 mov edx,dword ptr ss:[ebp+0x18] + * 011d4cb4 |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 011d4cb7 |. 8b5d 10 mov ebx,dword ptr ss:[ebp+0x10] + * 011d4cba |. 8b7d 38 mov edi,dword ptr ss:[ebp+0x38] + * 011d4cbd |. 898d d8f3ffff mov dword ptr ss:[ebp-0xc28],ecx + * 011d4cc3 |. 8b4d 28 mov ecx,dword ptr ss:[ebp+0x28] + * 011d4cc6 |. 8995 9cf3ffff mov dword ptr ss:[ebp-0xc64],edx + * 011d4ccc |. 51 push ecx + * 011d4ccd |. 8b0d 305c2901 mov ecx,dword ptr ds:[0x1295c30] + * 011d4cd3 |. 8985 e0f3ffff mov dword ptr ss:[ebp-0xc20],eax + * 011d4cd9 |. 8b45 1c mov eax,dword ptr ss:[ebp+0x1c] + * 011d4cdc |. 8d95 4cf4ffff lea edx,dword ptr ss:[ebp-0xbb4] + * 011d4ce2 |. 52 push edx + * 011d4ce3 |. 899d 40f4ffff mov dword ptr ss:[ebp-0xbc0],ebx + * 011d4ce9 |. 8985 1cf4ffff mov dword ptr ss:[ebp-0xbe4],eax + * 011d4cef |. 89bd f0f3ffff mov dword ptr ss:[ebp-0xc10],edi + * 011d4cf5 |. e8 862efdff call sekachu.011a7b80 + * 011d4cfa |. 33c9 xor ecx,ecx + * 011d4cfc |. 8985 60f3ffff mov dword ptr ss:[ebp-0xca0],eax + * 011d4d02 |. 3bc1 cmp eax,ecx + * 011d4d04 |. 0f84 0f1c0000 je sekachu.011d6919 + * 011d4d0a |. e8 31f6ffff call sekachu.011d4340 + * 011d4d0f |. e8 6cf8ffff call sekachu.011d4580 + * 011d4d14 |. 8985 64f3ffff mov dword ptr ss:[ebp-0xc9c],eax + * 011d4d1a |. 8a03 mov al,byte ptr ds:[ebx] + * 011d4d1c |. 898d 90f3ffff mov dword ptr ss:[ebp-0xc70],ecx + * 011d4d22 |. 898d 14f4ffff mov dword ptr ss:[ebp-0xbec],ecx + * 011d4d28 |. 898d 38f4ffff mov dword ptr ss:[ebp-0xbc8],ecx + * 011d4d2e |. 8d71 01 lea esi,dword ptr ds:[ecx+0x1] + * 011d4d31 |. 3c 20 cmp al,0x20 ; jichi: pattern starts + * 011d4d33 |. 7d 75 jge short sekachu.011d4daa + * 011d4d35 |. 0fbec0 movsx eax,al + * 011d4d38 |. 83c0 fe add eax,-0x2 ; switch (cases 2..8) + * 011d4d3b |. 83f8 06 cmp eax,0x6 + * 011d4d3e |. 77 6a ja short sekachu.011d4daa + * 011d4d40 |. ff2485 38691d0>jmp dword ptr ds:[eax*4+0x11d6938] + * + * 蒼の彼方 体験版 (8/6/2014) + * 01312cce cc int3 ; jichi: reladdr = 0x32cd0 + * 01312ccf cc int3 + * 01312cd0 $ 55 push ebp + * 01312cd1 . 8bec mov ebp,esp + * 01312cd3 . 83e4 f8 and esp,0xfffffff8 + * 01312cd6 . 6a ff push -0x1 + * 01312cd8 . 68 86583a01 push 蒼の彼方.013a5886 + * 01312cdd . 64:a1 00000000 mov eax,dword ptr fs:[0] + * 01312ce3 . 50 push eax + * 01312ce4 . 81ec 38090000 sub esp,0x938 + * 01312cea . a1 24673c01 mov eax,dword ptr ds:[0x13c6724] + * 01312cef . 33c4 xor eax,esp + * 01312cf1 . 898424 3009000>mov dword ptr ss:[esp+0x930],eax + * 01312cf8 . 53 push ebx + * 01312cf9 . 56 push esi + * 01312cfa . 57 push edi + * 01312cfb . a1 24673c01 mov eax,dword ptr ds:[0x13c6724] + * 01312d00 . 33c4 xor eax,esp + * 01312d02 . 50 push eax + * 01312d03 . 8d8424 4809000>lea eax,dword ptr ss:[esp+0x948] + * 01312d0a . 64:a3 00000000 mov dword ptr fs:[0],eax + * 01312d10 . 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 01312d13 . 8b7d 0c mov edi,dword ptr ss:[ebp+0xc] + * 01312d16 . 8b5d 30 mov ebx,dword ptr ss:[ebp+0x30] + * 01312d19 . 898424 8800000>mov dword ptr ss:[esp+0x88],eax + * 01312d20 . 8b45 14 mov eax,dword ptr ss:[ebp+0x14] + * 01312d23 . 898c24 8c00000>mov dword ptr ss:[esp+0x8c],ecx + * 01312d2a . 8b0d a8734a01 mov ecx,dword ptr ds:[0x14a73a8] + * 01312d30 . 894424 4c mov dword ptr ss:[esp+0x4c],eax + * 01312d34 . 899424 bc00000>mov dword ptr ss:[esp+0xbc],edx + * 01312d3b . 8b55 20 mov edx,dword ptr ss:[ebp+0x20] + * 01312d3e . 51 push ecx ; /arg1 => 00000000 + * 01312d3f . 8d8424 0c02000>lea eax,dword ptr ss:[esp+0x20c] ; | + * 01312d46 . 897c24 34 mov dword ptr ss:[esp+0x34],edi ; | + * 01312d4a . 899c24 8800000>mov dword ptr ss:[esp+0x88],ebx ; | + * 01312d51 . e8 ca59fdff call 蒼の彼方.012e8720 ; \蒼の彼方.012e8720 + * 01312d56 . 33c9 xor ecx,ecx + * 01312d58 . 898424 f400000>mov dword ptr ss:[esp+0xf4],eax + * 01312d5f . 3bc1 cmp eax,ecx + * 01312d61 . 0f84 391b0000 je 蒼の彼方.013148a0 + * 01312d67 . e8 54280000 call 蒼の彼方.013155c0 + * 01312d6c . e8 7f2a0000 call 蒼の彼方.013157f0 + * 01312d71 . 898424 f800000>mov dword ptr ss:[esp+0xf8],eax + * 01312d78 . 8a07 mov al,byte ptr ds:[edi] + * 01312d7a . 898c24 c400000>mov dword ptr ss:[esp+0xc4],ecx + * 01312d81 . 894c24 2c mov dword ptr ss:[esp+0x2c],ecx + * 01312d85 . 894c24 1c mov dword ptr ss:[esp+0x1c],ecx + * 01312d89 . b9 01000000 mov ecx,0x1 + * 01312d8e . 3c 20 cmp al,0x20 ; jichi: pattern starts + * 01312d90 . 7d 58 jge short 蒼の彼方.01312dea + * 01312d92 . 0fbec0 movsx eax,al + * 01312d95 . 83c0 fe add eax,-0x2 ; switch (cases 2..8) + * 01312d98 . 83f8 06 cmp eax,0x6 + * 01312d9b . 77 4d ja short 蒼の彼方.01312dea + * 01312d9d . ff2485 c448310>jmp dword ptr ds:[eax*4+0x13148c4] + * 01312da4 > 898c24 c400000>mov dword ptr ss:[esp+0xc4],ecx ; case 2 of switch 01312d95 + * 01312dab . 03f9 add edi,ecx + * 01312dad . eb 37 jmp short 蒼の彼方.01312de6 + * 01312daf > 894c24 2c mov dword ptr ss:[esp+0x2c],ecx ; case 3 of switch 01312d95 + * 01312db3 . 03f9 add edi,ecx + * 01312db5 . eb 2f jmp short 蒼の彼方.01312de6 + * 01312db7 > ba e0103b01 mov edx,蒼の彼方.013b10e0 ; case 4 of switch 01312d95 + * 01312dbc . eb 1a jmp short 蒼の彼方.01312dd8 + * 01312dbe > ba e4103b01 mov edx,蒼の彼方.013b10e4 ; case 5 of switch 01312d95 + * 01312dc3 . eb 13 jmp short 蒼の彼方.01312dd8 + * 01312dc5 > ba e8103b01 mov edx,蒼の彼方.013b10e8 ; case 6 of switch 01312d95 + * 01312dca . eb 0c jmp short 蒼の彼方.01312dd8 + * 01312dcc > ba ec103b01 mov edx,蒼の彼方.013b10ec ; case 7 of switch 01312d95 + * 01312dd1 . eb 05 jmp short 蒼の彼方.01312dd8 + * 01312dd3 > ba f0103b01 mov edx,蒼の彼方.013b10f0 ; case 8 of switch 01312d95 + * 01312dd8 > 8d7424 14 lea esi,dword ptr ss:[esp+0x14] + * 01312ddc . 894c24 1c mov dword ptr ss:[esp+0x1c],ecx + * 01312de0 . e8 1b8dffff call 蒼の彼方.0130bb00 + * 01312de5 . 47 inc edi + * 01312de6 > 897c24 30 mov dword ptr ss:[esp+0x30],edi + * 01312dea > 8d8424 0802000>lea eax,dword ptr ss:[esp+0x208] ; default case of switch 01312d95 + * 01312df1 . e8 ba1b0000 call 蒼の彼方.013149b0 + * 01312df6 . 837d 10 00 cmp dword ptr ss:[ebp+0x10],0x0 + * 01312dfa . 8bb424 2802000>mov esi,dword ptr ss:[esp+0x228] + * 01312e01 . 894424 5c mov dword ptr ss:[esp+0x5c],eax + * 01312e05 . 74 12 je short 蒼の彼方.01312e19 + * 01312e07 . 56 push esi ; /arg1 + * 01312e08 . e8 c31b0000 call 蒼の彼方.013149d0 ; \蒼の彼方.013149d0 + * 01312e0d . 83c4 04 add esp,0x4 + * 01312e10 . 898424 c000000>mov dword ptr ss:[esp+0xc0],eax + * 01312e17 . eb 0b jmp short 蒼の彼方.01312e24 + * 01312e19 > c78424 c000000>mov dword ptr ss:[esp+0xc0],0x0 + * 01312e24 > 8b4b 04 mov ecx,dword ptr ds:[ebx+0x4] + * 01312e27 . 0fafce imul ecx,esi + * 01312e2a . b8 1f85eb51 mov eax,0x51eb851f + * 01312e2f . f7e9 imul ecx + * 01312e31 . c1fa 05 sar edx,0x5 + * 01312e34 . 8bca mov ecx,edx + * 01312e36 . c1e9 1f shr ecx,0x1f + * 01312e39 . 03ca add ecx,edx + * 01312e3b . 894c24 70 mov dword ptr ss:[esp+0x70],ecx + * 01312e3f . 85c9 test ecx,ecx + * 01312e41 . 7f 09 jg short 蒼の彼方.01312e4c + * 01312e43 . b9 01000000 mov ecx,0x1 + * 01312e48 . 894c24 70 mov dword ptr ss:[esp+0x70],ecx + * 01312e4c > 8b53 08 mov edx,dword ptr ds:[ebx+0x8] + * 01312e4f . 0fafd6 imul edx,esi + * 01312e52 . b8 1f85eb51 mov eax,0x51eb851f + * 01312e57 . f7ea imul edx + * 01312e59 . c1fa 05 sar edx,0x5 + * 01312e5c . 8bc2 mov eax,edx + * 01312e5e . c1e8 1f shr eax,0x1f + * 01312e61 . 03c2 add eax,edx + * 01312e63 . 894424 78 mov dword ptr ss:[esp+0x78],eax + * 01312e67 . 85c0 test eax,eax + * 01312e69 . 7f 09 jg short 蒼の彼方.01312e74 + * 01312e6b . b8 01000000 mov eax,0x1 + * 01312e70 . 894424 78 mov dword ptr ss:[esp+0x78],eax + * 01312e74 > 33d2 xor edx,edx + * 01312e76 . 895424 64 mov dword ptr ss:[esp+0x64],edx + * 01312e7a . 895424 6c mov dword ptr ss:[esp+0x6c],edx + * 01312e7e . 8b13 mov edx,dword ptr ds:[ebx] + * 01312e80 . 4a dec edx ; switch (cases 1..2) + * 01312e81 . 74 0e je short 蒼の彼方.01312e91 + * 01312e83 . 4a dec edx + * 01312e84 . 75 13 jnz short 蒼の彼方.01312e99 + * 01312e86 . 8d1409 lea edx,dword ptr ds:[ecx+ecx] ; case 2 of switch 01312e80 + * 01312e89 . 895424 64 mov dword ptr ss:[esp+0x64],edx + * 01312e8d . 03c0 add eax,eax + * 01312e8f . eb 04 jmp short 蒼の彼方.01312e95 + * 01312e91 > 894c24 64 mov dword ptr ss:[esp+0x64],ecx ; case 1 of switch 01312e80 + * 01312e95 > 894424 6c mov dword ptr ss:[esp+0x6c],eax + * 01312e99 > 8b9c24 3802000>mov ebx,dword ptr ss:[esp+0x238] ; default case of switch 01312e80 + * 01312ea0 . 8bc3 mov eax,ebx + * 01312ea2 . e8 d98bffff call 蒼の彼方.0130ba80 + * 01312ea7 . 8bc8 mov ecx,eax + * 01312ea9 . 8bc3 mov eax,ebx + * 01312eab . e8 e08bffff call 蒼の彼方.0130ba90 + * 01312eb0 . 6a 01 push 0x1 ; /arg1 = 00000001 + * 01312eb2 . 8bd0 mov edx,eax ; | + * 01312eb4 . 8db424 1c01000>lea esi,dword ptr ss:[esp+0x11c] ; | + * 01312ebb . e8 3056fdff call 蒼の彼方.012e84f0 ; \蒼の彼方.012e84f0 + * 01312ec0 . 8bc7 mov eax,edi + * 01312ec2 . 83c4 04 add esp,0x4 + * 01312ec5 . 8d70 01 lea esi,dword ptr ds:[eax+0x1] + * 01312ec8 > 8a08 mov cl,byte ptr ds:[eax] + * 01312eca . 40 inc eax + * 01312ecb . 84c9 test cl,cl + * 01312ecd .^75 f9 jnz short 蒼の彼方.01312ec8 + * 01312ecf . 2bc6 sub eax,esi + * 01312ed1 . 40 inc eax + * 01312ed2 . 50 push eax + * 01312ed3 . e8 e74c0600 call 蒼の彼方.01377bbf + * 01312ed8 . 33f6 xor esi,esi + * 01312eda . 83c4 04 add esp,0x4 + * + * 1/1/2016 + * コドモノアソビ trial + * + * 00A64259 CC INT3 + * 00A6425A CC INT3 + * 00A6425B CC INT3 + * 00A6425C CC INT3 + * 00A6425D CC INT3 + * 00A6425E CC INT3 + * 00A6425F CC INT3 + * 00A64260 55 PUSH EBP + * 00A64261 8BEC MOV EBP,ESP + * 00A64263 83E4 F8 AND ESP,0xFFFFFFF8 + * 00A64266 6A FF PUSH -0x1 + * 00A64268 68 D610B000 PUSH .00B010D6 + * 00A6426D 64:A1 00000000 MOV EAX,DWORD PTR FS:[0] + * 00A64273 50 PUSH EAX + * 00A64274 81EC 40090000 SUB ESP,0x940 + * 00A6427A A1 2417B200 MOV EAX,DWORD PTR DS:[0xB21724] + * 00A6427F 33C4 XOR EAX,ESP + * 00A64281 898424 38090000 MOV DWORD PTR SS:[ESP+0x938],EAX + * 00A64288 53 PUSH EBX + * 00A64289 56 PUSH ESI + * 00A6428A 57 PUSH EDI + * 00A6428B A1 2417B200 MOV EAX,DWORD PTR DS:[0xB21724] + * 00A64290 33C4 XOR EAX,ESP + * 00A64292 50 PUSH EAX + * 00A64293 8D8424 50090000 LEA EAX,DWORD PTR SS:[ESP+0x950] + * 00A6429A 64:A3 00000000 MOV DWORD PTR FS:[0],EAX + * 00A642A0 8B45 08 MOV EAX,DWORD PTR SS:[EBP+0x8] + * 00A642A3 8B7D 0C MOV EDI,DWORD PTR SS:[EBP+0xC] + * 00A642A6 8B5D 30 MOV EBX,DWORD PTR SS:[EBP+0x30] + * 00A642A9 894424 50 MOV DWORD PTR SS:[ESP+0x50],EAX + * 00A642AD 8B45 14 MOV EAX,DWORD PTR SS:[EBP+0x14] + * 00A642B0 894C24 74 MOV DWORD PTR SS:[ESP+0x74],ECX + * 00A642B4 8B0D A024B800 MOV ECX,DWORD PTR DS:[0xB824A0] + * 00A642BA 894424 4C MOV DWORD PTR SS:[ESP+0x4C],EAX + * 00A642BE 899424 B8000000 MOV DWORD PTR SS:[ESP+0xB8],EDX + * 00A642C5 8B55 20 MOV EDX,DWORD PTR SS:[EBP+0x20] + * 00A642C8 51 PUSH ECX + * 00A642C9 8D8424 14020000 LEA EAX,DWORD PTR SS:[ESP+0x214] + * 00A642D0 897C24 2C MOV DWORD PTR SS:[ESP+0x2C],EDI + * 00A642D4 899C24 88000000 MOV DWORD PTR SS:[ESP+0x88],EBX + * 00A642DB E8 504CFDFF CALL .00A38F30 + * 00A642E0 33C9 XOR ECX,ECX + * 00A642E2 898424 F8000000 MOV DWORD PTR SS:[ESP+0xF8],EAX + * 00A642E9 3BC1 CMP EAX,ECX + * 00A642EB 0F84 391C0000 JE .00A65F2A + * 00A642F1 E8 FA2A0000 CALL .00A66DF0 + * 00A642F6 E8 252D0000 CALL .00A67020 + * 00A642FB 898424 FC000000 MOV DWORD PTR SS:[ESP+0xFC],EAX + * 00A64302 8A07 MOV AL,BYTE PTR DS:[EDI] + * 00A64304 898C24 CC000000 MOV DWORD PTR SS:[ESP+0xCC],ECX + * 00A6430B 894C24 30 MOV DWORD PTR SS:[ESP+0x30],ECX + * 00A6430F 894C24 1C MOV DWORD PTR SS:[ESP+0x1C],ECX + * 00A64313 B9 01000000 MOV ECX,0x1 + * 00A64318 3C 20 CMP AL,0x20 ; jichi: pattern found here + * 00A6431A 7D 58 JGE SHORT .00A64374 + * 00A6431C 0FBEC0 MOVSX EAX,AL + * 00A6431F 83C0 FE ADD EAX,-0x2 + * 00A64322 83F8 06 CMP EAX,0x6 + * 00A64325 77 4D JA SHORT .00A64374 + * 00A64327 FF2485 505FA600 JMP DWORD PTR DS:[EAX*4+0xA65F50] + * 00A6432E 898C24 CC000000 MOV DWORD PTR SS:[ESP+0xCC],ECX + * 00A64335 03F9 ADD EDI,ECX + * 00A64337 EB 37 JMP SHORT .00A64370 + * 00A64339 894C24 30 MOV DWORD PTR SS:[ESP+0x30],ECX + * 00A6433D 03F9 ADD EDI,ECX + * 00A6433F EB 2F JMP SHORT .00A64370 + * 00A64341 BA E0C1B000 MOV EDX,.00B0C1E0 + * 00A64346 EB 1A JMP SHORT .00A64362 + * 00A64348 BA E4C1B000 MOV EDX,.00B0C1E4 + * 00A6434D EB 13 JMP SHORT .00A64362 + * 00A6434F BA E8C1B000 MOV EDX,.00B0C1E8 + * 00A64354 EB 0C JMP SHORT .00A64362 + * 00A64356 BA ECC1B000 MOV EDX,.00B0C1EC + * 00A6435B EB 05 JMP SHORT .00A64362 + * 00A6435D BA F0C1B000 MOV EDX,.00B0C1F0 + * 00A64362 8D7424 14 LEA ESI,DWORD PTR SS:[ESP+0x14] + * 00A64366 894C24 1C MOV DWORD PTR SS:[ESP+0x1C],ECX + * 00A6436A E8 A196FFFF CALL .00A5DA10 + * 00A6436F 47 INC EDI + * 00A64370 897C24 28 MOV DWORD PTR SS:[ESP+0x28],EDI + * 00A64374 8D8424 10020000 LEA EAX,DWORD PTR SS:[ESP+0x210] + * 00A6437B E8 C01C0000 CALL .00A66040 + * 00A64380 837D 10 00 CMP DWORD PTR SS:[EBP+0x10],0x0 + * 00A64384 8BB424 30020000 MOV ESI,DWORD PTR SS:[ESP+0x230] + * 00A6438B 894424 60 MOV DWORD PTR SS:[ESP+0x60],EAX + * 00A6438F 74 12 JE SHORT .00A643A3 + * 00A64391 56 PUSH ESI + * 00A64392 E8 C91C0000 CALL .00A66060 + * 00A64397 83C4 04 ADD ESP,0x4 + * 00A6439A 898424 C4000000 MOV DWORD PTR SS:[ESP+0xC4],EAX + * 00A643A1 EB 0B JMP SHORT .00A643AE + * 00A643A3 C78424 C4000000 >MOV DWORD PTR SS:[ESP+0xC4],0x0 + * 00A643AE 8B4B 04 MOV ECX,DWORD PTR DS:[EBX+0x4] + * 00A643B1 0FAFCE IMUL ECX,ESI + * 00A643B4 B8 1F85EB51 MOV EAX,0x51EB851F + * 00A643B9 F7E9 IMUL ECX + * 00A643BB C1FA 05 SAR EDX,0x5 + * 00A643BE 8BCA MOV ECX,EDX + * 00A643C0 C1E9 1F SHR ECX,0x1F + * 00A643C3 03CA ADD ECX,EDX + * 00A643C5 898C24 94000000 MOV DWORD PTR SS:[ESP+0x94],ECX + * 00A643CC 85C9 TEST ECX,ECX + * 00A643D0 B9 01000000 MOV ECX,0x1 + * ... + */ +//static inline size_t _bgistrlen(LPCSTR text) +//{ +// size_t r = ::strlen(text); +// if (r >=2 && *(WORD *)(text + r - 2) == 0xa581) // remove trailing ▼ = \x81\xa5 +// r -= 2; +// return r; +//} +// +//static void SpecialHookBGI2(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +//{ +// LPCSTR text = (LPCSTR)*(DWORD *)(esp_base + hp->offset); +// if (text) { +// *data = (DWORD)text; +// *len = _bgistrlen(text); +// } +//} + +bool InsertBGI2Hook() +{ + const BYTE bytes[] = { + 0x3c, 0x20, // 011d4d31 |. 3c 20 cmp al,0x20 + 0x7d, XX, // 011d4d33 |. 7d 75 jge short sekachu.011d4daa ; jichi: 0x75 or 0x58 + 0x0f,0xbe,0xc0, // 011d4d35 |. 0fbec0 movsx eax,al + 0x83,0xc0, 0xfe, // 011d4d38 |. 83c0 fe add eax,-0x2 ; switch (cases 2..8) + 0x83,0xf8, 0x06 // 011d4d3b |. 83f8 06 cmp eax,0x6 + // The following code does not exist in newer BGI games after 蒼の彼方 + //0x77, 0x6a // 011d4d3e |. 77 6a ja short sekachu.011d4daa + }; + + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::matchBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //GROWL_DWORD(reladdr); + if (!addr) { + ConsoleOutput("vnreng:BGI2: pattern not found"); + return false; + } + + DWORD funaddr = MemDbg::findEnclosingAlignedFunction(addr, 0x100); // range is around 177 ~ 190 + + enum : BYTE { push_ebp = 0x55 }; // 011d4c80 /$ 55 push ebp + if (!funaddr || *(BYTE *)funaddr != push_ebp) { + ConsoleOutput("vnreng:BGI2: pattern found but the function offset is invalid"); + return false; + } + + HookParam hp = {}; + switch (funaddr - addr) { + // for old BGI2 game, text is arg3 + case 0x34c80 - 0x34d31: + hp.offset = 4 * 3; + break; + // for new BGI2 game since 蒼の彼方 (2014/08), text is in arg2 + case 0x01312cd0 - 0x01312d8e: + // For newer BGI2 game since コドモノアソビ (2015/11) + case 0x00A64260 - 0x00A64318: + hp.offset = 4 * 2; + break; + default: + ConsoleOutput("vnreng:BGI2: function found bug unrecognized hook offset"); + return false; + } + hp.address = funaddr; + + // jichi 5/12/2014: Using split could distinguish name and choices. But the signature might become unstable + hp.type = USING_STRING|USING_SPLIT; + hp.split = 4 * 8; // pseudo arg8 + //hp.split = -0x18; + + //GROWL_DWORD2(hp.address, module_base_); + + ConsoleOutput("vnreng: INSERT BGI2"); + NewHook(hp, "BGI2"); + + // Disable TextOutA, which is cached and hence missing characters. + ConsoleOutput("vnreng:BGI2: disable GDI hooks"); + DisableGDIHooks(); + return true; +} + +#if 0 +/** + * jichi 1/31/2014: Add a new BGI hook + * See: http://www.hongfire.com/forum/showthread.php/36807-AGTH-text-extraction-tool-for-games-translation/page702 + * See: http://www.hongfire.com/forum/showthread.php/36807-AGTH-text-extraction-tool-for-games-translation/page716 + * + * Issue: This hook has floating split char + * + * [ぷちけろ] コトバの消えた日 �忁�で裸にする純�調教~体験版 + * /HS-1C:-4@68E56:BGI.exe + * - addr: 429654 (0x68e56) + * - module: 3927275266 (0xea157702) + * - off: 4294967264 = 0xffffffe0 = -0x20 + * - split: 4294967288 = 0xfffffff8 = -0x8 + * - type: 81 = 0x51 + * + * 00e88e3d cc int3 + * 00e88e3e cc int3 + * 00e88e3f cc int3 + * 00e88e40 /. 55 push ebp + * 00e88e41 |. 8bec mov ebp,esp + * 00e88e43 |. 56 push esi + * 00e88e44 |. 57 push edi + * 00e88e45 |. 8b7d 08 mov edi,dword ptr ss:[ebp+0x8] + * 00e88e48 |. 57 push edi + * 00e88e49 |. e8 c28a0100 call bgi.00ea1910 + * 00e88e4e |. 57 push edi ; |arg1 + * 00e88e4f |. 8bf0 mov esi,eax ; | + * 00e88e51 |. e8 ba8a0100 call bgi.00ea1910 ; \bgi.00ea1910 + * 00e88e56 |. 83c4 08 add esp,0x8 ; jichi: hook here + * 00e88e59 |. 2bc6 sub eax,esi + * 00e88e5b |. eb 03 jmp short bgi.00e88e60 + * 00e88e5d | 8d49 00 lea ecx,dword ptr ds:[ecx] + * 00e88e60 |> 8a0e /mov cl,byte ptr ds:[esi] + * 00e88e62 |. 880c30 |mov byte ptr ds:[eax+esi],cl + * 00e88e65 |. 46 |inc esi + * 00e88e66 |. 84c9 |test cl,cl + * 00e88e68 |.^75 f6 \jnz short bgi.00e88e60 + * 00e88e6a |. 5f pop edi + * 00e88e6b |. 33c0 xor eax,eax + * 00e88e6d |. 5e pop esi + * 00e88e6e |. 5d pop ebp + * 00e88e6f \. c3 retn + */ +bool InsertBGI3Hook() +{ + const BYTE bytes[] = { + 0x83,0xc4, 0x08,// 00e88e56 |. 83c4 08 add esp,0x8 ; hook here + 0x2b,0xc6, // 00e88e59 |. 2bc6 sub eax,esi + 0xeb, 0x03, // 00e88e5b |. eb 03 jmp short bgi.00e88e60 + 0x8d,0x49, 0x00,// 00e88e5d | 8d49 00 lea ecx,dword ptr ds:[ecx] + 0x8a,0x0e, // 00e88e60 |> 8a0e /mov cl,byte ptr ds:[esi] + 0x88,0x0c,0x30, // 00e88e62 |. 880c30 |mov byte ptr ds:[eax+esi],cl + 0x46, // 00e88e65 |. 46 |inc esi + 0x84,0xc9, // 00e88e66 |. 84c9 |test cl,cl + 0x75, 0xf6 // 00e88e68 |.^75 f6 \jnz short bgi.00e88e60 + //0x5f, // 00e88e6a |. 5f pop edi + //0x33,0xc0, // 00e88e6b |. 33c0 xor eax,eax + //0x5e, // 00e88e6d |. 5e pop esi + //0x5d, // 00e88e6e |. 5d pop ebp + //0xc3 // 00e88e6f \. c3 retn + }; + //enum { addr_offset = 0 }; + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //reladdr = 0x68e56; + if (!addr) { + ConsoleOutput("vnreng:BGI3: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.type = USING_STRING|USING_SPLIT; + hp.offset = -0x20; + hp.split = -0x8; + hp.address = addr; + + //GROWL_DWORD2(hp.address, module_base_); + + ConsoleOutput("vnreng: INSERT BGI3"); + NewHook(hp, "BGI3"); + return true; +} +#endif // 0 +} // unnamed + +// jichi 5/12/2014: BGI1 and BGI2 game can co-exist, such as 世界と世界の真ん中で +// BGI1 can exist in both old and new games +// BGI2 only exist in new games +// Insert BGI2 first. +bool InsertBGIHook() +{ return InsertBGI2Hook() || InsertBGI1Hook(); } + +/******************************************************************************************** +Reallive hook: + Process name is reallive.exe or reallive*.exe. + + Technique to find Reallive hook is quite different from 2 above. + Usually Reallive engine has a font caching issue. This time we wait + until the first call to GetGlyphOutlineA. Reallive engine usually set + up stack frames so we can just refer to EBP to find function entry. + +********************************************************************************************/ +/** jichi 5/13/2015 + * RealLive does not work for 水着少女と媚薬アイス from 裸足少女 + * 012da80f cc int3 + * 012da810 55 push ebp ; jichi: change to hook here + * 012da811 8bec mov ebp,esp + * 012da813 83ec 10 sub esp,0x10 ; jichi: hook here by default + * 012da816 53 push ebx + * 012da817 56 push esi + * 012da818 57 push edi + * 012da819 8b7d 18 mov edi,dword ptr ss:[ebp+0x18] + * 012da81c 81ff 5c810000 cmp edi,0x815c + * 012da822 75 0a jnz short reallive.012da82e + * 012da824 c745 18 9f840000 mov dword ptr ss:[ebp+0x18],0x849f + * 012da82b 8b7d 18 mov edi,dword ptr ss:[ebp+0x18] + * 012da82e b8 9041e301 mov eax,reallive.01e34190 + * 012da833 b9 18a49001 mov ecx,reallive.0190a418 + * 012da838 e8 a38d0000 call reallive.012e35e0 + * 012da83d 85c0 test eax,eax + * 012da83f 74 14 je short reallive.012da855 + * 012da841 e8 6addffff call reallive.012d85b0 + * 012da846 ba 9041e301 mov edx,reallive.01e34190 + * 012da84b b8 18a49001 mov eax,reallive.0190a418 + * 012da850 e8 ab7c0000 call reallive.012e2500 + * 012da855 8d45 f0 lea eax,dword ptr ss:[ebp-0x10] + * 012da858 50 push eax + * 012da859 8d4d f4 lea ecx,dword ptr ss:[ebp-0xc] + * 012da85c 51 push ecx + * 012da85d 8d55 fc lea edx,dword ptr ss:[ebp-0x4] + * 012da860 52 push edx + * 012da861 8d45 f8 lea eax,dword ptr ss:[ebp-0x8] + * 012da864 50 push eax + * 012da865 8bc7 mov eax,edi + * 012da867 e8 54dfffff call reallive.012d87c0 + * 012da86c 8bf0 mov esi,eax + * 012da86e 83c4 10 add esp,0x10 + * 012da871 85f6 test esi,esi + * 012da873 75 4b jnz short reallive.012da8c0 + * 012da875 8d4d f4 lea ecx,dword ptr ss:[ebp-0xc] + * 012da878 51 push ecx + * 012da879 57 push edi + * 012da87a 8d4d f0 lea ecx,dword ptr ss:[ebp-0x10] + * 012da87d e8 cef0ffff call reallive.012d9950 + * 012da882 8bf0 mov esi,eax + * 012da884 83c4 08 add esp,0x8 + * 012da887 85f6 test esi,esi + */ +static bool InsertRealliveDynamicHook(LPVOID addr, DWORD frame, DWORD stack) +{ + if (addr != ::GetGlyphOutlineA) + return false; + // jichi 5/13/2015: Find the enclosing caller of GetGlyphOutlineA + if (DWORD i = frame) { + i = *(DWORD *)(i + 4); + for (DWORD j = i; j > i - 0x100; j--) + if (*(WORD *)j == 0xec83) { // jichi 7/26/2014: function starts + // 012da80f cc int3 + // 012da810 55 push ebp ; jichi: change to hook here + // 012da811 8bec mov ebp,esp + // 012da813 83ec 10 sub esp,0x10 ; jichi: hook here by default + if (*(DWORD *)(j-3) == 0x83ec8b55) + j -= 3; + + HookParam hp = {}; + hp.address = j; + hp.offset = 0x14; + hp.split = pusha_esp_off - 4; // -0x18 + hp.length_offset = 1; + hp.type = BIG_ENDIAN|USING_SPLIT; + //GROWL_DWORD(hp.address); + NewHook(hp, "RealLive"); + //RegisterEngineType(ENGINE_REALLIVE); + ConsoleOutput("vnreng:RealLive: disable GDI hooks"); + DisableGDIHooks(); + return true; + } + } + return true; // jichi 12/25/2013: return true +} +void InsertRealliveHook() +{ + //ConsoleOutput("Probably Reallive. Wait for text."); + ConsoleOutput("vnreng: TRIGGER Reallive"); + trigger_fun_ = InsertRealliveDynamicHook; + SwitchTrigger(true); +} + +namespace { // unnamed + +/** + * jichi 8/17/2013: SiglusEngine from siglusengine.exe + * The old hook does not work for new games. + * The new hook cannot recognize character names. + * Insert old first. As the pattern could also be found in the old engine. + */ + +/** jichi 10/25/2014: new SiglusEngine3 that can extract character name + * + * Sample game: リア兂�ラスメイト孕ませ催� -- /HW-4@F67DC:SiglusEngine.exe + * The character is in [edx+ecx*2]. Text in edx, and offset in ecx. + * + * 002667be cc int3 + * 002667bf cc int3 + * 002667c0 55 push ebp ; jichi: hook here + * 002667c1 8bec mov ebp,esp + * 002667c3 8bd1 mov edx,ecx + * 002667c5 8b4d 0c mov ecx,dword ptr ss:[ebp+0xc] + * 002667c8 83f9 01 cmp ecx,0x1 + * 002667cb 75 17 jnz short .002667e4 + * 002667cd 837a 14 08 cmp dword ptr ds:[edx+0x14],0x8 + * 002667d1 72 02 jb short .002667d5 + * 002667d3 8b12 mov edx,dword ptr ds:[edx] + * 002667d5 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + * 002667d8 66:8b45 10 mov ax,word ptr ss:[ebp+0x10] + * 002667dc 66:89044a mov word ptr ds:[edx+ecx*2],ax ; jichi: wchar_t is in ax + * 002667e0 5d pop ebp + * 002667e1 c2 0c00 retn 0xc + * 002667e4 837a 14 08 cmp dword ptr ds:[edx+0x14],0x8 + * 002667e8 72 02 jb short .002667ec + * 002667ea 8b12 mov edx,dword ptr ds:[edx] + * 002667ec 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 002667ef 57 push edi + * 002667f0 8d3c42 lea edi,dword ptr ds:[edx+eax*2] + * 002667f3 85c9 test ecx,ecx + * 002667f5 74 16 je short .0026680d + * 002667f7 8b45 10 mov eax,dword ptr ss:[ebp+0x10] + * 002667fa 0fb7d0 movzx edx,ax + * 002667fd 8bc2 mov eax,edx + * 002667ff c1e2 10 shl edx,0x10 + * 00266802 0bc2 or eax,edx + * 00266804 d1e9 shr ecx,1 + * 00266806 f3:ab rep stos dword ptr es:[edi] + * 00266808 13c9 adc ecx,ecx + * 0026680a 66:f3:ab rep stos word ptr es:[edi] + * 0026680d 5f pop edi + * 0026680e 5d pop ebp + * 0026680f c2 0c00 retn 0xc + * 00266812 cc int3 + * 00266813 cc int3 + * + * Stack when enter function call: + * 04cee270 00266870 return to .00266870 from .002667c0 + * 04cee274 00000002 jichi: arg1, ecx + * 04cee278 00000001 jichi: arg2, always 1 + * 04cee27c 000050ac jichi: arg3, wchar_t + * 04cee280 04cee4fc jichi: text address + * 04cee284 0ead055c arg5 + * 04cee288 0ead0568 arg6, last text when arg6 = arg5 = 2 + * 04cee28c /04cee2c0 + * 04cee290 |00266969 return to .00266969 from .00266820 + * 04cee294 |00000001 + * 04cee298 |000050ac + * 04cee29c |e1466fb2 + * 04cee2a0 |072f45f0 + * + * Target address (edx) is at [[ecx]] when enter function. + */ + +// jichi: 8/17/2013: Change return type to bool +bool InsertSiglus3Hook() +{ + const BYTE bytes[] = { + 0x8b,0x12, // 002667d3 8b12 mov edx,dword ptr ds:[edx] + 0x8b,0x4d, 0x08, // 002667d5 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + 0x66,0x8b,0x45, 0x10, // 002667d8 66:8b45 10 mov ax,word ptr ss:[ebp+0x10] + 0x66,0x89,0x04,0x4a // 002667dc 66:89044a mov word ptr ds:[edx+ecx*2],ax ; jichi: wchar_t in ax + // 002667e0 5d pop ebp + // 002667e1 c2 0c00 retn 0xc + }; + enum { addr_offset = sizeof(bytes) - 4 }; + ULONG range = max(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + if (!addr) { + //ConsoleOutput("Unknown SiglusEngine"); + ConsoleOutput("vnreng:Siglus3: pattern not found"); + return false; + } + + //addr = MemDbg::findEnclosingAlignedFunction(addr, 50); // 0x002667dc - 0x002667c0 = 28 + //if (!addr) { + // ConsoleOutput("vnreng:Siglus3: enclosing function not found"); + // return false; + //} + + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.offset = pusha_eax_off - 4; + hp.type = USING_UNICODE; + hp.length_offset = 1; + //hp.text_fun = SpecialHookSiglus3; + + ConsoleOutput("vnreng: INSERT Siglus3"); + NewHook(hp, "SiglusEngine3"); + + ConsoleOutput("vnreng:Siglus3: disable GDI hooks"); + DisableGDIHooks(); + return true; +} + +/** SiglusEngine4 5/23/2015 + * Sample game: AngleBeats trial + * Alternative ATcode from EGDB: + * UNIKOFILTER(30),FORCEFONT(5),HOOK(SiglusEngine.exe!0x0018CF39,TRANS(EAX,UNICODE,SMSTR,ADDNULL),RETNPOS(SOURCE)) + * Text address is [eax] + * + * 0042CEFD CC INT3 + * 0042CEFE CC INT3 + * 0042CEFF CC INT3 + * 0042CF00 55 PUSH EBP + * 0042CF01 8BEC MOV EBP,ESP + * 0042CF03 51 PUSH ECX + * 0042CF04 A1 005E8A00 MOV EAX,DWORD PTR DS:[0x8A5E00] + * 0042CF09 53 PUSH EBX + * 0042CF0A 56 PUSH ESI + * 0042CF0B 57 PUSH EDI + * 0042CF0C 8B40 10 MOV EAX,DWORD PTR DS:[EAX+0x10] + * 0042CF0F 8BF9 MOV EDI,ECX + * 0042CF11 33C9 XOR ECX,ECX + * 0042CF13 C745 FC 00000000 MOV DWORD PTR SS:[EBP-0x4],0x0 + * 0042CF1A 6A FF PUSH -0x1 + * 0042CF1C 51 PUSH ECX + * 0042CF1D 83E8 18 SUB EAX,0x18 + * 0042CF20 C747 14 07000000 MOV DWORD PTR DS:[EDI+0x14],0x7 + * 0042CF27 C747 10 00000000 MOV DWORD PTR DS:[EDI+0x10],0x0 + * 0042CF2E 66:890F MOV WORD PTR DS:[EDI],CX + * 0042CF31 8BCF MOV ECX,EDI + * 0042CF33 50 PUSH EAX + * 0042CF34 E8 E725F6FF CALL .0038F520 + * 0042CF39 8B1D 005E8A00 MOV EBX,DWORD PTR DS:[0x8A5E00] ; jichi: ATcode hooked here, text sometimes in eax sometimes address in eax, size in [eax+0x16] + * 0042CF3F 8B73 10 MOV ESI,DWORD PTR DS:[EBX+0x10] + * 0042CF42 837E FC 08 CMP DWORD PTR DS:[ESI-0x4],0x8 + * 0042CF46 72 0B JB SHORT .0042CF53 + * 0042CF48 FF76 E8 PUSH DWORD PTR DS:[ESI-0x18] + * 0042CF4B E8 EA131300 CALL .0055E33A + * 0042CF50 83C4 04 ADD ESP,0x4 + * 0042CF53 33C0 XOR EAX,EAX + * 0042CF55 C746 FC 07000000 MOV DWORD PTR DS:[ESI-0x4],0x7 + * 0042CF5C C746 F8 00000000 MOV DWORD PTR DS:[ESI-0x8],0x0 + * 0042CF63 66:8946 E8 MOV WORD PTR DS:[ESI-0x18],AX + * 0042CF67 8BC7 MOV EAX,EDI + * 0042CF69 8343 10 E8 ADD DWORD PTR DS:[EBX+0x10],-0x18 + * 0042CF6D 5F POP EDI + * 0042CF6E 5E POP ESI + * 0042CF6F 5B POP EBX + * 0042CF70 8BE5 MOV ESP,EBP + * 0042CF72 5D POP EBP + * 0042CF73 C3 RETN + * 0042CF74 CC INT3 + * 0042CF75 CC INT3 + * 0042CF76 CC INT3 + * 0042CF77 CC INT3 + */ +bool Siglus4Filter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + auto text = reinterpret_cast(data); + auto len = reinterpret_cast(size); + // Remove "NNLI" + //if (*len > 2 && ::all_ascii(text)) + // return false; + //if (*len == 2 && *text == L'N') + // return false; + WideStringFilter(text, len, L"NLI", 3); + // Replace 『�(300e, 300f) with 「�(300c,300d) + //WideCharReplacer(text, len, 0x300e, 0x300c); + //WideCharReplacer(text, len, 0x300f, 0x300d); + return true; +} +void SpecialHookSiglus4(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + //static uint64_t lastTextHash_; + DWORD eax = regof(eax, esp_base); // text + if (!eax || !*(const BYTE *)eax) // empty data + return; + DWORD size = *(DWORD *)(eax + 0x10); + if (!size) + return; + if (size < 8) + *data = eax; + else + *data = *(DWORD *)eax; + + // Skip all ascii characters + if (all_ascii((LPCWSTR)*data)) + return; + + // Avoid duplication + //LPCWSTR text = (LPCWSTR)*data; + //auto hash = hashstr(text); + //if (hash == lastTextHash_) + // return; + //lastTextHash_ = hash; + + *len = size * 2; // UTF-16 + DWORD s0 = retof(esp_base); // use stack[0] as split + if (s0 <= 0xff) // scenario text + *split = FIXED_SPLIT_VALUE; + else if (::IsBadReadPtr((LPCVOID)s0, 4)) + *split = s0; + else { + *split = *(DWORD *)s0; // This value is runtime dependent + if (*split == 0x54) + *split = FIXED_SPLIT_VALUE * 2; + } + *split += argof(1, esp_base); // plus stack[1] as split +} +bool InsertSiglus4Hook() +{ + const BYTE bytes[] = { + 0xc7,0x47, 0x14, 0x07,0x00,0x00,0x00, // 0042cf20 c747 14 07000000 mov dword ptr ds:[edi+0x14],0x7 + 0xc7,0x47, 0x10, 0x00,0x00,0x00,0x00, // 0042cf27 c747 10 00000000 mov dword ptr ds:[edi+0x10],0x0 + 0x66,0x89,0x0f, // 0042cf2e 66:890f mov word ptr ds:[edi],cx + 0x8b,0xcf, // 0042cf31 8bcf mov ecx,edi + 0x50, // 0042cf33 50 push eax + 0xe8 //XX4 // 0042cf34 e8 e725f6ff call .0038f520 + // hook here + }; + enum { addr_offset = sizeof(bytes) + 4 }; // +4 for the call address + ULONG range = max(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //ULONG addr = module_base_ + 0x0018cf39; + if (!addr) { + //ConsoleOutput("Unknown SiglusEngine"); + ConsoleOutput("vnreng:Siglus4: pattern not found"); + return false; + } + + //addr = MemDbg::findEnclosingAlignedFunction(addr, 50); // 0x002667dc - 0x002667c0 = 28 + //if (!addr) { + // ConsoleOutput("vnreng:Siglus3: enclosing function not found"); + // return false; + //} + + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.type = NO_CONTEXT; + hp.text_fun = SpecialHookSiglus4; + hp.filter_fun = Siglus4Filter; + //hp.offset = pusha_eax_off - 4; + //hp.type = USING_UNICODE|DATA_INDIRECT|USING_SPLIT|NO_CONTEXT; + //hp.type = USING_UNICODE|USING_SPLIT|NO_CONTEXT; + //hp.split = pusha_edx_off - 4; + + ConsoleOutput("vnreng: INSERT Siglus4"); + NewHook(hp, "SiglusEngine4"); + + ConsoleOutput("vnreng:Siglus4: disable GDI hooks"); + DisableGDIHooks(); + return true; +} + +#if 0 // not all text can be extracted +/** jichi: 6/16/2015 Siglus4Engine for Frill games + * Sample game: 冺�少女 + * + * This function is found by tracking where the text length is modified + * + * Base address: 0x070000 + * + * 0020F51B CC INT3 + * 0020F51C CC INT3 + * 0020F51D CC INT3 + * 0020F51E CC INT3 + * 0020F51F CC INT3 + * 0020F520 55 PUSH EBP ; jichi: memory address in [arg1+0x4], text length in arg1 + * 0020F521 8BEC MOV EBP,ESP + * 0020F523 6A FF PUSH -0x1 + * 0020F525 68 889B5900 PUSH .00599B88 + * 0020F52A 64:A1 00000000 MOV EAX,DWORD PTR FS:[0] + * 0020F530 50 PUSH EAX + * 0020F531 83EC 1C SUB ESP,0x1C + * 0020F534 53 PUSH EBX + * 0020F535 56 PUSH ESI + * 0020F536 57 PUSH EDI + * 0020F537 A1 E0946500 MOV EAX,DWORD PTR DS:[0x6594E0] + * 0020F53C 33C5 XOR EAX,EBP + * 0020F53E 50 PUSH EAX + * 0020F53F 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-0xC] + * 0020F542 64:A3 00000000 MOV DWORD PTR FS:[0],EAX + * 0020F548 8BD1 MOV EDX,ECX + * 0020F54A 8955 F0 MOV DWORD PTR SS:[EBP-0x10],EDX + * 0020F54D 8B45 0C MOV EAX,DWORD PTR SS:[EBP+0xC] + * 0020F550 8B5D 10 MOV EBX,DWORD PTR SS:[EBP+0x10] + * 0020F553 3BC3 CMP EAX,EBX + * 0020F555 0F8D DF000000 JGE .0020F63A + * 0020F55B 8B75 08 MOV ESI,DWORD PTR SS:[EBP+0x8] + * 0020F55E 8D0C40 LEA ECX,DWORD PTR DS:[EAX+EAX*2] + * 0020F561 C1E1 03 SHL ECX,0x3 + * 0020F564 2BD8 SUB EBX,EAX + * 0020F566 894D 0C MOV DWORD PTR SS:[EBP+0xC],ECX + * 0020F569 8DA424 00000000 LEA ESP,DWORD PTR SS:[ESP] + * 0020F570 8B82 A4000000 MOV EAX,DWORD PTR DS:[EDX+0xA4] + * 0020F576 03C1 ADD EAX,ECX + * 0020F578 C745 EC 07000000 MOV DWORD PTR SS:[EBP-0x14],0x7 + * 0020F57F 33C9 XOR ECX,ECX + * 0020F581 C745 E8 00000000 MOV DWORD PTR SS:[EBP-0x18],0x0 + * 0020F588 6A FF PUSH -0x1 + * 0020F58A 51 PUSH ECX + * 0020F58B 66:894D D8 MOV WORD PTR SS:[EBP-0x28],CX + * 0020F58F 8D4D D8 LEA ECX,DWORD PTR SS:[EBP-0x28] + * 0020F592 50 PUSH EAX + * 0020F593 E8 68EFF4FF CALL .0015E500 + * 0020F598 C745 FC 00000000 MOV DWORD PTR SS:[EBP-0x4],0x0 + * 0020F59F 8BCE MOV ECX,ESI + * 0020F5A1 8B46 0C MOV EAX,DWORD PTR DS:[ESI+0xC] + * 0020F5A4 8B7D E8 MOV EDI,DWORD PTR SS:[EBP-0x18] + * 0020F5A7 83C0 04 ADD EAX,0x4 + * 0020F5AA 50 PUSH EAX + * 0020F5AB E8 209DF5FF CALL .001692D0 + * 0020F5B0 8B0E MOV ECX,DWORD PTR DS:[ESI] + * 0020F5B2 8D55 D8 LEA EDX,DWORD PTR SS:[EBP-0x28] + * 0020F5B5 33C0 XOR EAX,EAX + * 0020F5B7 3B4E 04 CMP ECX,DWORD PTR DS:[ESI+0x4] + * 0020F5BA 0F44C8 CMOVE ECX,EAX + * 0020F5BD 8B46 0C MOV EAX,DWORD PTR DS:[ESI+0xC] + * 0020F5C0 893C01 MOV DWORD PTR DS:[ECX+EAX],EDI ; jichi: text length modified here + * 0020F5C3 8B45 E8 MOV EAX,DWORD PTR SS:[EBP-0x18] + * 0020F5C6 8346 0C 04 ADD DWORD PTR DS:[ESI+0xC],0x4 + * 0020F5CA 8B4D D8 MOV ECX,DWORD PTR SS:[EBP-0x28] + * 0020F5CD 8D3C00 LEA EDI,DWORD PTR DS:[EAX+EAX] + * 0020F5D0 8B45 EC MOV EAX,DWORD PTR SS:[EBP-0x14] + * 0020F5D3 83F8 08 CMP EAX,0x8 + * 0020F5D6 0F43D1 CMOVNB EDX,ECX + * 0020F5D9 8955 10 MOV DWORD PTR SS:[EBP+0x10],EDX + * 0020F5DC 85FF TEST EDI,EDI + * 0020F5DE 7E 32 JLE SHORT .0020F612 + * 0020F5E0 8B46 0C MOV EAX,DWORD PTR DS:[ESI+0xC] + * 0020F5E3 8BCE MOV ECX,ESI + * 0020F5E5 03C7 ADD EAX,EDI + * 0020F5E7 50 PUSH EAX + * 0020F5E8 E8 E39CF5FF CALL .001692D0 + * 0020F5ED 8B0E MOV ECX,DWORD PTR DS:[ESI] + * 0020F5EF 33C0 XOR EAX,EAX + * 0020F5F1 3B4E 04 CMP ECX,DWORD PTR DS:[ESI+0x4] + * 0020F5F4 57 PUSH EDI + * 0020F5F5 FF75 10 PUSH DWORD PTR SS:[EBP+0x10] + * 0020F5F8 0F44C8 CMOVE ECX,EAX + * 0020F5FB 8B46 0C MOV EAX,DWORD PTR DS:[ESI+0xC] + * 0020F5FE 03C1 ADD EAX,ECX + * 0020F600 50 PUSH EAX + * 0020F601 E8 EA1B1200 CALL .003311F0 + * 0020F606 8B45 EC MOV EAX,DWORD PTR SS:[EBP-0x14] + * 0020F609 83C4 0C ADD ESP,0xC + * 0020F60C 017E 0C ADD DWORD PTR DS:[ESI+0xC],EDI + * 0020F60F 8B4D D8 MOV ECX,DWORD PTR SS:[EBP-0x28] + * 0020F612 C745 FC FFFFFFFF MOV DWORD PTR SS:[EBP-0x4],-0x1 + * 0020F619 83F8 08 CMP EAX,0x8 + * 0020F61C 72 09 JB SHORT .0020F627 + * 0020F61E 51 PUSH ECX + * 0020F61F E8 A6DC1100 CALL .0032D2CA + * 0020F624 83C4 04 ADD ESP,0x4 + * 0020F627 8B4D 0C MOV ECX,DWORD PTR SS:[EBP+0xC] + * 0020F62A 8B55 F0 MOV EDX,DWORD PTR SS:[EBP-0x10] + * 0020F62D 83C1 18 ADD ECX,0x18 + * 0020F630 894D 0C MOV DWORD PTR SS:[EBP+0xC],ECX + * 0020F633 4B DEC EBX + * 0020F634 ^0F85 36FFFFFF JNZ .0020F570 + * 0020F63A 8B4D F4 MOV ECX,DWORD PTR SS:[EBP-0xC] + * 0020F63D 64:890D 00000000 MOV DWORD PTR FS:[0],ECX + * 0020F644 59 POP ECX + * 0020F645 5F POP EDI + * 0020F646 5E POP ESI + * 0020F647 5B POP EBX + * 0020F648 8BE5 MOV ESP,EBP + * 0020F64A 5D POP EBP + * 0020F64B C2 0C00 RETN 0xC + * 0020F64E CC INT3 + * 0020F64F CC INT3 + */ +void SpecialHookSiglus4(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + static uint64_t lastTextHash_; + DWORD arg1 = argof(1, esp_base); // arg1 + DWORD addr = *(DWORD *)(arg1 + 4); + int size = *(DWORD *)addr; + if (size <= 0 || size > VNR_TEXT_CAPACITY) + return; + auto text = LPWSTR(addr + 4); + if (!text || ::IsBadWritePtr(text, size * 2) || !*text || ::wcslen(text) != size || lastTextHash_ == hashstr(text)) // || text[size+1], skip if text's size + 1 is not empty + return; + lastTextHash_ = hashstr(text); // skip last repetition + *len = size * 2; + *data = (DWORD)text; + *split = argof(3, esp_base); // arg3 +} +bool InsertSiglus4Hook() +{ + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng:Siglus4: failed to get memory range"); + return false; + } + const BYTE bytes[] = { + 0x8b,0x75, 0x08, // 0020f55b 8b75 08 mov esi,dword ptr ss:[ebp+0x8] + 0x8d,0x0c,0x40, // 0020f55e 8d0c40 lea ecx,dword ptr ds:[eax+eax*2] + 0xc1,0xe1, 0x03, // 0020f561 c1e1 03 shl ecx,0x3 + 0x2b,0xd8, // 0020f564 2bd8 sub ebx,eax + 0x89,0x4d, 0x0c // 0020f566 894d 0c mov dword ptr ss:[ebp+0xc],ecx + + // The following pattern is not unique, there are at least four matches + // // 0020f5b7 3b4e 04 cmp ecx,dword ptr ds:[esi+0x4] + // // 0020f5ba 0f44c8 cmove ecx,eax + //0x8b,0x46, 0x0c, // 0020f5bd 8b46 0c mov eax,dword ptr ds:[esi+0xc] + //0x89,0x3c,0x01, // 0020f5c0 893c01 mov dword ptr ds:[ecx+eax],edi ; jichi: text length modified here + //0x8b,0x45, 0xe8, // 0020f5c3 8b45 e8 mov eax,dword ptr ss:[ebp-0x18] + //0x83,0x46, 0x0c, 0x04, // 0020f5c6 8346 0c 04 add dword ptr ds:[esi+0xc],0x4 + //0x8b,0x4d, 0xd8, // 0020f5ca 8b4d d8 mov ecx,dword ptr ss:[ebp-0x28] + //0x8d,0x3c,0x00 // 0020f5cd 8d3c00 lea edi,dword ptr ds:[eax+eax] + // // 0020f5d0 8b45 ec mov eax,dword ptr ss:[ebp-0x14] + }; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress); + if (!addr) { + //ConsoleOutput("Unknown SiglusEngine"); + ConsoleOutput("vnreng:Siglus4: pattern not found"); + return false; + } + addr = MemDbg::findEnclosingAlignedFunction(addr, 0x100); // 0x0020f55b - 0x0020F520 = 59 + if (!addr) { + ConsoleOutput("vnreng:Siglus4: enclosing function not found"); + return false; + } + + //addr += 0x0020f64b - 0x0020f520; // hook to ret instead + + HookParam hp = {}; + hp.address = addr; + //hp.type = USING_UNICODE; + hp.type = NO_CONTEXT; + hp.text_fun = SpecialHookSiglus4; + hp.filter_fun = Siglus4Filter; // remove NLI from the game + + //GROWL_DWORD(addr); + + ConsoleOutput("vnreng: INSERT Siglus4"); + NewHook(hp, "SiglusEngine4"); + + ConsoleOutput("vnreng:Siglus4: disable GDI hooks"); + DisableGDIHooks(); + return true; +} +#endif // 0 + + +/** + * jichi 8/16/2013: Insert new siglus hook + * See (CaoNiMaGeBi): http://tieba.baidu.com/p/2531786952 + * Issue: floating text + * Example: + * 0153588b9534fdffff8b43583bd7 + * 0153 58 add dword ptr ds:[ebx+58],edx + * 8b95 34fdffff mov edx,dword ptr ss:[ebp-2cc] + * 8b43 58 mov eax,dword ptr ds:[ebx+58] + * 3bd7 cmp edx,edi ; hook here + * + * /HW-1C@D9DB2:SiglusEngine.exe + * - addr: 892338 (0xd9db2) + * - text_fun: 0x0 + * - function: 0 + * - hook_len: 0 + * - ind: 0 + * - length_offset: 1 + * - module: 356004490 (0x1538328a) + * - off: 4294967264 (0xffffffe0L, 0x-20) + * - recover_len: 0 + * - split: 0 + * - split_ind: 0 + * - type: 66 (0x42) + * + * 10/19/2014: There are currently two patterns to find the function to render scenario text. + * In the future, if both of them do not work again, try the following pattern instead. + * It is used to infer SiglusEngine2's logic in vnragent. + * + * 01140f8d 56 push esi + * 01140f8e 8d8b 0c010000 lea ecx,dword ptr ds:[ebx+0x10c] + * 01140f94 e8 67acfcff call .0110bc00 + * 01140f99 837f 14 08 cmp dword ptr ds:[edi+0x14],0x8 + * 01140f9d 72 04 jb short .01140fa3 + * 01140f9f 8b37 mov esi,dword ptr ds:[edi] + * 01140fa1 eb 02 jmp short .01140fa5 + * + * Type1 (聖娼女): + * + * 013aac6c cc int3 + * 013aac6d cc int3 + * 013aac6e cc int3 + * 013aac6f cc int3 + * 013aac70 55 push ebp ; jichi: vnragent hooked here + * 013aac71 8bec mov ebp,esp + * 013aac73 6a ff push -0x1 + * 013aac75 68 d8306101 push .016130d8 + * 013aac7a 64:a1 00000000 mov eax,dword ptr fs:[0] + * 013aac80 50 push eax + * 013aac81 81ec dc020000 sub esp,0x2dc + * 013aac87 a1 90f46a01 mov eax,dword ptr ds:[0x16af490] + * 013aac8c 33c5 xor eax,ebp + * 013aac8e 8945 f0 mov dword ptr ss:[ebp-0x10],eax + * 013aac91 53 push ebx + * 013aac92 56 push esi + * 013aac93 57 push edi + * 013aac94 50 push eax + * 013aac95 8d45 f4 lea eax,dword ptr ss:[ebp-0xc] + * 013aac98 64:a3 00000000 mov dword ptr fs:[0],eax + * 013aac9e 8b45 0c mov eax,dword ptr ss:[ebp+0xc] + * 013aaca1 8b5d 08 mov ebx,dword ptr ss:[ebp+0x8] + * 013aaca4 8bf9 mov edi,ecx + * 013aaca6 8b77 10 mov esi,dword ptr ds:[edi+0x10] + * 013aaca9 89bd 20fdffff mov dword ptr ss:[ebp-0x2e0],edi + * 013aacaf 8985 18fdffff mov dword ptr ss:[ebp-0x2e8],eax + * 013aacb5 85f6 test esi,esi + * 013aacb7 0f84 77040000 je .013ab134 + * 013aacbd 8b93 18010000 mov edx,dword ptr ds:[ebx+0x118] + * 013aacc3 2b93 14010000 sub edx,dword ptr ds:[ebx+0x114] + * 013aacc9 8d8b 14010000 lea ecx,dword ptr ds:[ebx+0x114] + * 013aaccf b8 67666666 mov eax,0x66666667 + * 013aacd4 f7ea imul edx + * 013aacd6 c1fa 08 sar edx,0x8 + * 013aacd9 8bc2 mov eax,edx + * 013aacdb c1e8 1f shr eax,0x1f + * 013aacde 03c2 add eax,edx + * 013aace0 03c6 add eax,esi + * 013aace2 50 push eax + * 013aace3 e8 5896fcff call .01374340 + * 013aace8 837f 14 08 cmp dword ptr ds:[edi+0x14],0x8 + * 013aacec 72 04 jb short .013aacf2 + * 013aacee 8b07 mov eax,dword ptr ds:[edi] + * 013aacf0 eb 02 jmp short .013aacf4 + * 013aacf2 8bc7 mov eax,edi + * 013aacf4 8985 24fdffff mov dword ptr ss:[ebp-0x2dc],eax + * 013aacfa 8b57 14 mov edx,dword ptr ds:[edi+0x14] + * 013aacfd 83fa 08 cmp edx,0x8 + * 013aad00 72 04 jb short .013aad06 + * 013aad02 8b0f mov ecx,dword ptr ds:[edi] + * 013aad04 eb 02 jmp short .013aad08 + * 013aad06 8bcf mov ecx,edi + * 013aad08 8b47 10 mov eax,dword ptr ds:[edi+0x10] + * 013aad0b 8bb5 24fdffff mov esi,dword ptr ss:[ebp-0x2dc] + * 013aad11 03c0 add eax,eax + * 013aad13 03c8 add ecx,eax + * 013aad15 3bf1 cmp esi,ecx + * 013aad17 0f84 17040000 je .013ab134 + * 013aad1d c785 34fdffff 00>mov dword ptr ss:[ebp-0x2cc],0x0 + * 013aad27 c785 2cfdffff ff>mov dword ptr ss:[ebp-0x2d4],-0x1 + * 013aad31 89b5 1cfdffff mov dword ptr ss:[ebp-0x2e4],esi + * 013aad37 83fa 08 cmp edx,0x8 + * 013aad3a 72 04 jb short .013aad40 + * 013aad3c 8b0f mov ecx,dword ptr ds:[edi] + * 013aad3e eb 02 jmp short .013aad42 + * 013aad40 8bcf mov ecx,edi + * 013aad42 03c1 add eax,ecx + * 013aad44 8d8d 2cfdffff lea ecx,dword ptr ss:[ebp-0x2d4] + * 013aad4a 51 push ecx + * 013aad4b 8d95 34fdffff lea edx,dword ptr ss:[ebp-0x2cc] + * 013aad51 52 push edx + * 013aad52 50 push eax + * 013aad53 8d85 24fdffff lea eax,dword ptr ss:[ebp-0x2dc] + * 013aad59 50 push eax + * 013aad5a e8 b183faff call .01353110 + * 013aad5f 8bb5 2cfdffff mov esi,dword ptr ss:[ebp-0x2d4] + * 013aad65 83c4 10 add esp,0x10 + * 013aad68 83fe 0a cmp esi,0xa + * 013aad6b 75 09 jnz short .013aad76 + * 013aad6d 8bcb mov ecx,ebx + * 013aad6f e8 ac050000 call .013ab320 + * 013aad74 ^eb 84 jmp short .013aacfa + * 013aad76 83fe 07 cmp esi,0x7 + * 013aad79 75 2a jnz short .013aada5 + * 013aad7b 33c9 xor ecx,ecx + * 013aad7d 33c0 xor eax,eax + * 013aad7f 66:898b ec000000 mov word ptr ds:[ebx+0xec],cx + * 013aad86 8bcb mov ecx,ebx + * 013aad88 8983 e8000000 mov dword ptr ds:[ebx+0xe8],eax + * 013aad8e 8983 f0000000 mov dword ptr ds:[ebx+0xf0],eax + * 013aad94 e8 87050000 call .013ab320 + * 013aad99 c683 f9000000 01 mov byte ptr ds:[ebx+0xf9],0x1 + * 013aada0 ^e9 55ffffff jmp .013aacfa + * 013aada5 8b85 34fdffff mov eax,dword ptr ss:[ebp-0x2cc] + * 013aadab 85c0 test eax,eax + * 013aadad 75 37 jnz short .013aade6 + * 013aadaf 85f6 test esi,esi + * 013aadb1 ^0f84 43ffffff je .013aacfa + * 013aadb7 85c0 test eax,eax + * 013aadb9 75 2b jnz short .013aade6 + * 013aadbb f605 c0be9f05 01 test byte ptr ds:[0x59fbec0],0x1 + * 013aadc2 75 0c jnz short .013aadd0 + * 013aadc4 830d c0be9f05 01 or dword ptr ds:[0x59fbec0],0x1 + * 013aadcb e8 f02a0b00 call .0145d8c0 + * 013aadd0 0fb7d6 movzx edx,si + * 013aadd3 80ba c0be9e05 01 cmp byte ptr ds:[edx+0x59ebec0],0x1 + * 013aadda 75 0a jnz short .013aade6 + * 013aaddc 8b43 68 mov eax,dword ptr ds:[ebx+0x68] + * 013aaddf 99 cdq + * 013aade0 2bc2 sub eax,edx + * 013aade2 d1f8 sar eax,1 + * 013aade4 eb 03 jmp short .013aade9 + * 013aade6 8b43 68 mov eax,dword ptr ds:[ebx+0x68] + * 013aade9 8b8b a0000000 mov ecx,dword ptr ds:[ebx+0xa0] + * 013aadef 8b53 18 mov edx,dword ptr ds:[ebx+0x18] + * 013aadf2 8985 30fdffff mov dword ptr ss:[ebp-0x2d0],eax + * 013aadf8 0343 58 add eax,dword ptr ds:[ebx+0x58] + * 013aadfb 03d1 add edx,ecx + * 013aadfd 3bc2 cmp eax,edx + * 013aadff 7f 0f jg short .013aae10 + * 013aae01 3bc1 cmp eax,ecx + * 013aae03 7e 30 jle short .013aae35 + * 013aae05 8bc6 mov eax,esi + * 013aae07 e8 94faffff call .013aa8a0 + * 013aae0c 84c0 test al,al + * 013aae0e 75 25 jnz short .013aae35 + * 013aae10 8bcb mov ecx,ebx + * 013aae12 e8 09050000 call .013ab320 + * 013aae17 83bd 34fdffff 00 cmp dword ptr ss:[ebp-0x2cc],0x0 + * 013aae1e 75 15 jnz short .013aae35 + * 013aae20 83fe 20 cmp esi,0x20 + * 013aae23 ^0f84 d1feffff je .013aacfa + * 013aae29 81fe 00300000 cmp esi,0x3000 + * 013aae2f ^0f84 c5feffff je .013aacfa + * 013aae35 8b43 5c mov eax,dword ptr ds:[ebx+0x5c] + * 013aae38 3b83 a4000000 cmp eax,dword ptr ds:[ebx+0xa4] + * 013aae3e 0f8d 7e020000 jge .013ab0c2 + * 013aae44 8d8d 38fdffff lea ecx,dword ptr ss:[ebp-0x2c8] + * 013aae4a 51 push ecx + * 013aae4b e8 30e4ffff call .013a9280 + * 013aae50 c745 fc 01000000 mov dword ptr ss:[ebp-0x4],0x1 + * 013aae57 8b43 74 mov eax,dword ptr ds:[ebx+0x74] + * 013aae5a 8b0d 88b26c01 mov ecx,dword ptr ds:[0x16cb288] + * 013aae60 83f8 ff cmp eax,-0x1 + * 013aae63 74 04 je short .013aae69 + * 013aae65 8bd0 mov edx,eax + * 013aae67 eb 19 jmp short .013aae82 + * 013aae69 80b9 60010000 00 cmp byte ptr ds:[ecx+0x160],0x0 + * 013aae70 74 0d je short .013aae7f + * 013aae72 8b83 e0000000 mov eax,dword ptr ds:[ebx+0xe0] + * 013aae78 8bd0 mov edx,eax + * 013aae7a 83f8 ff cmp eax,-0x1 + * 013aae7d 75 03 jnz short .013aae82 + * 013aae7f 8b53 24 mov edx,dword ptr ds:[ebx+0x24] + * 013aae82 8b43 78 mov eax,dword ptr ds:[ebx+0x78] + * 013aae85 83f8 ff cmp eax,-0x1 + * 013aae88 75 17 jnz short .013aaea1 + * 013aae8a 80b9 60010000 00 cmp byte ptr ds:[ecx+0x160],0x0 + * 013aae91 74 0b je short .013aae9e + * 013aae93 8b83 e4000000 mov eax,dword ptr ds:[ebx+0xe4] + * 013aae99 83f8 ff cmp eax,-0x1 + * 013aae9c 75 03 jnz short .013aaea1 + * 013aae9e 8b43 28 mov eax,dword ptr ds:[ebx+0x28] + * 013aaea1 8b4b 60 mov ecx,dword ptr ds:[ebx+0x60] + * 013aaea4 8bb5 34fdffff mov esi,dword ptr ss:[ebp-0x2cc] + * 013aaeaa 034b 58 add ecx,dword ptr ds:[ebx+0x58] + * 013aaead 8b7b 68 mov edi,dword ptr ds:[ebx+0x68] + * 013aaeb0 8985 28fdffff mov dword ptr ss:[ebp-0x2d8],eax + * 013aaeb6 8b43 5c mov eax,dword ptr ds:[ebx+0x5c] + * 013aaeb9 0343 64 add eax,dword ptr ds:[ebx+0x64] + * 013aaebc 83fe 01 cmp esi,0x1 + * 013aaebf 75 02 jnz short .013aaec3 + * 013aaec1 33d2 xor edx,edx + * 013aaec3 80bb fa000000 00 cmp byte ptr ds:[ebx+0xfa],0x0 + * 013aaeca 89b5 38fdffff mov dword ptr ss:[ebp-0x2c8],esi + * 013aaed0 8bb5 2cfdffff mov esi,dword ptr ss:[ebp-0x2d4] + * 013aaed6 8995 44fdffff mov dword ptr ss:[ebp-0x2bc],edx + * 013aaedc 8b95 28fdffff mov edx,dword ptr ss:[ebp-0x2d8] + * 013aaee2 89b5 3cfdffff mov dword ptr ss:[ebp-0x2c4],esi + * 013aaee8 89bd 40fdffff mov dword ptr ss:[ebp-0x2c0],edi + * 013aaeee 8995 48fdffff mov dword ptr ss:[ebp-0x2b8],edx + * 013aaef4 898d 4cfdffff mov dword ptr ss:[ebp-0x2b4],ecx + * 013aaefa 8985 50fdffff mov dword ptr ss:[ebp-0x2b0],eax + * 013aaf00 74 19 je short .013aaf1b + * 013aaf02 8b43 58 mov eax,dword ptr ds:[ebx+0x58] + * 013aaf05 8b4b 5c mov ecx,dword ptr ds:[ebx+0x5c] + * 013aaf08 8983 fc000000 mov dword ptr ds:[ebx+0xfc],eax + * 013aaf0e 898b 00010000 mov dword ptr ds:[ebx+0x100],ecx + * 013aaf14 c683 fa000000 00 mov byte ptr ds:[ebx+0xfa],0x0 + * 013aaf1b 8b53 6c mov edx,dword ptr ds:[ebx+0x6c] + * 013aaf1e 0395 30fdffff add edx,dword ptr ss:[ebp-0x2d0] + * 013aaf24 33ff xor edi,edi + * 013aaf26 0153 58 add dword ptr ds:[ebx+0x58],edx + * 013aaf29 8b95 34fdffff mov edx,dword ptr ss:[ebp-0x2cc] + * 013aaf2f 8b43 58 mov eax,dword ptr ds:[ebx+0x58] + * 013aaf32 3bd7 cmp edx,edi ; jichi: hook here + * 013aaf34 75 4b jnz short .013aaf81 + * 013aaf36 81fe 0c300000 cmp esi,0x300c ; jichi 10/18/2014: searched here found the new siglus function + * 013aaf3c 74 10 je short .013aaf4e + * 013aaf3e 81fe 0e300000 cmp esi,0x300e + * 013aaf44 74 08 je short .013aaf4e + * 013aaf46 81fe 08ff0000 cmp esi,0xff08 + * 013aaf4c 75 33 jnz short .013aaf81 + * 013aaf4e 80bb f9000000 00 cmp byte ptr ds:[ebx+0xf9],0x0 + * 013aaf55 74 19 je short .013aaf70 + * 013aaf57 8983 e8000000 mov dword ptr ds:[ebx+0xe8],eax + * 013aaf5d 66:89b3 ec000000 mov word ptr ds:[ebx+0xec],si + * 013aaf64 c783 f0000000 01>mov dword ptr ds:[ebx+0xf0],0x1 + * 013aaf6e eb 11 jmp short .013aaf81 + * 013aaf70 0fb783 ec000000 movzx eax,word ptr ds:[ebx+0xec] + * 013aaf77 3bf0 cmp esi,eax + * 013aaf79 75 06 jnz short .013aaf81 + * 013aaf7b ff83 f0000000 inc dword ptr ds:[ebx+0xf0] + * 013aaf81 8b8b f0000000 mov ecx,dword ptr ds:[ebx+0xf0] + * 013aaf87 3bcf cmp ecx,edi + * 013aaf89 7e 71 jle short .013aaffc + * 013aaf8b 3bd7 cmp edx,edi + * 013aaf8d 75 50 jnz short .013aafdf + * 013aaf8f 0fb783 ec000000 movzx eax,word ptr ds:[ebx+0xec] + * 013aaf96 ba 0c300000 mov edx,0x300c + * 013aaf9b 66:3bc2 cmp ax,dx + * 013aaf9e 75 0f jnz short .013aafaf + * 013aafa0 81fe 0d300000 cmp esi,0x300d + * 013aafa6 75 07 jnz short .013aafaf + * 013aafa8 49 dec ecx + * 013aafa9 898b f0000000 mov dword ptr ds:[ebx+0xf0],ecx + * 013aafaf b9 0e300000 mov ecx,0x300e + * 013aafb4 66:3bc1 cmp ax,cx + * 013aafb7 75 0e jnz short .013aafc7 + * 013aafb9 81fe 0f300000 cmp esi,0x300f + * 013aafbf 75 06 jnz short .013aafc7 + * 013aafc1 ff8b f0000000 dec dword ptr ds:[ebx+0xf0] + * 013aafc7 ba 08ff0000 mov edx,0xff08 + * 013aafcc 66:3bc2 cmp ax,dx + * 013aafcf 75 0e jnz short .013aafdf + * 013aafd1 81fe 09ff0000 cmp esi,0xff09 + * 013aafd7 75 06 jnz short .013aafdf + * 013aafd9 ff8b f0000000 dec dword ptr ds:[ebx+0xf0] + * 013aafdf 39bb f0000000 cmp dword ptr ds:[ebx+0xf0],edi + * 013aafe5 75 15 jnz short .013aaffc + * 013aafe7 33c0 xor eax,eax + * 013aafe9 89bb e8000000 mov dword ptr ds:[ebx+0xe8],edi + * 013aafef 66:8983 ec000000 mov word ptr ds:[ebx+0xec],ax + * 013aaff6 89bb f0000000 mov dword ptr ds:[ebx+0xf0],edi + * 013aaffc 8d8d 38fdffff lea ecx,dword ptr ss:[ebp-0x2c8] + * 013ab002 8dbb 14010000 lea edi,dword ptr ds:[ebx+0x114] + * 013ab008 e8 b390fcff call .013740c0 + * 013ab00d 33ff xor edi,edi + * 013ab00f 39bd 34fdffff cmp dword ptr ss:[ebp-0x2cc],edi + * 013ab015 75 0e jnz short .013ab025 + * 013ab017 56 push esi + * 013ab018 8d83 a8000000 lea eax,dword ptr ds:[ebx+0xa8] + * 013ab01e e8 5d080000 call .013ab880 + * 013ab023 eb 65 jmp short .013ab08a + * 013ab025 8b85 1cfdffff mov eax,dword ptr ss:[ebp-0x2e4] + * 013ab02b 33c9 xor ecx,ecx + * 013ab02d 66:894d d4 mov word ptr ss:[ebp-0x2c],cx + * 013ab031 8b8d 24fdffff mov ecx,dword ptr ss:[ebp-0x2dc] + * 013ab037 c745 e8 07000000 mov dword ptr ss:[ebp-0x18],0x7 + * 013ab03e 897d e4 mov dword ptr ss:[ebp-0x1c],edi + * 013ab041 3bc1 cmp eax,ecx + * 013ab043 74 0d je short .013ab052 + * 013ab045 2bc8 sub ecx,eax + * 013ab047 d1f9 sar ecx,1 + * 013ab049 51 push ecx + * 013ab04a 8d75 d4 lea esi,dword ptr ss:[ebp-0x2c] + * 013ab04d e8 de72f2ff call .012d2330 + * 013ab052 6a ff push -0x1 + * 013ab054 57 push edi + * 013ab055 8d55 d4 lea edx,dword ptr ss:[ebp-0x2c] + * 013ab058 52 push edx + * 013ab059 8db3 a8000000 lea esi,dword ptr ds:[ebx+0xa8] + * 013ab05f c645 fc 02 mov byte ptr ss:[ebp-0x4],0x2 + * 013ab063 e8 3879f2ff call .012d29a0 + * 013ab068 837d e8 08 cmp dword ptr ss:[ebp-0x18],0x8 + * 013ab06c 72 0c jb short .013ab07a + * 013ab06e 8b45 d4 mov eax,dword ptr ss:[ebp-0x2c] + * 013ab071 50 push eax + * 013ab072 e8 5fbe1900 call .01546ed6 + * 013ab077 83c4 04 add esp,0x4 + * 013ab07a 33c9 xor ecx,ecx + * 013ab07c c745 e8 07000000 mov dword ptr ss:[ebp-0x18],0x7 + * 013ab083 897d e4 mov dword ptr ss:[ebp-0x1c],edi + * 013ab086 66:894d d4 mov word ptr ss:[ebp-0x2c],cx + * 013ab08a 8bbd 20fdffff mov edi,dword ptr ss:[ebp-0x2e0] + * 013ab090 c683 f9000000 00 mov byte ptr ds:[ebx+0xf9],0x0 + * 013ab097 8d95 88feffff lea edx,dword ptr ss:[ebp-0x178] + * 013ab09d 52 push edx + * 013ab09e c745 fc 03000000 mov dword ptr ss:[ebp-0x4],0x3 + * 013ab0a5 e8 d6c70800 call .01437880 + * 013ab0aa 8d85 58fdffff lea eax,dword ptr ss:[ebp-0x2a8] + * 013ab0b0 50 push eax + * 013ab0b1 c745 fc ffffffff mov dword ptr ss:[ebp-0x4],-0x1 + * 013ab0b8 e8 c3c70800 call .01437880 + * 013ab0bd ^e9 38fcffff jmp .013aacfa + * 013ab0c2 8b9d 18fdffff mov ebx,dword ptr ss:[ebp-0x2e8] + * 013ab0c8 85db test ebx,ebx + * 013ab0ca 74 68 je short .013ab134 + * 013ab0cc 837f 14 08 cmp dword ptr ds:[edi+0x14],0x8 + * 013ab0d0 72 04 jb short .013ab0d6 + * 013ab0d2 8b07 mov eax,dword ptr ds:[edi] + * 013ab0d4 eb 02 jmp short .013ab0d8 + * 013ab0d6 8bc7 mov eax,edi + * 013ab0d8 8b4f 10 mov ecx,dword ptr ds:[edi+0x10] + * 013ab0db 8d0448 lea eax,dword ptr ds:[eax+ecx*2] + * 013ab0de 8b8d 1cfdffff mov ecx,dword ptr ss:[ebp-0x2e4] + * 013ab0e4 33d2 xor edx,edx + * 013ab0e6 c745 cc 07000000 mov dword ptr ss:[ebp-0x34],0x7 + * 013ab0ed c745 c8 00000000 mov dword ptr ss:[ebp-0x38],0x0 + * 013ab0f4 66:8955 b8 mov word ptr ss:[ebp-0x48],dx + * 013ab0f8 3bc8 cmp ecx,eax + * 013ab0fa 74 0f je short .013ab10b + * 013ab0fc 2bc1 sub eax,ecx + * 013ab0fe d1f8 sar eax,1 + * 013ab100 50 push eax + * 013ab101 8bc1 mov eax,ecx + * 013ab103 8d75 b8 lea esi,dword ptr ss:[ebp-0x48] + * 013ab106 e8 2572f2ff call .012d2330 + * 013ab10b 6a 00 push 0x0 + * 013ab10d 8d45 b8 lea eax,dword ptr ss:[ebp-0x48] + * 013ab110 50 push eax + * 013ab111 83c8 ff or eax,0xffffffff + * 013ab114 8bcb mov ecx,ebx + * 013ab116 c745 fc 00000000 mov dword ptr ss:[ebp-0x4],0x0 + * 013ab11d e8 2e6ef2ff call .012d1f50 + * 013ab122 837d cc 08 cmp dword ptr ss:[ebp-0x34],0x8 + * 013ab126 72 0c jb short .013ab134 + * 013ab128 8b4d b8 mov ecx,dword ptr ss:[ebp-0x48] + * 013ab12b 51 push ecx + * 013ab12c e8 a5bd1900 call .01546ed6 + * 013ab131 83c4 04 add esp,0x4 + * 013ab134 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 013ab137 64:890d 00000000 mov dword ptr fs:[0],ecx + * 013ab13e 59 pop ecx + * 013ab13f 5f pop edi + * 013ab140 5e pop esi + * 013ab141 5b pop ebx + * 013ab142 8b4d f0 mov ecx,dword ptr ss:[ebp-0x10] + * 013ab145 33cd xor ecx,ebp + * 013ab147 e8 6ab30e00 call .014964b6 + * 013ab14c 8be5 mov esp,ebp + * 013ab14e 5d pop ebp + * 013ab14f c2 0800 retn 0x8 + * 013ab152 cc int3 + * 013ab153 cc int3 + * 013ab154 cc int3 + * + * 10/18/2014 Type2: リア兂�ラスメイト孕ませ催� + * + * 01140edb cc int3 + * 01140edc cc int3 + * 01140edd cc int3 + * 01140ede cc int3 + * 01140edf cc int3 + * 01140ee0 55 push ebp + * 01140ee1 8bec mov ebp,esp + * 01140ee3 6a ff push -0x1 + * 01140ee5 68 c6514a01 push .014a51c6 + * 01140eea 64:a1 00000000 mov eax,dword ptr fs:[0] + * 01140ef0 50 push eax + * 01140ef1 81ec dc020000 sub esp,0x2dc + * 01140ef7 a1 10745501 mov eax,dword ptr ds:[0x1557410] + * 01140efc 33c5 xor eax,ebp + * 01140efe 8945 f0 mov dword ptr ss:[ebp-0x10],eax + * 01140f01 53 push ebx + * 01140f02 56 push esi + * 01140f03 57 push edi + * 01140f04 50 push eax + * 01140f05 8d45 f4 lea eax,dword ptr ss:[ebp-0xc] + * 01140f08 64:a3 00000000 mov dword ptr fs:[0],eax + * 01140f0e 8bd9 mov ebx,ecx + * 01140f10 8b7d 08 mov edi,dword ptr ss:[ebp+0x8] + * 01140f13 837f 10 00 cmp dword ptr ds:[edi+0x10],0x0 + * 01140f17 8b45 0c mov eax,dword ptr ss:[ebp+0xc] + * 01140f1a 8985 1cfdffff mov dword ptr ss:[ebp-0x2e4],eax + * 01140f20 8d47 10 lea eax,dword ptr ds:[edi+0x10] + * 01140f23 89bd 38fdffff mov dword ptr ss:[ebp-0x2c8],edi + * 01140f29 8985 20fdffff mov dword ptr ss:[ebp-0x2e0],eax + * 01140f2f 0f84 2a050000 je .0114145f + * 01140f35 8b8b 10010000 mov ecx,dword ptr ds:[ebx+0x110] + * 01140f3b b8 67666666 mov eax,0x66666667 + * 01140f40 2b8b 0c010000 sub ecx,dword ptr ds:[ebx+0x10c] + * 01140f46 f7e9 imul ecx + * 01140f48 8b85 20fdffff mov eax,dword ptr ss:[ebp-0x2e0] + * 01140f4e 8b8b 14010000 mov ecx,dword ptr ds:[ebx+0x114] + * 01140f54 2b8b 0c010000 sub ecx,dword ptr ds:[ebx+0x10c] + * 01140f5a c1fa 08 sar edx,0x8 + * 01140f5d 8bf2 mov esi,edx + * 01140f5f c1ee 1f shr esi,0x1f + * 01140f62 03f2 add esi,edx + * 01140f64 0330 add esi,dword ptr ds:[eax] + * 01140f66 b8 67666666 mov eax,0x66666667 + * 01140f6b f7e9 imul ecx + * 01140f6d c1fa 08 sar edx,0x8 + * 01140f70 8bc2 mov eax,edx + * 01140f72 c1e8 1f shr eax,0x1f + * 01140f75 03c2 add eax,edx + * 01140f77 3bc6 cmp eax,esi + * 01140f79 73 1e jnb short .01140f99 + * 01140f7b 81fe 66666600 cmp esi,0x666666 ; unicode "s the data. + * 01140f81 76 0a jbe short .01140f8d + * 01140f83 68 c00f4f01 push .014f0fc0 ; ascii "vector too long" + * 01140f88 e8 b1a30e00 call .0122b33e + * 01140f8d 56 push esi + * 01140f8e 8d8b 0c010000 lea ecx,dword ptr ds:[ebx+0x10c] + * 01140f94 e8 67acfcff call .0110bc00 + * 01140f99 837f 14 08 cmp dword ptr ds:[edi+0x14],0x8 + * 01140f9d 72 04 jb short .01140fa3 + * 01140f9f 8b37 mov esi,dword ptr ds:[edi] + * 01140fa1 eb 02 jmp short .01140fa5 + * 01140fa3 8bf7 mov esi,edi + * 01140fa5 89b5 34fdffff mov dword ptr ss:[ebp-0x2cc],esi + * 01140fab eb 03 jmp short .01140fb0 + * 01140fad 8d49 00 lea ecx,dword ptr ds:[ecx] + * 01140fb0 8b57 14 mov edx,dword ptr ds:[edi+0x14] + * 01140fb3 83fa 08 cmp edx,0x8 + * 01140fb6 72 04 jb short .01140fbc + * 01140fb8 8b07 mov eax,dword ptr ds:[edi] + * 01140fba eb 02 jmp short .01140fbe + * 01140fbc 8bc7 mov eax,edi + * 01140fbe 8b8d 20fdffff mov ecx,dword ptr ss:[ebp-0x2e0] + * 01140fc4 8b09 mov ecx,dword ptr ds:[ecx] + * 01140fc6 03c9 add ecx,ecx + * 01140fc8 03c1 add eax,ecx + * 01140fca 3bf0 cmp esi,eax + * 01140fcc 0f84 8d040000 je .0114145f + * 01140fd2 8b85 38fdffff mov eax,dword ptr ss:[ebp-0x2c8] + * 01140fd8 8bfe mov edi,esi + * 01140fda c785 3cfdffff 00>mov dword ptr ss:[ebp-0x2c4],0x0 + * 01140fe4 c785 2cfdffff ff>mov dword ptr ss:[ebp-0x2d4],-0x1 + * 01140fee 83fa 08 cmp edx,0x8 + * 01140ff1 72 02 jb short .01140ff5 + * 01140ff3 8b00 mov eax,dword ptr ds:[eax] + * 01140ff5 03c1 add eax,ecx + * 01140ff7 8d95 3cfdffff lea edx,dword ptr ss:[ebp-0x2c4] + * 01140ffd 8d8d 2cfdffff lea ecx,dword ptr ss:[ebp-0x2d4] + * 01141003 51 push ecx + * 01141004 50 push eax + * 01141005 8d8d 34fdffff lea ecx,dword ptr ss:[ebp-0x2cc] + * 0114100b e8 e033fbff call .010f43f0 + * 01141010 8bb5 2cfdffff mov esi,dword ptr ss:[ebp-0x2d4] + * 01141016 83c4 08 add esp,0x8 + * 01141019 83fe 0a cmp esi,0xa + * 0114101c 75 18 jnz short .01141036 + * 0114101e 8bcb mov ecx,ebx + * 01141020 e8 2b060000 call .01141650 + * 01141025 8bb5 34fdffff mov esi,dword ptr ss:[ebp-0x2cc] + * 0114102b 8bbd 38fdffff mov edi,dword ptr ss:[ebp-0x2c8] + * 01141031 ^e9 7affffff jmp .01140fb0 + * 01141036 83fe 07 cmp esi,0x7 + * 01141039 75 38 jnz short .01141073 + * 0114103b 33c0 xor eax,eax + * 0114103d c783 e0000000 00>mov dword ptr ds:[ebx+0xe0],0x0 + * 01141047 8bcb mov ecx,ebx + * 01141049 66:8983 e4000000 mov word ptr ds:[ebx+0xe4],ax + * 01141050 8983 e8000000 mov dword ptr ds:[ebx+0xe8],eax + * 01141056 e8 f5050000 call .01141650 + * 0114105b 8bb5 34fdffff mov esi,dword ptr ss:[ebp-0x2cc] + * 01141061 8bbd 38fdffff mov edi,dword ptr ss:[ebp-0x2c8] + * 01141067 c683 f1000000 01 mov byte ptr ds:[ebx+0xf1],0x1 + * 0114106e ^e9 3dffffff jmp .01140fb0 + * 01141073 8b85 3cfdffff mov eax,dword ptr ss:[ebp-0x2c4] + * 01141079 85c0 test eax,eax + * 0114107b 75 36 jnz short .011410b3 + * 0114107d 85f6 test esi,esi + * 0114107f 74 7f je short .01141100 + * 01141081 85c0 test eax,eax + * 01141083 75 2e jnz short .011410b3 + * 01141085 a1 00358905 mov eax,dword ptr ds:[0x5893500] + * 0114108a a8 01 test al,0x1 + * 0114108c 75 0d jnz short .0114109b + * 0114108e 83c8 01 or eax,0x1 + * 01141091 a3 00358905 mov dword ptr ds:[0x5893500],eax + * 01141096 e8 65160b00 call .011f2700 + * 0114109b 0fb7c6 movzx eax,si + * 0114109e 80b8 10358905 01 cmp byte ptr ds:[eax+0x5893510],0x1 + * 011410a5 75 0c jnz short .011410b3 + * 011410a7 8b43 68 mov eax,dword ptr ds:[ebx+0x68] + * 011410aa 99 cdq + * 011410ab 2bc2 sub eax,edx + * 011410ad 8bc8 mov ecx,eax + * 011410af d1f9 sar ecx,1 + * 011410b1 eb 03 jmp short .011410b6 + * 011410b3 8b4b 68 mov ecx,dword ptr ds:[ebx+0x68] + * 011410b6 8b43 18 mov eax,dword ptr ds:[ebx+0x18] + * 011410b9 8b93 a0000000 mov edx,dword ptr ds:[ebx+0xa0] + * 011410bf 03c2 add eax,edx + * 011410c1 898d 28fdffff mov dword ptr ss:[ebp-0x2d8],ecx + * 011410c7 034b 58 add ecx,dword ptr ds:[ebx+0x58] + * 011410ca 3bc8 cmp ecx,eax + * 011410cc 7f 0f jg short .011410dd + * 011410ce 3bca cmp ecx,edx + * 011410d0 7e 3f jle short .01141111 + * 011410d2 8bce mov ecx,esi + * 011410d4 e8 37faffff call .01140b10 + * 011410d9 84c0 test al,al + * 011410db 75 34 jnz short .01141111 + * 011410dd 8bcb mov ecx,ebx + * 011410df e8 6c050000 call .01141650 + * 011410e4 83bd 3cfdffff 00 cmp dword ptr ss:[ebp-0x2c4],0x0 + * 011410eb 75 24 jnz short .01141111 + * 011410ed 83fe 20 cmp esi,0x20 + * 011410f0 74 0e je short .01141100 + * 011410f2 81fe 00300000 cmp esi,0x3000 + * 011410f8 75 17 jnz short .01141111 + * 011410fa 8d9b 00000000 lea ebx,dword ptr ds:[ebx] + * 01141100 8bb5 34fdffff mov esi,dword ptr ss:[ebp-0x2cc] + * 01141106 8bbd 38fdffff mov edi,dword ptr ss:[ebp-0x2c8] + * 0114110c ^e9 9ffeffff jmp .01140fb0 + * 01141111 8b43 5c mov eax,dword ptr ds:[ebx+0x5c] + * 01141114 3b83 a4000000 cmp eax,dword ptr ds:[ebx+0xa4] + * 0114111a 0f8d cb020000 jge .011413eb + * 01141120 8d8d 40fdffff lea ecx,dword ptr ss:[ebp-0x2c0] + * 01141126 e8 d5e3ffff call .0113f500 + * 0114112b c745 fc 01000000 mov dword ptr ss:[ebp-0x4],0x1 + * 01141132 8b4b 74 mov ecx,dword ptr ds:[ebx+0x74] + * 01141135 8b15 98285701 mov edx,dword ptr ds:[0x1572898] + * 0114113b 898d 30fdffff mov dword ptr ss:[ebp-0x2d0],ecx + * 01141141 83f9 ff cmp ecx,-0x1 + * 01141144 75 23 jnz short .01141169 + * 01141146 80ba 58010000 00 cmp byte ptr ds:[edx+0x158],0x0 + * 0114114d 74 11 je short .01141160 + * 0114114f 8b8b d8000000 mov ecx,dword ptr ds:[ebx+0xd8] + * 01141155 898d 30fdffff mov dword ptr ss:[ebp-0x2d0],ecx + * 0114115b 83f9 ff cmp ecx,-0x1 + * 0114115e 75 09 jnz short .01141169 + * 01141160 8b43 24 mov eax,dword ptr ds:[ebx+0x24] + * 01141163 8985 30fdffff mov dword ptr ss:[ebp-0x2d0],eax + * 01141169 8b43 78 mov eax,dword ptr ds:[ebx+0x78] + * 0114116c 8985 24fdffff mov dword ptr ss:[ebp-0x2dc],eax + * 01141172 83f8 ff cmp eax,-0x1 + * 01141175 75 23 jnz short .0114119a + * 01141177 80ba 58010000 00 cmp byte ptr ds:[edx+0x158],0x0 + * 0114117e 74 11 je short .01141191 + * 01141180 8b83 dc000000 mov eax,dword ptr ds:[ebx+0xdc] + * 01141186 8985 24fdffff mov dword ptr ss:[ebp-0x2dc],eax + * 0114118c 83f8 ff cmp eax,-0x1 + * 0114118f 75 09 jnz short .0114119a + * 01141191 8b43 28 mov eax,dword ptr ds:[ebx+0x28] + * 01141194 8985 24fdffff mov dword ptr ss:[ebp-0x2dc],eax + * 0114119a 8b53 64 mov edx,dword ptr ds:[ebx+0x64] + * 0114119d 0353 5c add edx,dword ptr ds:[ebx+0x5c] + * 011411a0 8b4b 60 mov ecx,dword ptr ds:[ebx+0x60] + * 011411a3 034b 58 add ecx,dword ptr ds:[ebx+0x58] + * 011411a6 83bd 3cfdffff 01 cmp dword ptr ss:[ebp-0x2c4],0x1 + * 011411ad 8bb5 30fdffff mov esi,dword ptr ss:[ebp-0x2d0] + * 011411b3 8b43 68 mov eax,dword ptr ds:[ebx+0x68] + * 011411b6 c785 18fdffff 00>mov dword ptr ss:[ebp-0x2e8],0x0 + * 011411c0 0f44b5 18fdffff cmove esi,dword ptr ss:[ebp-0x2e8] + * 011411c7 80bb f2000000 00 cmp byte ptr ds:[ebx+0xf2],0x0 + * 011411ce 89b5 30fdffff mov dword ptr ss:[ebp-0x2d0],esi + * 011411d4 8bb5 3cfdffff mov esi,dword ptr ss:[ebp-0x2c4] + * 011411da 8985 48fdffff mov dword ptr ss:[ebp-0x2b8],eax + * 011411e0 8b85 30fdffff mov eax,dword ptr ss:[ebp-0x2d0] + * 011411e6 89b5 40fdffff mov dword ptr ss:[ebp-0x2c0],esi + * 011411ec 8bb5 2cfdffff mov esi,dword ptr ss:[ebp-0x2d4] + * 011411f2 8985 4cfdffff mov dword ptr ss:[ebp-0x2b4],eax + * 011411f8 8b85 24fdffff mov eax,dword ptr ss:[ebp-0x2dc] + * 011411fe 89b5 44fdffff mov dword ptr ss:[ebp-0x2bc],esi + * 01141204 8985 50fdffff mov dword ptr ss:[ebp-0x2b0],eax + * 0114120a 898d 54fdffff mov dword ptr ss:[ebp-0x2ac],ecx + * 01141210 8995 58fdffff mov dword ptr ss:[ebp-0x2a8],edx + * 01141216 74 19 je short .01141231 + * 01141218 8b43 58 mov eax,dword ptr ds:[ebx+0x58] + * 0114121b 8983 f4000000 mov dword ptr ds:[ebx+0xf4],eax + * 01141221 8b43 5c mov eax,dword ptr ds:[ebx+0x5c] + * 01141224 8983 f8000000 mov dword ptr ds:[ebx+0xf8],eax + * 0114122a c683 f2000000 00 mov byte ptr ds:[ebx+0xf2],0x0 + * 01141231 8b43 6c mov eax,dword ptr ds:[ebx+0x6c] + * 01141234 0385 28fdffff add eax,dword ptr ss:[ebp-0x2d8] + * 0114123a 0143 58 add dword ptr ds:[ebx+0x58],eax + * 0114123d 8b85 3cfdffff mov eax,dword ptr ss:[ebp-0x2c4] + * 01141243 8b4b 58 mov ecx,dword ptr ds:[ebx+0x58] + * 01141246 85c0 test eax,eax + * 01141248 75 51 jnz short .0114129b + * 0114124a 81fe 0c300000 cmp esi,0x300c ; jichi: hook here, utf16 character is in esi + * 01141250 74 10 je short .01141262 + * 01141252 81fe 0e300000 cmp esi,0x300e + * 01141258 74 08 je short .01141262 + * 0114125a 81fe 08ff0000 cmp esi,0xff08 + * 01141260 75 39 jnz short .0114129b + * 01141262 80bb f1000000 00 cmp byte ptr ds:[ebx+0xf1],0x0 + * 01141269 74 19 je short .01141284 + * 0114126b 898b e0000000 mov dword ptr ds:[ebx+0xe0],ecx + * 01141271 66:89b3 e4000000 mov word ptr ds:[ebx+0xe4],si + * 01141278 c783 e8000000 01>mov dword ptr ds:[ebx+0xe8],0x1 + * 01141282 eb 17 jmp short .0114129b + * 01141284 0fb783 e4000000 movzx eax,word ptr ds:[ebx+0xe4] + * 0114128b 3bf0 cmp esi,eax + * 0114128d 8b85 3cfdffff mov eax,dword ptr ss:[ebp-0x2c4] + * 01141293 75 06 jnz short .0114129b + * 01141295 ff83 e8000000 inc dword ptr ds:[ebx+0xe8] + * 0114129b 8b93 e8000000 mov edx,dword ptr ds:[ebx+0xe8] + * 011412a1 85d2 test edx,edx + * 011412a3 7e 78 jle short .0114131d + * 011412a5 85c0 test eax,eax + * 011412a7 75 52 jnz short .011412fb + * 011412a9 0fb78b e4000000 movzx ecx,word ptr ds:[ebx+0xe4] + * 011412b0 b8 0c300000 mov eax,0x300c + * 011412b5 66:3bc8 cmp cx,ax + * 011412b8 75 11 jnz short .011412cb + * 011412ba 81fe 0d300000 cmp esi,0x300d + * 011412c0 75 09 jnz short .011412cb + * 011412c2 8d42 ff lea eax,dword ptr ds:[edx-0x1] + * 011412c5 8983 e8000000 mov dword ptr ds:[ebx+0xe8],eax + * 011412cb b8 0e300000 mov eax,0x300e + * 011412d0 66:3bc8 cmp cx,ax + * 011412d3 75 0e jnz short .011412e3 + * 011412d5 81fe 0f300000 cmp esi,0x300f + * 011412db 75 06 jnz short .011412e3 + * 011412dd ff8b e8000000 dec dword ptr ds:[ebx+0xe8] + * 011412e3 b8 08ff0000 mov eax,0xff08 + * 011412e8 66:3bc8 cmp cx,ax + * 011412eb 75 0e jnz short .011412fb + * 011412ed 81fe 09ff0000 cmp esi,0xff09 + * 011412f3 75 06 jnz short .011412fb + * 011412f5 ff8b e8000000 dec dword ptr ds:[ebx+0xe8] + * 011412fb 83bb e8000000 00 cmp dword ptr ds:[ebx+0xe8],0x0 + * 01141302 75 19 jnz short .0114131d + * 01141304 33c0 xor eax,eax + * 01141306 c783 e0000000 00>mov dword ptr ds:[ebx+0xe0],0x0 + * 01141310 66:8983 e4000000 mov word ptr ds:[ebx+0xe4],ax + * 01141317 8983 e8000000 mov dword ptr ds:[ebx+0xe8],eax + * 0114131d 8d85 40fdffff lea eax,dword ptr ss:[ebp-0x2c0] + * 01141323 50 push eax + * 01141324 8d8b 0c010000 lea ecx,dword ptr ds:[ebx+0x10c] + * 0114132a e8 31a6fcff call .0110b960 + * 0114132f 83bd 3cfdffff 00 cmp dword ptr ss:[ebp-0x2c4],0x0 + * 01141336 8bb5 34fdffff mov esi,dword ptr ss:[ebp-0x2cc] + * 0114133c 75 13 jnz short .01141351 + * 0114133e ffb5 2cfdffff push dword ptr ss:[ebp-0x2d4] + * 01141344 8d8b a8000000 lea ecx,dword ptr ds:[ebx+0xa8] + * 0114134a e8 010a0000 call .01141d50 + * 0114134f eb 64 jmp short .011413b5 + * 01141351 33c0 xor eax,eax + * 01141353 c745 ec 07000000 mov dword ptr ss:[ebp-0x14],0x7 + * 0114135a c745 e8 00000000 mov dword ptr ss:[ebp-0x18],0x0 + * 01141361 66:8945 d8 mov word ptr ss:[ebp-0x28],ax + * 01141365 3bfe cmp edi,esi + * 01141367 74 10 je short .01141379 + * 01141369 8bc6 mov eax,esi + * 0114136b 8d4d d8 lea ecx,dword ptr ss:[ebp-0x28] + * 0114136e 2bc7 sub eax,edi + * 01141370 d1f8 sar eax,1 + * 01141372 50 push eax + * 01141373 57 push edi + * 01141374 e8 b7daf2ff call .0106ee30 + * 01141379 6a ff push -0x1 + * 0114137b 6a 00 push 0x0 + * 0114137d 8d45 d8 lea eax,dword ptr ss:[ebp-0x28] + * 01141380 c645 fc 02 mov byte ptr ss:[ebp-0x4],0x2 + * 01141384 50 push eax + * 01141385 8d8b a8000000 lea ecx,dword ptr ds:[ebx+0xa8] + * 0114138b e8 205cf3ff call .01076fb0 + * 01141390 837d ec 08 cmp dword ptr ss:[ebp-0x14],0x8 + * 01141394 72 0b jb short .011413a1 + * 01141396 ff75 d8 push dword ptr ss:[ebp-0x28] + * 01141399 e8 fccb0e00 call .0122df9a + * 0114139e 83c4 04 add esp,0x4 + * 011413a1 33c0 xor eax,eax + * 011413a3 c745 ec 07000000 mov dword ptr ss:[ebp-0x14],0x7 + * 011413aa c745 e8 00000000 mov dword ptr ss:[ebp-0x18],0x0 + * 011413b1 66:8945 d8 mov word ptr ss:[ebp-0x28],ax + * 011413b5 c683 f1000000 00 mov byte ptr ds:[ebx+0xf1],0x0 + * 011413bc 8d8d 90feffff lea ecx,dword ptr ss:[ebp-0x170] + * 011413c2 c745 fc 03000000 mov dword ptr ss:[ebp-0x4],0x3 + * 011413c9 e8 42bb0800 call .011ccf10 + * 011413ce 8d8d 60fdffff lea ecx,dword ptr ss:[ebp-0x2a0] + * 011413d4 c745 fc ffffffff mov dword ptr ss:[ebp-0x4],-0x1 + * 011413db e8 30bb0800 call .011ccf10 + * 011413e0 8bbd 38fdffff mov edi,dword ptr ss:[ebp-0x2c8] + * 011413e6 ^e9 c5fbffff jmp .01140fb0 + * 011413eb 8b9d 1cfdffff mov ebx,dword ptr ss:[ebp-0x2e4] + * 011413f1 85db test ebx,ebx + * 011413f3 74 6a je short .0114145f + * 011413f5 8b8d 38fdffff mov ecx,dword ptr ss:[ebp-0x2c8] + * 011413fb 8379 14 08 cmp dword ptr ds:[ecx+0x14],0x8 + * 011413ff 72 02 jb short .01141403 + * 01141401 8b09 mov ecx,dword ptr ds:[ecx] + * 01141403 8b85 20fdffff mov eax,dword ptr ss:[ebp-0x2e0] + * 01141409 c745 d4 07000000 mov dword ptr ss:[ebp-0x2c],0x7 + * 01141410 c745 d0 00000000 mov dword ptr ss:[ebp-0x30],0x0 + * 01141417 8b00 mov eax,dword ptr ds:[eax] + * 01141419 8d0441 lea eax,dword ptr ds:[ecx+eax*2] + * 0114141c 33c9 xor ecx,ecx + * 0114141e 66:894d c0 mov word ptr ss:[ebp-0x40],cx + * 01141422 3bf8 cmp edi,eax + * 01141424 74 0e je short .01141434 + * 01141426 2bc7 sub eax,edi + * 01141428 8d4d c0 lea ecx,dword ptr ss:[ebp-0x40] + * 0114142b d1f8 sar eax,1 + * 0114142d 50 push eax + * 0114142e 57 push edi + * 0114142f e8 fcd9f2ff call .0106ee30 + * 01141434 8d45 c0 lea eax,dword ptr ss:[ebp-0x40] + * 01141437 c745 fc 00000000 mov dword ptr ss:[ebp-0x4],0x0 + * 0114143e 3bd8 cmp ebx,eax + * 01141440 74 0c je short .0114144e + * 01141442 6a ff push -0x1 + * 01141444 6a 00 push 0x0 + * 01141446 50 push eax + * 01141447 8bcb mov ecx,ebx + * 01141449 e8 c2def2ff call .0106f310 + * 0114144e 837d d4 08 cmp dword ptr ss:[ebp-0x2c],0x8 + * 01141452 72 0b jb short .0114145f + * 01141454 ff75 c0 push dword ptr ss:[ebp-0x40] + * 01141457 e8 3ecb0e00 call .0122df9a + * 0114145c 83c4 04 add esp,0x4 + * 0114145f 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 01141462 64:890d 00000000 mov dword ptr fs:[0],ecx + * 01141469 59 pop ecx + * 0114146a 5f pop edi + * 0114146b 5e pop esi + * 0114146c 5b pop ebx + * 0114146d 8b4d f0 mov ecx,dword ptr ss:[ebp-0x10] + * 01141470 33cd xor ecx,ebp + * 01141472 e8 14cb0e00 call .0122df8b + * 01141477 8be5 mov esp,ebp + * 01141479 5d pop ebp + * 0114147a c2 0800 retn 0x8 + * 0114147d cc int3 + * 0114147e cc int3 + * + * In AngleBeats, base = 0x09a0000 + * 00B6B87C CC INT3 + * 00B6B87D CC INT3 + * 00B6B87E CC INT3 + * 00B6B87F CC INT3 + * 00B6B880 55 PUSH EBP + * 00B6B881 8BEC MOV EBP,ESP + * 00B6B883 6A FF PUSH -0x1 + * 00B6B885 68 7964ED00 PUSH .00ED6479 + * 00B6B88A 64:A1 00000000 MOV EAX,DWORD PTR FS:[0] + * 00B6B890 50 PUSH EAX + * 00B6B891 81EC 1C040000 SUB ESP,0x41C + * 00B6B897 A1 E0A4F800 MOV EAX,DWORD PTR DS:[0xF8A4E0] + * 00B6B89C 33C5 XOR EAX,EBP + * 00B6B89E 8945 F0 MOV DWORD PTR SS:[EBP-0x10],EAX + * 00B6B8A1 53 PUSH EBX + * 00B6B8A2 56 PUSH ESI + * 00B6B8A3 57 PUSH EDI + * 00B6B8A4 50 PUSH EAX + * 00B6B8A5 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-0xC] + * 00B6B8A8 64:A3 00000000 MOV DWORD PTR FS:[0],EAX + * 00B6B8AE 8BD9 MOV EBX,ECX + * 00B6B8B0 8B7D 08 MOV EDI,DWORD PTR SS:[EBP+0x8] + * 00B6B8B3 837F 10 00 CMP DWORD PTR DS:[EDI+0x10],0x0 + * 00B6B8B7 8B45 0C MOV EAX,DWORD PTR SS:[EBP+0xC] + * 00B6B8BA 8985 E0FBFFFF MOV DWORD PTR SS:[EBP-0x420],EAX + * 00B6B8C0 8D47 10 LEA EAX,DWORD PTR DS:[EDI+0x10] + * 00B6B8C3 89BD FCFBFFFF MOV DWORD PTR SS:[EBP-0x404],EDI + * 00B6B8C9 8985 F0FBFFFF MOV DWORD PTR SS:[EBP-0x410],EAX + * 00B6B8CF 0F84 31060000 JE .00B6BF06 + * 00B6B8D5 8B8B 1C010000 MOV ECX,DWORD PTR DS:[EBX+0x11C] + * 00B6B8DB B8 71F8428A MOV EAX,0x8A42F871 + * 00B6B8E0 2B8B 18010000 SUB ECX,DWORD PTR DS:[EBX+0x118] + * 00B6B8E6 F7E9 IMUL ECX + * 00B6B8E8 8B85 F0FBFFFF MOV EAX,DWORD PTR SS:[EBP-0x410] + * 00B6B8EE 03D1 ADD EDX,ECX + * 00B6B8F0 8B8B 20010000 MOV ECX,DWORD PTR DS:[EBX+0x120] + * 00B6B8F6 2B8B 18010000 SUB ECX,DWORD PTR DS:[EBX+0x118] + * 00B6B8FC C1FA 09 SAR EDX,0x9 + * 00B6B8FF 8BF2 MOV ESI,EDX + * 00B6B901 C1EE 1F SHR ESI,0x1F + * 00B6B904 03F2 ADD ESI,EDX + * 00B6B906 0330 ADD ESI,DWORD PTR DS:[EAX] + * 00B6B908 B8 71F8428A MOV EAX,0x8A42F871 + * 00B6B90D F7E9 IMUL ECX + * 00B6B90F 03D1 ADD EDX,ECX + * 00B6B911 C1FA 09 SAR EDX,0x9 + * 00B6B914 8BC2 MOV EAX,EDX + * 00B6B916 C1E8 1F SHR EAX,0x1F + * 00B6B919 03C2 ADD EAX,EDX + * 00B6B91B 3BC6 CMP EAX,ESI + * 00B6B91D 73 1E JNB SHORT .00B6B93D + * 00B6B91F 81FE 7C214500 CMP ESI,0x45217C + * 00B6B925 76 0A JBE SHORT .00B6B931 + * 00B6B927 68 C031F200 PUSH .00F231C0 ; ASCII "vector too long" + * 00B6B92C E8 D2FC0E00 CALL .00C5B603 + * 00B6B931 56 PUSH ESI + * 00B6B932 8D8B 18010000 LEA ECX,DWORD PTR DS:[EBX+0x118] + * 00B6B938 E8 A38DFCFF CALL .00B346E0 + * 00B6B93D 837F 14 08 CMP DWORD PTR DS:[EDI+0x14],0x8 + * 00B6B941 72 04 JB SHORT .00B6B947 + * 00B6B943 8B37 MOV ESI,DWORD PTR DS:[EDI] + * 00B6B945 EB 02 JMP SHORT .00B6B949 + * 00B6B947 8BF7 MOV ESI,EDI + * 00B6B949 89B5 F8FBFFFF MOV DWORD PTR SS:[EBP-0x408],ESI + * 00B6B94F 90 NOP + * 00B6B950 8B57 14 MOV EDX,DWORD PTR DS:[EDI+0x14] + * 00B6B953 83FA 08 CMP EDX,0x8 + * 00B6B956 72 04 JB SHORT .00B6B95C + * 00B6B958 8B07 MOV EAX,DWORD PTR DS:[EDI] + * 00B6B95A EB 02 JMP SHORT .00B6B95E + * 00B6B95C 8BC7 MOV EAX,EDI + * 00B6B95E 8B8D F0FBFFFF MOV ECX,DWORD PTR SS:[EBP-0x410] + * 00B6B964 8B09 MOV ECX,DWORD PTR DS:[ECX] + * 00B6B966 03C9 ADD ECX,ECX + * 00B6B968 03C1 ADD EAX,ECX + * 00B6B96A 3BF0 CMP ESI,EAX + * 00B6B96C 0F84 94050000 JE .00B6BF06 + * 00B6B972 8B85 FCFBFFFF MOV EAX,DWORD PTR SS:[EBP-0x404] + * 00B6B978 8BFE MOV EDI,ESI + * 00B6B97A C785 00FCFFFF 00>MOV DWORD PTR SS:[EBP-0x400],0x0 + * 00B6B984 C785 E8FBFFFF FF>MOV DWORD PTR SS:[EBP-0x418],-0x1 + * 00B6B98E 83FA 08 CMP EDX,0x8 + * 00B6B991 72 02 JB SHORT .00B6B995 + * 00B6B993 8B00 MOV EAX,DWORD PTR DS:[EAX] + * 00B6B995 03C1 ADD EAX,ECX + * 00B6B997 8D95 00FCFFFF LEA EDX,DWORD PTR SS:[EBP-0x400] + * 00B6B99D 8D8D E8FBFFFF LEA ECX,DWORD PTR SS:[EBP-0x418] + * 00B6B9A3 51 PUSH ECX + * 00B6B9A4 50 PUSH EAX + * 00B6B9A5 8D8D F8FBFFFF LEA ECX,DWORD PTR SS:[EBP-0x408] + * 00B6B9AB E8 5025FBFF CALL .00B1DF00 + * 00B6B9B0 8BB5 E8FBFFFF MOV ESI,DWORD PTR SS:[EBP-0x418] + * 00B6B9B6 83C4 08 ADD ESP,0x8 + * 00B6B9B9 83FE 0A CMP ESI,0xA + * 00B6B9BC 75 18 JNZ SHORT .00B6B9D6 + * 00B6B9BE 8BCB MOV ECX,EBX + * 00B6B9C0 E8 FB070000 CALL .00B6C1C0 + * 00B6B9C5 8BB5 F8FBFFFF MOV ESI,DWORD PTR SS:[EBP-0x408] + * 00B6B9CB 8BBD FCFBFFFF MOV EDI,DWORD PTR SS:[EBP-0x404] + * 00B6B9D1 ^E9 7AFFFFFF JMP .00B6B950 + * 00B6B9D6 83FE 07 CMP ESI,0x7 + * 00B6B9D9 75 38 JNZ SHORT .00B6BA13 + * 00B6B9DB 33C0 XOR EAX,EAX + * 00B6B9DD C783 EC000000 00>MOV DWORD PTR DS:[EBX+0xEC],0x0 + * 00B6B9E7 8BCB MOV ECX,EBX + * 00B6B9E9 66:8983 F0000000 MOV WORD PTR DS:[EBX+0xF0],AX + * 00B6B9F0 8983 F4000000 MOV DWORD PTR DS:[EBX+0xF4],EAX + * 00B6B9F6 E8 C5070000 CALL .00B6C1C0 + * 00B6B9FB 8BB5 F8FBFFFF MOV ESI,DWORD PTR SS:[EBP-0x408] + * 00B6BA01 8BBD FCFBFFFF MOV EDI,DWORD PTR SS:[EBP-0x404] + * 00B6BA07 C683 FD000000 01 MOV BYTE PTR DS:[EBX+0xFD],0x1 + * 00B6BA0E ^E9 3DFFFFFF JMP .00B6B950 + * 00B6BA13 8B85 00FCFFFF MOV EAX,DWORD PTR SS:[EBP-0x400] + * 00B6BA19 85C0 TEST EAX,EAX + * 00B6BA1B 75 3A JNZ SHORT .00B6BA57 + * 00B6BA1D 85F6 TEST ESI,ESI + * 00B6BA1F 0F84 BE000000 JE .00B6BAE3 + * 00B6BA25 85C0 TEST EAX,EAX + * 00B6BA27 75 2E JNZ SHORT .00B6BA57 + * 00B6BA29 A1 486A2C05 MOV EAX,DWORD PTR DS:[0x52C6A48] + * 00B6BA2E A8 01 TEST AL,0x1 + * 00B6BA30 75 0D JNZ SHORT .00B6BA3F + * 00B6BA32 83C8 01 OR EAX,0x1 + * 00B6BA35 A3 486A2C05 MOV DWORD PTR DS:[0x52C6A48],EAX + * 00B6BA3A E8 B15F0B00 CALL .00C219F0 + * 00B6BA3F 0FB7C6 MOVZX EAX,SI + * 00B6BA42 80B8 506A2C05 01 CMP BYTE PTR DS:[EAX+0x52C6A50],0x1 + * 00B6BA49 75 0C JNZ SHORT .00B6BA57 + * 00B6BA4B 8B43 6C MOV EAX,DWORD PTR DS:[EBX+0x6C] + * 00B6BA4E 99 CDQ + * 00B6BA4F 2BC2 SUB EAX,EDX + * 00B6BA51 8BC8 MOV ECX,EAX + * 00B6BA53 D1F9 SAR ECX,1 + * 00B6BA55 EB 03 JMP SHORT .00B6BA5A + * 00B6BA57 8B4B 6C MOV ECX,DWORD PTR DS:[EBX+0x6C] + * 00B6BA5A 8B15 9C5DFA00 MOV EDX,DWORD PTR DS:[0xFA5D9C] + * 00B6BA60 898D ECFBFFFF MOV DWORD PTR SS:[EBP-0x414],ECX + * 00B6BA66 83BA 84CF0000 01 CMP DWORD PTR DS:[EDX+0xCF84],0x1 + * 00B6BA6D 75 26 JNZ SHORT .00B6BA95 + * 00B6BA6F 8B43 60 MOV EAX,DWORD PTR DS:[EBX+0x60] + * 00B6BA72 03C1 ADD EAX,ECX + * 00B6BA74 8B8B AC000000 MOV ECX,DWORD PTR DS:[EBX+0xAC] + * 00B6BA7A 8985 04FCFFFF MOV DWORD PTR SS:[EBP-0x3FC],EAX + * 00B6BA80 8B43 18 MOV EAX,DWORD PTR DS:[EBX+0x18] + * 00B6BA83 03C1 ADD EAX,ECX + * 00B6BA85 3985 04FCFFFF CMP DWORD PTR SS:[EBP-0x3FC],EAX + * 00B6BA8B 7F 39 JG SHORT .00B6BAC6 + * 00B6BA8D 398D 04FCFFFF CMP DWORD PTR SS:[EBP-0x3FC],ECX + * 00B6BA93 EB 24 JMP SHORT .00B6BAB9 + * 00B6BA95 8B43 5C MOV EAX,DWORD PTR DS:[EBX+0x5C] + * 00B6BA98 03C1 ADD EAX,ECX + * 00B6BA9A 8B8B A8000000 MOV ECX,DWORD PTR DS:[EBX+0xA8] + * 00B6BAA0 8985 04FCFFFF MOV DWORD PTR SS:[EBP-0x3FC],EAX + * 00B6BAA6 8B43 18 MOV EAX,DWORD PTR DS:[EBX+0x18] + * 00B6BAA9 03C1 ADD EAX,ECX + * 00B6BAAB 3985 04FCFFFF CMP DWORD PTR SS:[EBP-0x3FC],EAX + * 00B6BAB1 7F 13 JG SHORT .00B6BAC6 + * 00B6BAB3 398D 04FCFFFF CMP DWORD PTR SS:[EBP-0x3FC],ECX + * 00B6BAB9 7E 3F JLE SHORT .00B6BAFA + * 00B6BABB 8BCE MOV ECX,ESI + * 00B6BABD E8 EEF9FFFF CALL .00B6B4B0 + * 00B6BAC2 84C0 TEST AL,AL + * 00B6BAC4 75 34 JNZ SHORT .00B6BAFA + * 00B6BAC6 8BCB MOV ECX,EBX + * 00B6BAC8 E8 F3060000 CALL .00B6C1C0 + * 00B6BACD 83BD 00FCFFFF 00 CMP DWORD PTR SS:[EBP-0x400],0x0 + * 00B6BAD4 75 1E JNZ SHORT .00B6BAF4 + * 00B6BAD6 83FE 20 CMP ESI,0x20 + * 00B6BAD9 74 08 JE SHORT .00B6BAE3 + * 00B6BADB 81FE 00300000 CMP ESI,0x3000 + * 00B6BAE1 75 11 JNZ SHORT .00B6BAF4 + * 00B6BAE3 8BB5 F8FBFFFF MOV ESI,DWORD PTR SS:[EBP-0x408] + * 00B6BAE9 8BBD FCFBFFFF MOV EDI,DWORD PTR SS:[EBP-0x404] + * 00B6BAEF ^E9 5CFEFFFF JMP .00B6B950 + * 00B6BAF4 8B15 9C5DFA00 MOV EDX,DWORD PTR DS:[0xFA5D9C] + * 00B6BAFA 83BA 84CF0000 01 CMP DWORD PTR DS:[EDX+0xCF84],0x1 + * 00B6BB01 75 66 JNZ SHORT .00B6BB69 + * 00B6BB03 8B83 A8000000 MOV EAX,DWORD PTR DS:[EBX+0xA8] + * 00B6BB09 F7D8 NEG EAX + * 00B6BB0B 3943 5C CMP DWORD PTR DS:[EBX+0x5C],EAX + * 00B6BB0E 7F 68 JG SHORT .00B6BB78 + * 00B6BB10 8B9D E0FBFFFF MOV EBX,DWORD PTR SS:[EBP-0x420] + * 00B6BB16 85DB TEST EBX,EBX + * 00B6BB18 0F84 E8030000 JE .00B6BF06 + * 00B6BB1E 8B8D FCFBFFFF MOV ECX,DWORD PTR SS:[EBP-0x404] + * 00B6BB24 8379 14 08 CMP DWORD PTR DS:[ECX+0x14],0x8 + * 00B6BB28 72 02 JB SHORT .00B6BB2C + * 00B6BB2A 8B09 MOV ECX,DWORD PTR DS:[ECX] + * 00B6BB2C 8B85 F0FBFFFF MOV EAX,DWORD PTR SS:[EBP-0x410] + * 00B6BB32 C745 EC 07000000 MOV DWORD PTR SS:[EBP-0x14],0x7 + * 00B6BB39 C745 E8 00000000 MOV DWORD PTR SS:[EBP-0x18],0x0 + * 00B6BB40 8B00 MOV EAX,DWORD PTR DS:[EAX] + * 00B6BB42 8D0441 LEA EAX,DWORD PTR DS:[ECX+EAX*2] + * 00B6BB45 33C9 XOR ECX,ECX + * 00B6BB47 66:894D D8 MOV WORD PTR SS:[EBP-0x28],CX + * 00B6BB4B 3BF8 CMP EDI,EAX + * 00B6BB4D 74 0E JE SHORT .00B6BB5D + * 00B6BB4F 2BC7 SUB EAX,EDI + * 00B6BB51 8D4D D8 LEA ECX,DWORD PTR SS:[EBP-0x28] + * 00B6BB54 D1F8 SAR EAX,1 + * 00B6BB56 50 PUSH EAX + * 00B6BB57 57 PUSH EDI + * 00B6BB58 E8 E334F2FF CALL .00A8F040 + * 00B6BB5D C745 FC 00000000 MOV DWORD PTR SS:[EBP-0x4],0x0 + * 00B6BB64 E9 82030000 JMP .00B6BEEB + * 00B6BB69 8B43 60 MOV EAX,DWORD PTR DS:[EBX+0x60] + * 00B6BB6C 3B83 AC000000 CMP EAX,DWORD PTR DS:[EBX+0xAC] + * 00B6BB72 0F8D 23030000 JGE .00B6BE9B + * 00B6BB78 8D8D 08FCFFFF LEA ECX,DWORD PTR SS:[EBP-0x3F8] + * 00B6BB7E E8 EDDEFFFF CALL .00B69A70 + * 00B6BB83 C745 FC 02000000 MOV DWORD PTR SS:[EBP-0x4],0x2 + * 00B6BB8A 8B43 78 MOV EAX,DWORD PTR DS:[EBX+0x78] + * 00B6BB8D 8B15 C05DFA00 MOV EDX,DWORD PTR DS:[0xFA5DC0] + * 00B6BB93 8985 F4FBFFFF MOV DWORD PTR SS:[EBP-0x40C],EAX + * 00B6BB99 83F8 FF CMP EAX,-0x1 + * 00B6BB9C 75 23 JNZ SHORT .00B6BBC1 + * 00B6BB9E 80BA 60010000 00 CMP BYTE PTR DS:[EDX+0x160],0x0 + * 00B6BBA5 74 11 JE SHORT .00B6BBB8 + * 00B6BBA7 8B83 E0000000 MOV EAX,DWORD PTR DS:[EBX+0xE0] + * 00B6BBAD 8985 F4FBFFFF MOV DWORD PTR SS:[EBP-0x40C],EAX + * 00B6BBB3 83F8 FF CMP EAX,-0x1 + * 00B6BBB6 75 09 JNZ SHORT .00B6BBC1 + * 00B6BBB8 8B43 24 MOV EAX,DWORD PTR DS:[EBX+0x24] + * 00B6BBBB 8985 F4FBFFFF MOV DWORD PTR SS:[EBP-0x40C],EAX + * 00B6BBC1 8B4B 7C MOV ECX,DWORD PTR DS:[EBX+0x7C] + * 00B6BBC4 898D E4FBFFFF MOV DWORD PTR SS:[EBP-0x41C],ECX + * 00B6BBCA 83F9 FF CMP ECX,-0x1 + * 00B6BBCD 75 23 JNZ SHORT .00B6BBF2 + * 00B6BBCF 80BA 60010000 00 CMP BYTE PTR DS:[EDX+0x160],0x0 + * 00B6BBD6 74 11 JE SHORT .00B6BBE9 + * 00B6BBD8 8B8B E4000000 MOV ECX,DWORD PTR DS:[EBX+0xE4] + * 00B6BBDE 898D E4FBFFFF MOV DWORD PTR SS:[EBP-0x41C],ECX + * 00B6BBE4 83F9 FF CMP ECX,-0x1 + * 00B6BBE7 75 09 JNZ SHORT .00B6BBF2 + * 00B6BBE9 8B43 28 MOV EAX,DWORD PTR DS:[EBX+0x28] + * 00B6BBEC 8985 E4FBFFFF MOV DWORD PTR SS:[EBP-0x41C],EAX + * 00B6BBF2 8B83 80000000 MOV EAX,DWORD PTR DS:[EBX+0x80] + * 00B6BBF8 8985 04FCFFFF MOV DWORD PTR SS:[EBP-0x3FC],EAX + * 00B6BBFE 83F8 FF CMP EAX,-0x1 + * 00B6BC01 75 23 JNZ SHORT .00B6BC26 + * 00B6BC03 80BA 60010000 00 CMP BYTE PTR DS:[EDX+0x160],0x0 + * 00B6BC0A 74 11 JE SHORT .00B6BC1D + * 00B6BC0C 8B83 E8000000 MOV EAX,DWORD PTR DS:[EBX+0xE8] + * 00B6BC12 8985 04FCFFFF MOV DWORD PTR SS:[EBP-0x3FC],EAX + * 00B6BC18 83F8 FF CMP EAX,-0x1 + * 00B6BC1B 75 09 JNZ SHORT .00B6BC26 + * 00B6BC1D 8B43 2C MOV EAX,DWORD PTR DS:[EBX+0x2C] + * 00B6BC20 8985 04FCFFFF MOV DWORD PTR SS:[EBP-0x3FC],EAX + * 00B6BC26 8B53 68 MOV EDX,DWORD PTR DS:[EBX+0x68] + * 00B6BC29 0353 60 ADD EDX,DWORD PTR DS:[EBX+0x60] + * 00B6BC2C 8B4B 5C MOV ECX,DWORD PTR DS:[EBX+0x5C] + * 00B6BC2F 034B 64 ADD ECX,DWORD PTR DS:[EBX+0x64] + * 00B6BC32 83BD 00FCFFFF 01 CMP DWORD PTR SS:[EBP-0x400],0x1 + * 00B6BC39 8BB5 F4FBFFFF MOV ESI,DWORD PTR SS:[EBP-0x40C] + * 00B6BC3F 8B43 6C MOV EAX,DWORD PTR DS:[EBX+0x6C] + * 00B6BC42 C785 DCFBFFFF 00>MOV DWORD PTR SS:[EBP-0x424],0x0 + * 00B6BC4C 0F44B5 DCFBFFFF CMOVE ESI,DWORD PTR SS:[EBP-0x424] + * 00B6BC53 80BB FE000000 00 CMP BYTE PTR DS:[EBX+0xFE],0x0 + * 00B6BC5A 89B5 F4FBFFFF MOV DWORD PTR SS:[EBP-0x40C],ESI + * 00B6BC60 8BB5 00FCFFFF MOV ESI,DWORD PTR SS:[EBP-0x400] + * 00B6BC66 8985 10FCFFFF MOV DWORD PTR SS:[EBP-0x3F0],EAX + * 00B6BC6C 8B85 F4FBFFFF MOV EAX,DWORD PTR SS:[EBP-0x40C] + * 00B6BC72 8985 14FCFFFF MOV DWORD PTR SS:[EBP-0x3EC],EAX + * 00B6BC78 8B85 E4FBFFFF MOV EAX,DWORD PTR SS:[EBP-0x41C] + * 00B6BC7E 89B5 08FCFFFF MOV DWORD PTR SS:[EBP-0x3F8],ESI + * 00B6BC84 8BB5 E8FBFFFF MOV ESI,DWORD PTR SS:[EBP-0x418] + * 00B6BC8A 8985 18FCFFFF MOV DWORD PTR SS:[EBP-0x3E8],EAX + * 00B6BC90 8B85 04FCFFFF MOV EAX,DWORD PTR SS:[EBP-0x3FC] + * 00B6BC96 89B5 0CFCFFFF MOV DWORD PTR SS:[EBP-0x3F4],ESI + * 00B6BC9C 8985 1CFCFFFF MOV DWORD PTR SS:[EBP-0x3E4],EAX + * 00B6BCA2 898D 20FCFFFF MOV DWORD PTR SS:[EBP-0x3E0],ECX + * 00B6BCA8 8995 24FCFFFF MOV DWORD PTR SS:[EBP-0x3DC],EDX + * 00B6BCAE 74 19 JE SHORT .00B6BCC9 + * 00B6BCB0 8B43 5C MOV EAX,DWORD PTR DS:[EBX+0x5C] + * 00B6BCB3 8983 00010000 MOV DWORD PTR DS:[EBX+0x100],EAX + * 00B6BCB9 8B43 60 MOV EAX,DWORD PTR DS:[EBX+0x60] + * 00B6BCBC 8983 04010000 MOV DWORD PTR DS:[EBX+0x104],EAX + * 00B6BCC2 C683 FE000000 00 MOV BYTE PTR DS:[EBX+0xFE],0x0 + * 00B6BCC9 A1 9C5DFA00 MOV EAX,DWORD PTR DS:[0xFA5D9C] + * 00B6BCCE 83B8 84CF0000 01 CMP DWORD PTR DS:[EAX+0xCF84],0x1 + * 00B6BCD5 8B43 70 MOV EAX,DWORD PTR DS:[EBX+0x70] + * 00B6BCD8 75 0B JNZ SHORT .00B6BCE5 + * 00B6BCDA 0385 ECFBFFFF ADD EAX,DWORD PTR SS:[EBP-0x414] + * 00B6BCE0 0143 60 ADD DWORD PTR DS:[EBX+0x60],EAX + * 00B6BCE3 EB 09 JMP SHORT .00B6BCEE + * 00B6BCE5 0385 ECFBFFFF ADD EAX,DWORD PTR SS:[EBP-0x414] + * 00B6BCEB 0143 5C ADD DWORD PTR DS:[EBX+0x5C],EAX + * 00B6BCEE 8B8D 00FCFFFF MOV ECX,DWORD PTR SS:[EBP-0x400] + * 00B6BCF4 85C9 TEST ECX,ECX + * 00B6BCF6 75 42 JNZ SHORT .00B6BD3A + * 00B6BCF8 81FE 0C300000 CMP ESI,0x300C ; jichi: type2 found here + * 00B6BCFE 74 10 JE SHORT .00B6BD10 + * 00B6BD00 81FE 0E300000 CMP ESI,0x300E + * 00B6BD06 74 08 JE SHORT .00B6BD10 + * 00B6BD08 81FE 08FF0000 CMP ESI,0xFF08 + * 00B6BD0E 75 2A JNZ SHORT .00B6BD3A + * 00B6BD10 80BB FD000000 00 CMP BYTE PTR DS:[EBX+0xFD],0x0 + * 00B6BD17 74 10 JE SHORT .00B6BD29 + * 00B6BD19 56 PUSH ESI + */ +bool InsertSiglus2Hook() +{ + //const BYTE bytes[] = { // size = 14 + // 0x01,0x53, 0x58, // 0153 58 add dword ptr ds:[ebx+58],edx + // 0x8b,0x95, 0x34,0xfd,0xff,0xff, // 8b95 34fdffff mov edx,dword ptr ss:[ebp-2cc] + // 0x8b,0x43, 0x58, // 8b43 58 mov eax,dword ptr ds:[ebx+58] + // 0x3b,0xd7 // 3bd7 cmp edx,edi ; hook here + //}; + //enum { cur_ins_size = 2 }; + //enum { addr_offset = sizeof(bytes) - cur_ins_size }; // = 14 - 2 = 12, current inst is the last one + + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr; + { // type 1 + const BYTE bytes[] = { + 0x3b,0xd7, // cmp edx,edi ; hook here + 0x75,0x4b // jnz short + }; + //enum { addr_offset = 0 }; + addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + if (addr) + ConsoleOutput("vnreng:Siglus2: type 1 pattern found"); + } + if (!addr) { + // 81fe0c300000 + const BYTE bytes[] = { + 0x81,0xfe, 0x0c,0x30,0x00,0x00 // 0114124a 81fe 0c300000 cmp esi,0x300c ; jichi: hook here + }; + //enum { addr_offset = 0 }; + addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + if (addr) + ConsoleOutput("vnreng:Siglus2: type 2 pattern found"); + } + + if (!addr) { + ConsoleOutput("vnreng:Siglus2: both type1 and type2 patterns not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.offset = pusha_esi_off - 4; // -0x20 + hp.type = USING_UNICODE|FIXING_SPLIT; // jichi 6/1/2014: fixing the split value + hp.length_offset = 1; + + ConsoleOutput("vnreng: INSERT Siglus2"); + NewHook(hp, "SiglusEngine2"); + //ConsoleOutput("SiglusEngine2"); + return true; +} + +static void SpecialHookSiglus1(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + __asm + { + mov edx,esp_base + mov ecx,[edx-0xc] + mov eax,[ecx+0x14] + add ecx,4 + cmp eax,0x8 + cmovnb ecx,[ecx] + mov edx,len + add eax,eax + mov [edx],eax + mov edx,data + mov [edx],ecx + } +} + +// jichi: 8/17/2013: Change return type to bool +bool InsertSiglus1Hook() +{ + const BYTE bytes[] = {0x33,0xc0,0x8b,0xf9,0x89,0x7c,0x24}; + ULONG range = max(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + if (!addr) { // jichi 8/17/2013: Add "== 0" check to prevent breaking new games + //ConsoleOutput("Unknown SiglusEngine"); + ConsoleOutput("vnreng:Siglus: pattern not found"); + return false; + } + + DWORD limit = addr - 0x100; + while (addr > limit) { + if (*(WORD*)addr == 0xff6a) { + HookParam hp = {}; + hp.address = addr; + hp.text_fun = SpecialHookSiglus1; + hp.type = USING_UNICODE; + ConsoleOutput("vnreng: INSERT Siglus"); + NewHook(hp, "SiglusEngine"); + //RegisterEngineType(ENGINE_SIGLUS); + return true; + } + addr--; + } + ConsoleOutput("vnreng:Siglus: failed"); + return false; +} + +} // unnamed namespace + +// jichi 8/17/2013: Insert old first. As the pattern could also be found in the old engine. +bool InsertSiglusHook() +{ + if (InsertSiglus1Hook()) + return true; + bool ok = InsertSiglus2Hook(); + ok = InsertSiglus3Hook() || ok; + ok = InsertSiglus4Hook() || ok; + return ok; +} + +/******************************************************************************************** +MAJIRO hook: + Game folder contains both data.arc and scenario.arc. arc files is + quite common seen so we need 2 files to confirm it's MAJIRO engine. + + Font caching issue. Find call to TextOutA and the function entry. + + The original Majiro hook will catch furiga mixed with the text. + To split them out we need to find a parameter. Seems there's no + simple way to handle this case. + At the function entry, EAX seems to point to a structure to describe + current drawing context. +28 seems to be font size. +48 is negative + if furigana. I don't know exact meaning of this structure, + just do memory comparisons and get the value working for current release. + +********************************************************************************************/ +// jichi 11/28/2014: Disable original Majiro special hook that does not work for new Majiro games, such as: 流され妻 +// In the new Majiro engine, arg1 could be zero +#if 0 +static void SpecialHookMajiro(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + // jichi 5/12/2014 + // See: http://stackoverflow.com/questions/14210614/bind-function-parameter-to-specific-register + // x86-64, the first 6 (integral) parameters are passed in the registers %rdi, %rsi, %rdx, %rcx, %r8, and %r9. + __asm + { + mov edx,esp_base + mov edi,[edx+0xc] ; jichi 5/11/2014: the third function parameter is LPCSTR text + mov eax,data + mov [eax],edi + or ecx,0xffffffff + xor eax,eax + repne scasb + not ecx + dec ecx + mov eax,len + mov [eax],ecx + mov eax,[edx+4] ; jichi 5/11/2014: the first function parameter is LPCSTR font name (MS Gothic) + mov edx,[eax+0x28] ; 0x28 and 0x48 are in the caller of this fuction hook + mov eax,[eax+0x48] ; *split = ([eax+0x28] & 0xff) | (([eax+0x48] >> 1) & 0xffff00) + sar eax,0x1f + mov dh,al + mov ecx,split + mov [ecx],edx + } +} +#endif // 0 + +/** jichi 12/28/2014: new Majiro hook pattern + * + * Different function starts: + * + * Old Majiro: + * enum { sub_esp = 0xec81 }; // caller pattern: sub esp = 0x81,0xec byte + * + * New Majiro since [141128] [アトリエさくら] 流され妻、綾�“ネトラレ”��体験版 + * 003e9230 55 push ebp + * 003e9231 8bec mov ebp,esp + * 003e9233 83ec 64 sub esp,0x64 + * + * Also, function addresses are fixed in old majiro, but floating in new majiro. + * In the old Majiro game, caller's address could be used as split. + * In the new Majiro game, the hooked function is invoked by the same caller. + * + * Use a split instead. + * Sample stack values are as follows. + * - Old majiro: arg3 is text, arg1 is font name + * - New majiro: arg3 is text, arg4 is font name + * + * Name: + * 0038f164 003e8163 return to .003e8163 from .003e9230 + * 0038f168 00000000 + * 0038f16c 00000000 + * 0038f170 08b04dbc ; jichi: arg3, text + * 0038f174 006709f0 ; jichi: arg4, font name + * 0038f178 006dace8 + * 0038f17c 00000000 + * 0038f180 00000013 + * 0038f184 006fcba8 + * 0038f188 00000078 ; jichi: 0x24, alternative split + * 0038f18c 00000078 + * 0038f190 00000018 + * 0038f194 00000002 + * 0038f198 08b04dbc + * 0038f19c 006709f0 + * 0038f1a0 00000000 + * 0038f1a4 00000000 + * 0038f1a8 00000078 + * 0038f1ac 00000018 + * 0038f1b0 08aa0130 + * 0038f1b4 01b6b6c0 + * 0038f1b8 beff26e4 + * 0038f1bc 0038f1fc + * 0038f1c0 004154af return to .004154af from .00415400 ; jichi: 0x52, could be used as split + * 0038f1c4 0000000e + * 0038f1c8 000001ae + * 0038f1cc 00000158 + * 0038f1d0 00000023 + * 0038f1d4 beff2680 + * 0038f1d8 0038f208 + * 0038f1dc 003ecfda return to .003ecfda from .00415400 + * + * Scenario: + * 0038e57c 003e8163 return to .003e8163 from .003e9230 + * 0038e580 00000000 + * 0038e584 00000000 + * 0038e588 0038ee4c ; jichi: arg3, text + * 0038e58c 004d5400 .004d5400 ; jichi: arg4, font name + * 0038e590 006dace8 + * 0038e594 0038ee6d + * 0038e598 004d7549 .004d7549 + * 0038e59c 00000000 + * 0038e5a0 00000180 ; jichi: 0x24, alternative hook + * 0038e5a4 00000180 + * 0038e5a8 00000018 + * 0038e5ac 00000002 + * 0038e5b0 0038ee4c + * 0038e5b4 004d5400 .004d5400 + * 0038e5b8 00000000 + * 0038e5bc 00000000 + * 0038e5c0 00000180 + * 0038e5c4 00000018 + * 0038e5c8 006a0180 + * 0038e5cc 0038e5f8 + * 0038e5d0 0041fc87 return to .0041fc87 from .0041fc99 + * 0038e5d4 0038e5f8 + * 0038e5d8 00418165 return to .00418165 from .0041fc81 ; jichi: used as split + * 0038e5dc 004d7549 .004d7549 + * 0038e5e0 0038ee6d + * 0038e5e4 0038e608 + * 0038e5e8 00419555 return to .00419555 from .0041814e + * 0038e5ec 00000000 + * 0038e5f0 004d7549 .004d7549 + * 0038e5f4 0038ee6d + * + * 12/4/2014: Add split for furigana. + * Sample game: [141128] [チュアブルソフト] 残念な俺達�青春事情 + * Following are memory values after arg4 (font name) + * + * Surface: � * 00EC5400 82 6C 82 72 20 82 6F 83 53 83 56 83 62 83 4E 00 �� �ゴシヂ�. + * 00EC5410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + * 00EC5420 01 00 00 00 00 00 00 00 1C 00 00 00 0D 00 00 00 ....... ....... + * 00EC5430 (2D)00 00 00 FF FF FF 00 00 00 00 02 00 00 00 00 -...���.... .... ; jichi: first byte as split in parenthesis + * 00EC5440 00(00 00 00)60 F7 3F 00 F0 D8 FF FF 00 00 00 00 ....`・. .... ; jichi: first word without first byte as split + * + * 00EC5450 32 01 00 00 0C 00 00 00 A0 02 00 00 88 00 00 00 2 ......� ..・.. + * 00EC5460 00 00 00 00 01 00 00 00 00 00 00 00 32 01 00 00 .... .......2 .. + * 00EC5470 14 00 00 00 01 00 00 00 82 6C 82 72 20 82 6F 83 ... ...�� �・ ; MS P Gothic + * 00EC5480 53 S + * + * Furigana: そ� + * 00EC5400 82 6C 82 72 20 83 53 83 56 83 62 83 4E 00 4E 00 �� ゴシヂ�.N. + * 00EC5410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + * 00EC5420 01 00 00 00 00 00 00 00 0E 00 00 00 06 00 00 00 ....... ... ... + * 00EC5430 (16)00 00 00 FF FF FF 00 00 00 00 02 00 00 00 00 ...���.... .... + * 00EC5440 00(00 00 00)60 F7 3F 00 F0 D8 FF FF 00 00 00 00 ....`・. .... + * + * 00EC5450 32 01 00 00 0C 00 00 00 A0 02 00 00 88 00 00 00 2 ......� ..・.. + * 00EC5460 00 00 00 00 00 00 00 00 00 00 00 00 32 01 00 00 ............2 .. + * 00EC5470 14 00 00 00 01 00 00 00 82 6C 82 72 20 82 6F 83 ... ...�� �・ ; MS P Gothic + * 00EC5480 53 S + * + * Furigana: そ� + * 00EC5400 82 6C 82 72 20 82 6F 83 53 83 56 83 62 83 4E 00 �� �ゴシヂ�. + * 00EC5410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + * 00EC5420 01 00 00 00 00 00 00 00 0E 00 00 00 06 00 00 00 ....... ... ... + * 00EC5430 (2D)00 00 00 FF FF FF 00 00 00 00 02 00 00 00 00 -...���.... .... + * 00EC5440 00(00 00 00)60 F7 3F 00 2B 01 00 00 06 00 00 00 ....`・.+ .. ... + * + * 00EC5450 32 01 00 00 0C 00 00 00 A0 02 00 00 88 00 00 00 2 ......� ..・.. + * 00EC5460 00 00 00 00 00 00 00 00 00 00 00 00 32 01 00 00 ............2 .. + * 00EC5470 14 00 00 00 01 00 00 00 82 6C 82 72 20 82 6F 83 ... ...�� �・ ; MS P Gothic + * 00EC5480 53 S + * + * ---- need to split the above and below case + * + * Text: � * 00EC5400 82 6C 82 72 20 82 6F 83 53 83 56 83 62 83 4E 00 �� �ゴシヂ�. + * 00EC5410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + * 00EC5420 01 00 00 00 00 00 00 00 1C 00 00 00 0D 00 00 00 ....... ....... + * 00EC5430 (2D)00 00 00 FF FF FF 00 00 00 00 02 00 00 00 00 -...���.... .... ; jichi: first byte as split in parenthesis + * 00EC5440 FF(FF FF FF)60 F7 3F 00 32 01 00 00 14 00 00 00 ����`・.2 .. ... ; jichi: first word without first byte as split + * + * 00EC5450 32 01 00 00 0C 00 00 00 A0 02 00 00 88 00 00 00 2 ......� ..・.. + * 00EC5460 00 00 00 00 01 00 00 00 00 00 00 00 32 01 00 00 .... .......2 .. + * 00EC5470 14 00 00 00 00 00 00 00 82 6C 82 72 20 82 6F 83 .......�� �・ ; MS P Gothic + * 00EC5480 53 S + * + * Text: らには、一人の少女� * 00EC5400 82 6C 82 72 20 82 6F 83 53 83 56 83 62 83 4E 00 �� �ゴシヂ�. + * 00EC5410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + * 00EC5420 01 00 00 00 00 00 00 00 1C 00 00 00 0D 00 00 00 ....... ....... + * 00EC5430 (2D)00 00 00 FF FF FF 00 00 00 00 02 00 00 00 00 -...���.... .... + * 00EC5440 FF(FF FF FF)60 F7 3F 00 4D 01 00 00 14 00 00 00 ����`・.M .. ... + * + * 00EC5450 32 01 00 00 0C 00 00 00 A0 02 00 00 88 00 00 00 2 ......� ..・.. + * 00EC5460 00 00 00 00 01 00 00 00 00 00 00 00 32 01 00 00 .... .......2 .. + * 00EC5470 14 00 00 00 00 00 00 00 82 6C 82 72 20 82 6F 83 .......�� �・ ; MS P Gothic + * 00EC5480 53 S + */ + +namespace { // unnamed + +// These values are the same as the assembly logic of ITH: +// ([eax+0x28] & 0xff) | (([eax+0x48] >> 1) & 0xffffff00) +// 0x28 = 10 * 4, 0x48 = 18 / 4 +inline DWORD MajiroOldFontSplit(const DWORD *arg) // arg is supposed to be a string, though +{ return (arg[10] & 0xff) | ((arg[18] >> 1) & 0xffffff00); } + +// Remove lower bytes use 0xffffff00, which are different for furigana +inline DWORD MajiroNewFontSplit(const DWORD *arg) // arg is supposed to be a string, though +{ return (arg[12] & 0xff) | (arg[16] & 0xffffff00); } + +void SpecialHookMajiro(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD arg3 = argof(3, esp_base); // text + *data = arg3; + *len = ::strlen((LPCSTR)arg3); + // IsBadReadPtr is not needed for old Majiro game. + // I am not sure if it is needed by new Majiro game. + if (hp->user_value) { // new majiro + if (DWORD arg4 = argof(4, esp_base)) // old majiro + *split = MajiroNewFontSplit((LPDWORD)arg4); + else + *split = *(DWORD *)(esp_base + 0x5c); // = 4 * 23, caller's caller + } else if (DWORD arg1 = argof(1, esp_base)) // old majiro + *split = MajiroOldFontSplit((LPDWORD)arg1); +} +} // unnamed namespace +bool InsertMajiroHook() +{ + // jichi 7/12/2014: Change to accurate memory ranges + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng:Majiro: failed to get memory range"); + return false; + } + // jichi 4/19/2014: There must be a function in Majiro game which contains 6 TextOutA. + // That function draws all texts. + // + // jichi 11/28/2014: Add new function signature + const DWORD funcs[] = { // caller patterns + 0xec81, // sub esp = 0x81,0xec byte old majiro + 0x83ec8b55 // mov ebp,esp, sub esp,* new majiro + }; + enum { FunctionCount = sizeof(funcs) / sizeof(*funcs) }; + ULONG addr = MemDbg::findMultiCallerAddress((ULONG)::TextOutA, funcs, FunctionCount, startAddress, stopAddress); + //ULONG addr = MemDbg::findCallerAddress((ULONG)::TextOutA, 0x83ec8b55, startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:Majiro: failed"); + return false; + } + + bool newMajiro = 0x55 == *(BYTE *)addr; + + HookParam hp = {}; + //hp.offset=0xc; + //hp.split=4; + //hp.split_index=0x28; + //hp.type|=USING_STRING|USING_SPLIT|SPLIT_INDIRECT; + hp.address = addr; + hp.text_fun = SpecialHookMajiro; + hp.user_value = newMajiro; + if (newMajiro) { + hp.type = NO_CONTEXT; // do not use return address for new majiro + ConsoleOutput("vnreng: INSERT Majiro2"); + NewHook(hp, "Majiro2"); + } else { + ConsoleOutput("vnreng: INSERT Majiro"); + NewHook(hp, "Majiro"); + } + //RegisterEngineType(ENGINE_MAJIRO); + return true; +} + +/******************************************************************************************** +CMVS hook: + Process name is cmvs.exe or cnvs.exe or cmvs*.exe. Used by PurpleSoftware games. + + Font caching issue. Find call to GetGlyphOutlineA and the function entry. +********************************************************************************************/ + +namespace { // unnamed + +// jichi 3/6/2014: This is the original CMVS hook in ITH +// It does not work for パ�プルソフトウェア games after しあわせ家族部 (2012) +bool InsertCMVS1Hook() +{ + // jichi 7/12/2014: Change to accurate memory ranges + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng:CMVS1: failed to get memory range"); + return false; + } + enum { sub_esp = 0xec83 }; // caller pattern: sub esp = 0x83,0xec + ULONG addr = MemDbg::findCallerAddress((ULONG)::GetGlyphOutlineA, sub_esp, startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:CMVS1: failed"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.offset = 0x8; + hp.split = -0x18; + hp.type = BIG_ENDIAN|USING_SPLIT; + hp.length_offset = 1; + + ConsoleOutput("vnreng: INSERT CMVS1"); + NewHook(hp, "CMVS"); + //RegisterEngineType(ENGINE_CMVS); + return true; +} + +/** + * CMSV + * Sample games: + * ハピメア: /HAC@48FF3:cmvs32.exe + * ハピメアFD: /HB-1C*0@44EE95 + * + * Optional: ハピメアFD: /HB-1C*0@44EE95 + * This hook has issue that the text will be split to a large amount of threads + * - length_offset: 1 + * - off: 4294967264 = 0xffffffe0 = -0x20 + * - type: 8 + * + * ハピメア: /HAC@48FF3:cmvs32.exe + * base: 0x400000 + * - length_offset: 1 + * - off: 12 = 0xc + * - type: 68 = 0x44 + * + * 00448fee cc int3 + * 00448fef cc int3 + * 00448ff0 /$ 55 push ebp + * 00448ff1 |. 8bec mov ebp,esp + * 00448ff3 |. 83ec 68 sub esp,0x68 ; jichi: hook here, it is actually tagTEXTMETRICA + * 00448ff6 |. 8b01 mov eax,dword ptr ds:[ecx] + * 00448ff8 |. 56 push esi + * 00448ff9 |. 33f6 xor esi,esi + * 00448ffb |. 33d2 xor edx,edx + * 00448ffd |. 57 push edi + * 00448ffe |. 894d fc mov dword ptr ss:[ebp-0x4],ecx + * 00449001 |. 3bc6 cmp eax,esi + * 00449003 |. 74 37 je short cmvs32.0044903c + * 00449005 |> 66:8b78 08 /mov di,word ptr ds:[eax+0x8] + * 00449009 |. 66:3b7d 0c |cmp di,word ptr ss:[ebp+0xc] + * 0044900d |. 75 0a |jnz short cmvs32.00449019 + * 0044900f |. 66:8b7d 10 |mov di,word ptr ss:[ebp+0x10] + * 00449013 |. 66:3978 0a |cmp word ptr ds:[eax+0xa],di + * 00449017 |. 74 0a |je short cmvs32.00449023 + * 00449019 |> 8bd0 |mov edx,eax + * 0044901b |. 8b00 |mov eax,dword ptr ds:[eax] + * 0044901d |. 3bc6 |cmp eax,esi + * 0044901f |.^75 e4 \jnz short cmvs32.00449005 + * 00449021 |. eb 19 jmp short cmvs32.0044903c + * 00449023 |> 3bd6 cmp edx,esi + * 00449025 |. 74 0a je short cmvs32.00449031 + * 00449027 |. 8b38 mov edi,dword ptr ds:[eax] + * 00449029 |. 893a mov dword ptr ds:[edx],edi + * 0044902b |. 8b11 mov edx,dword ptr ds:[ecx] + * 0044902d |. 8910 mov dword ptr ds:[eax],edx + * 0044902f |. 8901 mov dword ptr ds:[ecx],eax + * 00449031 |> 8b40 04 mov eax,dword ptr ds:[eax+0x4] + * 00449034 |. 3bc6 cmp eax,esi + * 00449036 |. 0f85 64010000 jnz cmvs32.004491a0 + * 0044903c |> 8b55 08 mov edx,dword ptr ss:[ebp+0x8] + * 0044903f |. 53 push ebx + * 00449040 |. 0fb75d 0c movzx ebx,word ptr ss:[ebp+0xc] + * 00449044 |. b8 00000100 mov eax,0x10000 + * 00449049 |. 8945 e4 mov dword ptr ss:[ebp-0x1c],eax + * 0044904c |. 8945 f0 mov dword ptr ss:[ebp-0x10],eax + * 0044904f |. 8d45 e4 lea eax,dword ptr ss:[ebp-0x1c] + * 00449052 |. 50 push eax ; /pMat2 + * 00449053 |. 56 push esi ; |Buffer + * 00449054 |. 56 push esi ; |BufSize + * 00449055 |. 8d4d d0 lea ecx,dword ptr ss:[ebp-0x30] ; | + * 00449058 |. 51 push ecx ; |pMetrics + * 00449059 |. 6a 05 push 0x5 ; |Format = GGO_GRAY4_BITMAP + * 0044905b |. 53 push ebx ; |Char + * 0044905c |. 52 push edx ; |hDC + * 0044905d |. 8975 e8 mov dword ptr ss:[ebp-0x18],esi ; | + * 00449060 |. 8975 ec mov dword ptr ss:[ebp-0x14],esi ; | + * 00449063 |. ff15 5cf05300 call dword ptr ds:[<&gdi32.getglyphoutli>; \GetGlyphOutlineA + * 00449069 |. 8b75 10 mov esi,dword ptr ss:[ebp+0x10] + * 0044906c |. 0faff6 imul esi,esi + * 0044906f |. 8bf8 mov edi,eax + * 00449071 |. 8d04bd 0000000>lea eax,dword ptr ds:[edi*4] + * 00449078 |. 3bc6 cmp eax,esi + * 0044907a |. 76 02 jbe short cmvs32.0044907e + * 0044907c |. 8bf0 mov esi,eax + * 0044907e |> 56 push esi ; /Size + * 0044907f |. 6a 00 push 0x0 ; |Flags = LMEM_FIXED + * 00449081 |. ff15 34f25300 call dword ptr ds:[<&kernel32.localalloc>; \LocalAlloc + */ +bool InsertCMVS2Hook() +{ + // There are multiple functions satisfy the pattern below. + // Hook to any one of them is OK. + const BYTE bytes[] = { // function begin + 0x55, // 00448ff0 /$ 55 push ebp + 0x8b,0xec, // 00448ff1 |. 8bec mov ebp,esp + 0x83,0xec, 0x68, // 00448ff3 |. 83ec 68 sub esp,0x68 ; jichi: hook here + 0x8b,0x01, // 00448ff6 |. 8b01 mov eax,dword ptr ds:[ecx] + 0x56, // 00448ff8 |. 56 push esi + 0x33,0xf6, // 00448ff9 |. 33f6 xor esi,esi + 0x33,0xd2, // 00448ffb |. 33d2 xor edx,edx + 0x57, // 00448ffd |. 57 push edi + 0x89,0x4d, 0xfc, // 00448ffe |. 894d fc mov dword ptr ss:[ebp-0x4],ecx + 0x3b,0xc6, // 00449001 |. 3bc6 cmp eax,esi + 0x74, 0x37 // 00449003 |. 74 37 je short cmvs32.0044903c + }; + enum { addr_offset = 3 }; // offset from the beginning of the function + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + if (!addr) { + ConsoleOutput("vnreng:CMVS2: pattern not found"); + return false; + } + + //reladdr = 0x48ff0; + //reladdr = 0x48ff3; + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.offset = 0xc; + hp.type = BIG_ENDIAN; + hp.length_offset = 1; + + ConsoleOutput("vnreng: INSERT CMVS2"); + NewHook(hp, "CMVS2"); + return true; +} + +} // unnamed namespace + +// jichi 3/7/2014: Insert the old hook first since GetGlyphOutlineA can NOT be found in new games +bool InsertCMVSHook() +{ + // Both CMVS1 and CMVS2 exists in new games. + // Insert the CMVS2 first. Since CMVS1 could break CMVS2 + // And the CMVS1 games do not have CMVS2 patterns. + return InsertCMVS2Hook() || InsertCMVS1Hook(); +} + +namespace { // unnamed rUGP + +/******************************************************************************************** +rUGP hook: + Process name is rugp.exe. Used by AGE/GIGA games. + + Font caching issue. Find call to GetGlyphOutlineA and keep stepping out functions. + After several tries we comes to address in rvmm.dll and everything is catched. + We see CALL [E*X+0x*] while EBP contains the character data. + It's not as simple to reverse in rugp at run time as like reallive since rugp dosen't set + up stack frame. In other words EBP is used for other purpose. We need to find alternative + approaches. + The way to the entry of that function gives us clue to find it. There is one CMP EBP,0x8140 + instruction in this function and that's enough! 0x8140 is the start of SHIFT-JIS + characters. It's determining if ebp contains a SHIFT-JIS character. This function is not likely + to be used in other ways. We simply search for this instruction and place hook around. +********************************************************************************************/ +void SpecialHookRUGP1(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + CC_UNUSED(split); + DWORD *stack = (DWORD *)esp_base; + DWORD i, val; + for (i = 0; i < 4; i++) { + val = *stack++; + if ((val >> 16) == 0) + break; + + } + if (i < 4) { + hp->offset = i << 2; + *data = val; + *len = 2; + hp->text_fun = nullptr; + //hp->type &= ~EXTERN_HOOK; + } + else + *len = 0; +} + +// jichi 10/1/2013: Change return type to bool +bool InsertRUGP1Hook() +{ + DWORD low, high; + if (!IthCheckFile(L"rvmm.dll") || !SafeFillRange(L"rvmm.dll", &low, &high)) { + ConsoleOutput("vnreng:rUGP: rvmm.dll does not exist"); + return false; + } + //WCHAR str[0x40]; + LPVOID ch = (LPVOID)0x8140; + enum { range = 0x20000 }; + DWORD t = SearchPattern(low + range, high - low - range, &ch, 4) + range; + BYTE *s = (BYTE *)(low + t); + //if (t) { + if (t != range) { // jichi 10/1/2013: Changed to compare with 0x20000 + if (*(s - 2) != 0x81) + return false; + if (DWORD i = SafeFindEntryAligned((DWORD)s, 0x200)) { + HookParam hp = {}; + hp.address = i; + //hp.offset= -8; + hp.length_offset = 1; + hp.text_fun = SpecialHookRUGP1; + hp.type = BIG_ENDIAN; + ConsoleOutput("vnreng: INSERT rUGP#1"); + NewHook(hp, "rUGP"); + return true; + } + } else { + t = SearchPattern(low, range, &s, 4); + if (!t) { + ConsoleOutput("vnreng:rUGP: pattern not found"); + //ConsoleOutput("Can't find characteristic instruction."); + return false; + } + + s = (BYTE *)(low + t); + for (int i = 0; i < 0x200; i++, s--) + if (s[0] == 0x90 + && *(DWORD *)(s - 3) == 0x90909090) { + t = low+ t - i + 1; + //swprintf(str, L"HookAddr 0x%.8x", t); + //ConsoleOutput(str); + HookParam hp = {}; + hp.address = t; + hp.offset = 0x4; + hp.length_offset = 1; + hp.type = BIG_ENDIAN; + ConsoleOutput("vnreng:INSERT rUGP#2"); + NewHook(hp, "rUGP"); + //RegisterEngineType(ENGINE_RUGP); + return true; + } + } + ConsoleOutput("vnreng:rUGP: failed"); + return false; +//rt: + //ConsoleOutput("Unknown rUGP engine."); +} + +/** rUGP2 10/11/2014 jichi + * + * Sample game: マブラヴ オルタネイヂ�ヴ ト�タル・イクリプス + * The existing rUGP#1/#2 cannot be identified. + * H-codes: + * - /HAN-4@1E51D:VM60.DLL + * - addr: 124189 = 0x1e51d + * - length_offset: 1 + * - module: 3037492083 = 0xb50c7373 + * - off: 4294967288 = 0xfffffff8 = -8 + * - type: 1092 = 0x444 + * - /HAN-4@1001E51D ( alternative) + * - addr: 268559645 = 0x1001e51d + * - length_offset: 1 + * - type: 1028 = 0x404 + * + * This function is very long. + * 1001e4b2 ^e9 c0fcffff jmp _18.1001e177 + * 1001e4b7 8b45 14 mov eax,dword ptr ss:[ebp+0x14] + * 1001e4ba c745 08 08000000 mov dword ptr ss:[ebp+0x8],0x8 + * 1001e4c1 85c0 test eax,eax + * 1001e4c3 74 3c je short _18.1001e501 + * 1001e4c5 8378 04 00 cmp dword ptr ds:[eax+0x4],0x0 + * 1001e4c9 7f 36 jg short _18.1001e501 + * 1001e4cb 7c 05 jl short _18.1001e4d2 + * 1001e4cd 8338 00 cmp dword ptr ds:[eax],0x0 + * 1001e4d0 73 2f jnb short _18.1001e501 + * 1001e4d2 8b4d f0 mov ecx,dword ptr ss:[ebp-0x10] + * 1001e4d5 8b91 38a20000 mov edx,dword ptr ds:[ecx+0xa238] + * 1001e4db 8910 mov dword ptr ds:[eax],edx + * 1001e4dd 8b89 3ca20000 mov ecx,dword ptr ds:[ecx+0xa23c] + * 1001e4e3 8948 04 mov dword ptr ds:[eax+0x4],ecx + * 1001e4e6 eb 19 jmp short _18.1001e501 + * 1001e4e8 c745 08 09000000 mov dword ptr ss:[ebp+0x8],0x9 + * 1001e4ef eb 10 jmp short _18.1001e501 + * 1001e4f1 c745 08 16000000 mov dword ptr ss:[ebp+0x8],0x16 + * 1001e4f8 eb 07 jmp short _18.1001e501 + * 1001e4fa c745 08 1f000000 mov dword ptr ss:[ebp+0x8],0x1f + * 1001e501 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 1001e504 8ad0 mov dl,al + * 1001e506 80f2 20 xor dl,0x20 + * 1001e509 80c2 5f add dl,0x5f + * 1001e50c 80fa 3b cmp dl,0x3b + * 1001e50f 0f87 80010000 ja _18.1001e695 + * 1001e515 0fb60e movzx ecx,byte ptr ds:[esi] + * 1001e518 c1e0 08 shl eax,0x8 + * 1001e51b 0bc1 or eax,ecx + * 1001e51d b9 01000000 mov ecx,0x1 ; jichi: hook here + * 1001e522 03f1 add esi,ecx + * 1001e524 8945 08 mov dword ptr ss:[ebp+0x8],eax + * 1001e527 8975 0c mov dword ptr ss:[ebp+0xc],esi + * 1001e52a 3d 79810000 cmp eax,0x8179 + * 1001e52f 0f85 9d000000 jnz _18.1001e5d2 + * 1001e535 8b4d f0 mov ecx,dword ptr ss:[ebp-0x10] + * 1001e538 56 push esi + * 1001e539 8d55 d0 lea edx,dword ptr ss:[ebp-0x30] + * 1001e53c 52 push edx + * 1001e53d e8 0e0bffff call _18.1000f050 + * 1001e542 8d4d d0 lea ecx,dword ptr ss:[ebp-0x30] + * 1001e545 c745 fc 07000000 mov dword ptr ss:[ebp-0x4],0x7 + * 1001e54c ff15 500a0e10 call dword ptr ds:[0x100e0a50] ; _19.6a712fa9 + * 1001e552 84c0 test al,al + * 1001e554 75 67 jnz short _18.1001e5bd + * 1001e556 8b75 f0 mov esi,dword ptr ss:[ebp-0x10] + * 1001e559 8d45 d0 lea eax,dword ptr ss:[ebp-0x30] + * 1001e55c 50 push eax + * 1001e55d 8bce mov ecx,esi + * 1001e55f c745 e4 01000000 mov dword ptr ss:[ebp-0x1c],0x1 + * 1001e566 c745 e0 00000000 mov dword ptr ss:[ebp-0x20],0x0 + * 1001e56d e8 5e80ffff call _18.100165d0 + * 1001e572 0fb7f8 movzx edi,ax + * 1001e575 57 push edi + * 1001e576 8bce mov ecx,esi + * 1001e578 e8 c380ffff call _18.10016640 + * 1001e57d 85c0 test eax,eax + * 1001e57f 74 0d je short _18.1001e58e + * 1001e581 f640 38 02 test byte ptr ds:[eax+0x38],0x2 + * 1001e585 74 07 je short _18.1001e58e + * 1001e587 c745 e0 01000000 mov dword ptr ss:[ebp-0x20],0x1 + * 1001e58e 837d bc 10 cmp dword ptr ss:[ebp-0x44],0x10 + * 1001e592 74 29 je short _18.1001e5bd + * 1001e594 8b43 28 mov eax,dword ptr ds:[ebx+0x28] + * 1001e597 85c0 test eax,eax + */ +bool InsertRUGP2Hook() +{ + DWORD low, high; + if (!IthCheckFile(L"vm60.dll") || !SafeFillRange(L"vm60.dll", &low, &high)) { + ConsoleOutput("vnreng:rUGP2: vm60.dll does not exist"); + return false; + } + const BYTE bytes[] = { + 0x0f,0xb6,0x0e, // 1001e515 0fb60e movzx ecx,byte ptr ds:[esi] + 0xc1,0xe0, 0x08, // 1001e518 c1e0 08 shl eax,0x8 + 0x0b,0xc1, // 1001e51b 0bc1 or eax,ecx + 0xb9, 0x01,0x00,0x00,0x00, // 1001e51d b9 01000000 mov ecx,0x1 ; jichi: hook here + 0x03,0xf1, // 1001e522 03f1 add esi,ecx + 0x89,0x45, 0x08, // 1001e524 8945 08 mov dword ptr ss:[ebp+0x8],eax + 0x89,0x75, 0x0c // 1001e527 8975 0c mov dword ptr ss:[ebp+0xc],esi + }; + enum { addr_offset = 0x1001e51d - 0x1001e515 }; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), low, high); + //GROWL_DWORD(addr); + if (!addr) { + ConsoleOutput("vnreng:rUGP2: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.length_offset = 1; + hp.offset = -8; + hp.type = NO_CONTEXT|BIG_ENDIAN; + ConsoleOutput("vnreng: INSERT rUGP2"); + NewHook(hp, "rUGP2"); + return true; +} + +} // unnamed namespace + +bool InsertRUGPHook() +{ return InsertRUGP1Hook() || InsertRUGP2Hook(); } + +/******************************************************************************************** +Lucifen hook: + Game folder contains *.lpk. Used by Navel games. + Hook is same to GetTextExtentPoint32A, use ESP to split name. +********************************************************************************************/ +void InsertLucifenHook() +{ + // BOOL GetTextExtentPoint32( + // _In_ HDC hdc, + // _In_ LPCTSTR lpString, + // _In_ int c, + // _Out_ LPSIZE lpSize + // ); + HookParam hp = {}; + hp.address = (DWORD)::GetTextExtentPoint32A; + hp.offset = 0x8; // arg2 lpString + hp.split = -0x18; // jichi 8/12/2014: = -4 - pusha_esp_off + hp.length_offset = 3; + hp.type = USING_STRING|USING_SPLIT; + ConsoleOutput("vnreng: INSERT Lucifen"); + NewHook(hp, "Lucifen"); + //RegisterEngineType(ENGINE_LUCIFEN); +} +/******************************************************************************************** +System40 hook: + System40 is a game engine developed by Alicesoft. + Afaik, there are 2 very different types of System40. Each requires a particular hook. + + Pattern 1: Either SACTDX.dll or SACT2.dll exports SP_TextDraw. + The first relative call in this function draw text to some surface. + Text pointer is return by last absolute indirect call before that. + Split parameter is a little tricky. The first register pushed onto stack at the begining + usually is used as font size later. According to instruction opcode map, push + eax -- 50, ecx -- 51, edx -- 52, ebx --53, esp -- 54, ebp -- 55, esi -- 56, edi -- 57 + Split parameter value: + eax - -8, ecx - -C, edx - -10, ebx - -14, esp - -18, ebp - -1C, esi - -20, edi - -24 + Just extract the low 4 bit and shift left 2 bit, then minus by -8, + will give us the split parameter. e.g. push ebx 53->3 *4->C, -8-C=-14. + Sometimes if split function is enabled, ITH will split text spoke by different + character into different thread. Just open hook dialog and uncheck split parameter. + Then click modify hook. + + Pattern 2: *engine.dll exports SP_SetTextSprite. + At the entry point, EAX should be a pointer to some structure, character at +0x8. + Before calling this function, the caller put EAX onto stack, we can also find this + value on stack. But seems parameter order varies from game release. If a future + game breaks the EAX rule then we need to disassemble the caller code to determine + data offset dynamically. +********************************************************************************************/ + +static void InsertAliceHook1(DWORD addr, DWORD module, DWORD limit) +{ + if (!addr) { + ConsoleOutput("vnreng:AliceHook1: failed"); + return; + } + for (DWORD i = addr, s = addr; i < s + 0x100; i++) + if (*(BYTE *)i == 0xe8) { // Find the first relative call. + DWORD j = i + 5 + *(DWORD *)(i + 1); + if (j > module && j < limit) { + while (true) { // Find the first register push onto stack. + DWORD c = ::disasm((BYTE *)s); + if (c == 1) + break; + s += c; + } + DWORD c = *(BYTE *)s; + HookParam hp = {}; + hp.address = j; + hp.offset = -0x8; + hp.split = -8 -((c & 0xf) << 2); + hp.type = USING_STRING|USING_SPLIT; + //if (s>j) hp.type^=USING_SPLIT; + ConsoleOutput("vnreng: INSERT AliceHook1"); + NewHook(hp, "System40"); + //RegisterEngineType(ENGINE_SYS40); + return; + } + } + ConsoleOutput("vnreng:AliceHook1: failed"); +} +static void InsertAliceHook2(DWORD addr) +{ + if (!addr) { + ConsoleOutput("vnreng:AliceHook2: failed"); + return; + } + HookParam hp = {}; + hp.address = addr; + hp.offset = -0x8; + hp.index = 0x8; + hp.length_offset = 1; + hp.type = DATA_INDIRECT; + ConsoleOutput("vnreng: INSERT AliceHook2"); + NewHook(hp, "System40"); + //RegisterEngineType(ENGINE_SYS40); +} + +// jichi 8/23/2013 Move here from engine.cc +// Do not work for the latest Alice games +// jichi 5/13/2015: Looking for function entries in StoatSpriteEngine.dll +bool InsertAliceHook() +{ + DWORD low, high, addr; + if (GetFunctionAddr("SP_TextDraw", &addr, &low, &high, 0) && addr) { + InsertAliceHook1(addr, low, low + high); + return true; + } + if (GetFunctionAddr("SP_SetTextSprite", &addr, &low, &high, 0) && addr) { + InsertAliceHook2(addr); + return true; + } + ConsoleOutput("vnreng:AliceHook: failed"); + return false; +} + +/** + * jichi 12/26/2013: Rance hook + * + * ランス01 光をもとめて: /HSN4:-14@5506A9 + * - addr: 5572265 (0x5596a9) + * - off: 4 + * - split: 4294967272 (0xffffffe8 = -0x18) + * - type: 1041 (0x411) + * + * the above code has the same pattern except int3. + * 005506a9 |. e8 f2fb1600 call Rance01.006c02a0 ; hook here + * 005506ae |. 83c4 0c add esp,0xc + * 005506b1 |. 5f pop edi + * 005506b2 |. 5e pop esi + * 005506b3 |. b0 01 mov al,0x1 + * 005506b5 |. 5b pop ebx + * 005506b6 \. c2 0400 retn 0x4 + * 005506b9 cc int3 + * + * ランス・クエス� /hsn4:-14@42e08a + * 0042e08a |. e8 91ed1f00 call Ranceque.0062ce20 ; hook here + * 0042e08f |. 83c4 0c add esp,0xc + * 0042e092 |. 5f pop edi + * 0042e093 |. 5e pop esi + * 0042e094 |. b0 01 mov al,0x1 + * 0042e096 |. 5b pop ebx + * 0042e097 \. c2 0400 retn 0x4 + * 0042e09a cc int3 + * + * 5/7/2015 イブニクル version 1.0.1 + * The hooked function is no longer get called after loading AliceRunPatch.dll. + * The hooked function is below. + * See also ATcode: http://capita.tistory.com/m/post/256 + * 005C40AE CC INT3 + * 005C40AF CC INT3 + * 005C40B0 53 PUSH EBX + * 005C40B1 8B5C24 08 MOV EBX,DWORD PTR SS:[ESP+0x8] + * 005C40B5 56 PUSH ESI + * 005C40B6 57 PUSH EDI + * 005C40B7 8B7B 10 MOV EDI,DWORD PTR DS:[EBX+0x10] + * 005C40BA 8BF0 MOV ESI,EAX + * 005C40BC 47 INC EDI + * 005C40BD 3B7E 0C CMP EDI,DWORD PTR DS:[ESI+0xC] + * 005C40C0 76 0F JBE SHORT .005C40D1 + * 005C40C2 E8 79F8FFFF CALL .005C3940 + * 005C40C7 84C0 TEST AL,AL + * 005C40C9 75 06 JNZ SHORT .005C40D1 + * 005C40CB 5F POP EDI + * 005C40CC 5E POP ESI + * 005C40CD 5B POP EBX + * 005C40CE C2 0400 RETN 0x4 + * 005C40D1 837B 14 10 CMP DWORD PTR DS:[EBX+0x14],0x10 + * 005C40D5 72 02 JB SHORT .005C40D9 + * 005C40D7 8B1B MOV EBX,DWORD PTR DS:[EBX] + * 005C40D9 837E 0C 00 CMP DWORD PTR DS:[ESI+0xC],0x0 + * 005C40DD 75 15 JNZ SHORT .005C40F4 + * 005C40DF 57 PUSH EDI + * 005C40E0 33C0 XOR EAX,EAX + * 005C40E2 53 PUSH EBX + * 005C40E3 50 PUSH EAX + * 005C40E4 E8 B7400D00 CALL .006981A0 + * 005C40E9 83C4 0C ADD ESP,0xC + * 005C40EC 5F POP EDI + * 005C40ED 5E POP ESI + * 005C40EE B0 01 MOV AL,0x1 + * 005C40F0 5B POP EBX + * 005C40F1 C2 0400 RETN 0x4 + * 005C40F4 8B46 08 MOV EAX,DWORD PTR DS:[ESI+0x8] + * 005C40F7 57 PUSH EDI + * 005C40F8 53 PUSH EBX + * 005C40F9 50 PUSH EAX + * 005C40FA E8 A1400D00 CALL .006981A0 ; jichi: call here + * 005C40FF 83C4 0C ADD ESP,0xC + * 005C4102 5F POP EDI + * 005C4103 5E POP ESI + * 005C4104 B0 01 MOV AL,0x1 + * 005C4106 5B POP EBX + * 005C4107 C2 0400 RETN 0x4 + * 005C410A CC INT3 + * 005C410B CC INT3 + * 005C410C CC INT3 * + */ +static bool InsertSystem43OldHook(ULONG startAddress, ULONG stopAddress, LPCSTR hookName) +{ + // i.e. 83c40c5f5eb0015bc20400cccc without leading 0xe8 + //const BYTE ins[] = { // 005506a9 |. e8 f2fb1600 call rance01.006c02a0 ; hook here + // 0x83,0xc4, 0x0c, // 005506ae |. 83c4 0c add esp,0xc + // 0x5f, // 005506b1 |. 5f pop edi + // 0x5e, // 005506b2 |. 5e pop esi + // 0xb0, 0x01, // 005506b3 |. b0 01 mov al,0x1 + // 0x5b, // 005506b5 |. 5b pop ebx + // 0xc2, 0x04,0x00, // 005506b6 \. c2 0400 retn 0x4 + // 0xcc, 0xcc // patching a few int3 to make sure that this is at the end of the code block + //}; + //enum { addr_offset = -5 }; // the function call before the ins + //ULONG addr = module_base_; //- sizeof(ins); + ////addr = 0x5506a9; + //enum { near_call = 0xe8 }; // intra-module function call + //do { + // //addr += sizeof(ins); // so that each time return diff address -- not needed + // ULONG range = min(module_limit_ - addr, MAX_REL_ADDR); + // addr = MemDbg::findBytes(ins, sizeof(ins), addr, addr + range); + // if (!addr) { + // //ITH_MSG(L"failed"); + // ConsoleOutput("vnreng:System43: pattern not found"); + // return false; + // } + // addr += addr_offset; + //} while(near_call != *(BYTE *)addr); // function call + //GROWL_DWORD(addr); + + // i.e. 83c40c5f5eb0015bc20400cccc without leading 0xe8 + const BYTE bytes[] = { + 0xe8, XX4, // 005506a9 |. e8 f2fb1600 call rance01.006c02a0 ; hook here + 0x83,0xc4, 0x0c, // 005506ae |. 83c4 0c add esp,0xc + 0x5f, // 005506b1 |. 5f pop edi + 0x5e, // 005506b2 |. 5e pop esi + 0xb0, 0x01, // 005506b3 |. b0 01 mov al,0x1 + 0x5b, // 005506b5 |. 5b pop ebx + 0xc2, 0x04,0x00, // 005506b6 \. c2 0400 retn 0x4 + 0xcc, 0xcc // patching a few int3 to make sure that this is at the end of the code block + }; + enum { addr_offset = 0 }; + ULONG addr = MemDbg::matchBytes(bytes, sizeof(bytes), startAddress, stopAddress); + //GROWL_DWORD(addr); + if (!addr) { + ConsoleOutput("vnreng:System43: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.offset = 4; + hp.split = -0x18; + hp.type = NO_CONTEXT|USING_SPLIT|USING_STRING; + ConsoleOutput("vnreng: INSERT System43"); + NewHook(hp, hookName); + + ConsoleOutput("vnreng:System43: disable GDI hooks"); // disable hooking to TextOutA, which is cached + DisableGDIHooks(); + return true; +} + +/** 5/13/2015 Add new hook for System43 engine that has no garbage threads and can detect character name + * Sample game: Evenicle + * See: http://capita.tistory.com/m/post/256 + * + * 004EEA6C CC INT3 + * 004EEA6D CC INT3 + * 004EEA6E CC INT3 + * 004EEA6F CC INT3 + * 004EEA70 6A FF PUSH -0x1 + * 004EEA72 68 E8267000 PUSH .007026E8 + * 004EEA77 64:A1 00000000 MOV EAX,DWORD PTR FS:[0] + * 004EEA7D 50 PUSH EAX + * 004EEA7E 83EC 20 SUB ESP,0x20 + * 004EEA81 A1 DCC47700 MOV EAX,DWORD PTR DS:[0x77C4DC] + * 004EEA86 33C4 XOR EAX,ESP + * 004EEA88 894424 1C MOV DWORD PTR SS:[ESP+0x1C],EAX + * 004EEA8C 53 PUSH EBX + * 004EEA8D 55 PUSH EBP + * 004EEA8E 56 PUSH ESI + * 004EEA8F 57 PUSH EDI + * 004EEA90 A1 DCC47700 MOV EAX,DWORD PTR DS:[0x77C4DC] + * 004EEA95 33C4 XOR EAX,ESP + * 004EEA97 50 PUSH EAX + * 004EEA98 8D4424 34 LEA EAX,DWORD PTR SS:[ESP+0x34] + * 004EEA9C 64:A3 00000000 MOV DWORD PTR FS:[0],EAX + * 004EEAA2 8B4424 44 MOV EAX,DWORD PTR SS:[ESP+0x44] + * 004EEAA6 8BF1 MOV ESI,ECX + * 004EEAA8 E8 8346FBFF CALL .004A3130 + * 004EEAAD 8BE8 MOV EBP,EAX + * 004EEAAF 33DB XOR EBX,EBX + * 004EEAB1 3BEB CMP EBP,EBX + * 004EEAB3 75 07 JNZ SHORT .004EEABC + * 004EEAB5 32C0 XOR AL,AL + * 004EEAB7 E9 92000000 JMP .004EEB4E + * 004EEABC 8B06 MOV EAX,DWORD PTR DS:[ESI] + * 004EEABE 8B10 MOV EDX,DWORD PTR DS:[EAX] + * 004EEAC0 8BCE MOV ECX,ESI + * 004EEAC2 FFD2 CALL EDX + * 004EEAC4 8BC8 MOV ECX,EAX + * 004EEAC6 C74424 28 0F0000>MOV DWORD PTR SS:[ESP+0x28],0xF + * 004EEACE 895C24 24 MOV DWORD PTR SS:[ESP+0x24],EBX + * 004EEAD2 885C24 14 MOV BYTE PTR SS:[ESP+0x14],BL + * 004EEAD6 8D71 01 LEA ESI,DWORD PTR DS:[ECX+0x1] + * 004EEAD9 8DA424 00000000 LEA ESP,DWORD PTR SS:[ESP] + * 004EEAE0 8A11 MOV DL,BYTE PTR DS:[ECX] + * 004EEAE2 41 INC ECX + * 004EEAE3 3AD3 CMP DL,BL + * 004EEAE5 ^75 F9 JNZ SHORT .004EEAE0 + * 004EEAE7 2BCE SUB ECX,ESI + * 004EEAE9 50 PUSH EAX + * 004EEAEA 8BF9 MOV EDI,ECX + * 004EEAEC 8D7424 18 LEA ESI,DWORD PTR SS:[ESP+0x18] + * 004EEAF0 E8 CB27F1FF CALL .004012C0 + * 004EEAF5 8B7C24 48 MOV EDI,DWORD PTR SS:[ESP+0x48] + * 004EEAF9 895C24 3C MOV DWORD PTR SS:[ESP+0x3C],EBX + * 004EEAFD 8B75 3C MOV ESI,DWORD PTR SS:[EBP+0x3C] + * 004EEB00 E8 1B4A0100 CALL .00503520 + * 004EEB05 8BF8 MOV EDI,EAX + * 004EEB07 8DB7 E4000000 LEA ESI,DWORD PTR DS:[EDI+0xE4] + * 004EEB0D 8D4424 14 LEA EAX,DWORD PTR SS:[ESP+0x14] + * 004EEB11 8BD6 MOV EDX,ESI + * 004EEB13 E8 985CF1FF CALL .004047B0 + * 004EEB18 BD 10000000 MOV EBP,0x10 + * 004EEB1D 84C0 TEST AL,AL + * 004EEB1F 75 18 JNZ SHORT .004EEB39 + * 004EEB21 895E 10 MOV DWORD PTR DS:[ESI+0x10],EBX + * 004EEB24 396E 14 CMP DWORD PTR DS:[ESI+0x14],EBP + * 004EEB27 72 02 JB SHORT .004EEB2B + * 004EEB29 8B36 MOV ESI,DWORD PTR DS:[ESI] + * 004EEB2B 8D4424 14 LEA EAX,DWORD PTR SS:[ESP+0x14] + * 004EEB2F 50 PUSH EAX + * 004EEB30 8BCF MOV ECX,EDI + * 004EEB32 881E MOV BYTE PTR DS:[ESI],BL + * 004EEB34 E8 67CB0100 CALL .0050B6A0 ; jichi: ATcode modified here, text is on the top of the stack + * 004EEB39 396C24 28 CMP DWORD PTR SS:[ESP+0x28],EBP + * 004EEB3D 72 0D JB SHORT .004EEB4C + * 004EEB3F 8B4C24 14 MOV ECX,DWORD PTR SS:[ESP+0x14] + * 004EEB43 51 PUSH ECX + * 004EEB44 E8 42DC1900 CALL .0068C78B + * 004EEB49 83C4 04 ADD ESP,0x4 + * 004EEB4C B0 01 MOV AL,0x1 + * 004EEB4E 8B4C24 34 MOV ECX,DWORD PTR SS:[ESP+0x34] + * 004EEB52 64:890D 00000000 MOV DWORD PTR FS:[0],ECX + * 004EEB59 59 POP ECX + * 004EEB5A 5F POP EDI + * 004EEB5B 5E POP ESI + * 004EEB5C 5D POP EBP + * 004EEB5D 5B POP EBX + * 004EEB5E 8B4C24 1C MOV ECX,DWORD PTR SS:[ESP+0x1C] + * 004EEB62 33CC XOR ECX,ESP + * 004EEB64 E8 9CD61900 CALL .0068C205 + * 004EEB69 83C4 2C ADD ESP,0x2C + * 004EEB6C C3 RETN + * 004EEB6D CC INT3 + * 004EEB6E CC INT3 + * + * Actual binary patch for Evenicle exe: http://capita.tistory.com/m/post/256 + * {005E393B(EB), 004EEB34(E9 13 B6 21 00), 005C71E0(E9 48 2F 14 00), 005B6494(E9 10 3D 15 00), 0070A10F(90 90 90 90 90 E8 F7 9F EB FF E9 C7 D0 EB FF 90 90 90 90 90 E8 78 15 E0 FF E9 0C 4A DE FF 50 8B 87 B0 00 00 00 66 81 38 84 00 75 0E 83 78 EA 5B 75 08 E8 A2 00 00 00 58 EB C6 58 EB C8 50 52 BA E0 0B 7A 00 60 89 D7 8B 74 E4 28 B9 06 00 00 00 F3 A5 61 8B 44 E4 08 8B 40 10 85 C0 74 29 8B 44 E4 08 8B 40 14 83 F8 0F 75 08 89 54 E4 08 5A 58 EB 9D 8D 42 20 60 89 C7 8B 32 8B 4A 14 83 C1 09 F3 A4 61 89 02 EB E3 5A 58 EB 89 90 90 90 90 90 E8 6C 9F EB FF E9 F0 C2 EA FF 50 8B 44 E4 04 83 78 0C 01 76 31 8B 87 84 02 00 00 66 83 78 FC 46 75 24 83 78 F8 22 74 16 83 78 F8 13 75 18 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 E8 06 00 00 00 58 EB B5 58 EB B7 60 8B 74 E4 28 BF E0 0B 7A 00 89 7C E4 28 B9 0C 00 00 00 F3 A5 61 C3)} + * + * ATcode: FORCEFONT(5),ENCODEKOR,FONT(Malgun Gothic,-13),HOOK(0x0070A10F,TRANS([[ESP]+0x8],LEN([ESP]+0XC),PTRCHEAT),RETNPOS(COPY)),HOOK(0x0070A11E,TRANS([ESP],SMSTR(IGNORE)),RETNPOS(COPY)),HOOK(0x0070A19A,TRANS([[ESP]+0x8],LEN([ESP]+0XC),PTRCHEAT),RETNPOS(COPY)) + * FilterCode: DenyWord{CUT(2)},FixLine{},KoFilter{},DumpText{},CustomDic{CDic},CustomScript{Write,Pass(-1),Cache} + * + * The second hooked address pointed to the text address. + * The logic here is simplify buffer the read text, and replace the text by zero + * Then translate/paint them together. + * Several variables near the text address is used to check if the text is finished or not. + * + * Function immediately before patched code: + * 0070A09E CC INT3 + * 0070A09F CC INT3 + * 0070A0A0 6A FF PUSH -0x1 + * 0070A0A2 68 358A7000 PUSH .00708A35 + * 0070A0A7 64:A1 00000000 MOV EAX,DWORD PTR FS:[0] + * 0070A0AD 50 PUSH EAX + * 0070A0AE 51 PUSH ECX + * 0070A0AF 56 PUSH ESI + * 0070A0B0 A1 DCC47700 MOV EAX,DWORD PTR DS:[0x77C4DC] + * 0070A0B5 33C4 XOR EAX,ESP + * 0070A0B7 50 PUSH EAX + * 0070A0B8 8D4424 0C LEA EAX,DWORD PTR SS:[ESP+0xC] + * 0070A0BC 64:A3 00000000 MOV DWORD PTR FS:[0],EAX + * 0070A0C2 C74424 14 000000>MOV DWORD PTR SS:[ESP+0x14],0x0 + * 0070A0CA A1 54D17900 MOV EAX,DWORD PTR DS:[0x79D154] + * 0070A0CF 8B08 MOV ECX,DWORD PTR DS:[EAX] + * 0070A0D1 50 PUSH EAX + * 0070A0D2 51 PUSH ECX + * 0070A0D3 8D7424 10 LEA ESI,DWORD PTR SS:[ESP+0x10] + * 0070A0D7 E8 6416F8FF CALL .0068B740 + * 0070A0DC A1 54D17900 MOV EAX,DWORD PTR DS:[0x79D154] + * 0070A0E1 50 PUSH EAX + * 0070A0E2 E8 A426F8FF CALL .0068C78B + * 0070A0E7 83C4 04 ADD ESP,0x4 + * 0070A0EA 8B4C24 0C MOV ECX,DWORD PTR SS:[ESP+0xC] + * 0070A0EE 64:890D 00000000 MOV DWORD PTR FS:[0],ECX + * 0070A0F5 59 POP ECX + * 0070A0F6 5E POP ESI + * 0070A0F7 83C4 10 ADD ESP,0x10 + * 0070A0FA C3 RETN + * 0070A0FB C705 C4C17900 64>MOV DWORD PTR DS:[0x79C1C4],.0070B664 + * 0070A105 B9 C4C17900 MOV ECX,.0079C1C4 + * 0070A10A ^E9 0722F8FF JMP .0068C316 + * + * Patched code: + * 0070A10F 90 NOP ; jichi: ATcode hooked here + * 0070A110 90 NOP + * 0070A111 90 NOP + * 0070A112 90 NOP + * 0070A113 90 NOP + * 0070A114 E8 F79FEBFF CALL .005C4110 + * 0070A119 ^E9 C7D0EBFF JMP .005C71E5 + * 0070A11E 90 NOP + * 0070A11F 90 NOP + * 0070A120 90 NOP + * 0070A121 90 NOP + * 0070A122 90 NOP + * 0070A123 E8 7815E0FF CALL .0050B6A0 ; jichi: call the original function for hookpoint #2 + * 0070A128 ^E9 0C4ADEFF JMP .004EEB39 ; jichi: come back to hookpoint#2 + * 0070A12D 50 PUSH EAX ; jichi: this is for hookpoint #3, translate the text before send it to paint + * 0070A12E 8B87 B0000000 MOV EAX,DWORD PTR DS:[EDI+0xB0] + * 0070A134 66:8138 8400 CMP WORD PTR DS:[EAX],0x84 + * 0070A139 75 0E JNZ SHORT .0070A149 + * 0070A13B 8378 EA 5B CMP DWORD PTR DS:[EAX-0x16],0x5B + * 0070A13F 75 08 JNZ SHORT .0070A149 + * 0070A141 E8 A2000000 CALL .0070A1E8 + * 0070A146 58 POP EAX + * 0070A147 ^EB C6 JMP SHORT .0070A10F + * 0070A149 58 POP EAX + * 0070A14A ^EB C8 JMP SHORT .0070A114 + * 0070A14C 50 PUSH EAX ; jichi: hookpoint#2 jmp here, text address is in [esp] + * 0070A14D 52 PUSH EDX + * 0070A14E BA E00B7A00 MOV EDX,.007A0BE0 ; jichi: 007A0BE0 points to unused zeroed memory + * 0070A153 60 PUSHAD ; jichi esp -= 0x20, now, esp[0x28] is text address, esp[0x24] = eax, and esp[0x20] = edx + * 0070A154 89D7 MOV EDI,EDX ; set 007A0BE0 as the target buffer to save text, edx is never modified + * 0070A156 8B74E4 28 MOV ESI,DWORD PTR SS:[ESP+0x28] ; set source text as target + * 0070A15A B9 06000000 MOV ECX,0x6 ; move for 6 bytes + * 0070A15F F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] + * 0070A161 61 POPAD ; finished saving text, now [esp] is old edx, esp[0x4] is old eax, esp[0x8] is old text address + * 0070A162 8B44E4 08 MOV EAX,DWORD PTR SS:[ESP+0x8] ; eax = original text address + * 0070A166 8B40 10 MOV EAX,DWORD PTR DS:[EAX+0x10] ; eax = text[0x10] + * 0070A169 85C0 TEST EAX,EAX ; if end of text, + * 0070A16B 74 29 JE SHORT .0070A196 ; jump if eax is zero, comeback to hookpoint and ignore it + * 0070A16D 8B44E4 08 MOV EAX,DWORD PTR SS:[ESP+0x8] ; otherwise, if eax is not zero + * 0070A171 8B40 14 MOV EAX,DWORD PTR DS:[EAX+0x14] ; eax = text[0x14] + * 0070A174 83F8 0F CMP EAX,0xF ; jichi: compare text[0x14] with 0xf + * 0070A177 75 08 JNZ SHORT .0070A181 ; jump if not zero leaving text not modified, other continue and modify the text + * 0070A179 8954E4 08 MOV DWORD PTR SS:[ESP+0x8],EDX ; override esp+8 with edx, i.e. override text address by new text address and do translation + * 0070A17D 5A POP EDX + * 0070A17E 58 POP EAX ; jichi: restore edx and eax, now esp is back to normal. [esp] is the new text address + * 0070A17F ^EB 9D JMP SHORT .0070A11E ; jichi: jump to the top of the hooked place (nop) and do translation before coming back + * 0070A181 8D42 20 LEA EAX,DWORD PTR DS:[EDX+0x20] ; text is not modified, esp[0x8] is the text address, edx is the modified buffer, eax = buffer[0x20] address + * 0070A184 60 PUSHAD ; jichi: esp[0x28] is now the text address + * 0070A185 89C7 MOV EDI,EAX ; jichi: edx[0x20] is the target + * 0070A187 8B32 MOV ESI,DWORD PTR DS:[EDX] ; jichi: edx is the source + * 0070A189 8B4A 14 MOV ECX,DWORD PTR DS:[EDX+0x14] + * 0070A18C 83C1 09 ADD ECX,0x9 ; move for [edx+0x14]+0x9 time + * 0070A18F F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] ; jichi: shift text by 0x14 dword ptr + * 0070A191 61 POPAD ; jichi: now esp[0x8] is the text address + * 0070A192 8902 MOV DWORD PTR DS:[EDX],EAX ; eax is the new text address (edx+0x20), move the address to beginning of buffer ([edx]), i.e. edx is pointed to zero memory now + * 0070A194 ^EB E3 JMP SHORT .0070A179 ; come bback to modify the text address + * 0070A196 5A POP EDX + * 0070A197 58 POP EAX + * 0070A198 ^EB 89 JMP SHORT .0070A123 ; jichi: come back to call + * 0070A19A 90 NOP + * 0070A19B 90 NOP + * 0070A19C 90 NOP + * 0070A19D 90 NOP + * 0070A19E 90 NOP + * 0070A19F E8 6C9FEBFF CALL .005C4110 + * 0070A1A4 ^E9 F0C2EAFF JMP .005B6499 + * 0070A1A9 50 PUSH EAX ; jichi: from hookpoint #4 + * 0070A1AA 8B44E4 04 MOV EAX,DWORD PTR SS:[ESP+0x4] ; jichi: move top of the old stack address to eax + * 0070A1AE 8378 0C 01 CMP DWORD PTR DS:[EAX+0xC],0x1 + * 0070A1B2 76 31 JBE SHORT .0070A1E5 ; jichi: jump to leave if text[0xc] <= 0x1 + * 0070A1B4 8B87 84020000 MOV EAX,DWORD PTR DS:[EDI+0x284] + * 0070A1BA 66:8378 FC 46 CMP WORD PTR DS:[EAX-0x4],0x46 + * 0070A1BF 75 24 JNZ SHORT .0070A1E5 + * 0070A1C1 8378 F8 22 CMP DWORD PTR DS:[EAX-0x8],0x22 + * 0070A1C5 74 16 JE SHORT .0070A1DD + * 0070A1C7 8378 F8 13 CMP DWORD PTR DS:[EAX-0x8],0x13 + * 0070A1CB 75 18 JNZ SHORT .0070A1E5 + * 0070A1CD 90 NOP + * 0070A1CE 90 NOP + * 0070A1CF 90 NOP + * 0070A1D0 90 NOP + * 0070A1D1 90 NOP + * 0070A1D2 90 NOP + * 0070A1D3 90 NOP + * 0070A1D4 90 NOP + * 0070A1D5 90 NOP + * 0070A1D6 90 NOP + * 0070A1D7 90 NOP + * 0070A1D8 90 NOP + * 0070A1D9 90 NOP + * 0070A1DA 90 NOP + * 0070A1DB 90 NOP + * 0070A1DC 90 NOP + * 0070A1DD E8 06000000 CALL .0070A1E8 + * 0070A1E2 58 POP EAX + * 0070A1E3 ^EB B5 JMP SHORT .0070A19A + * 0070A1E5 58 POP EAX + * 0070A1E6 ^EB B7 JMP SHORT .0070A19F + * 0070A1E8 60 PUSHAD + * 0070A1E9 8B74E4 28 MOV ESI,DWORD PTR SS:[ESP+0x28] + * 0070A1ED BF E00B7A00 MOV EDI,.007A0BE0 + * 0070A1F2 897CE4 28 MOV DWORD PTR SS:[ESP+0x28],EDI + * 0070A1F6 B9 0C000000 MOV ECX,0xC + * 0070A1FB F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] + * 0070A1FD 61 POPAD + * 0070A1FE C3 RETN + * 0070A1FF 0000 ADD BYTE PTR DS:[EAX],AL + * 0070A201 0000 ADD BYTE PTR DS:[EAX],AL + * 0070A203 0000 ADD BYTE PTR DS:[EAX],AL + * + * Modified places: + * + * 005E391C CC INT3 + * 005E391D CC INT3 + * 005E391E CC INT3 + * 005E391F CC INT3 + * 005E3920 55 PUSH EBP + * 005E3921 8BEC MOV EBP,ESP + * 005E3923 83E4 C0 AND ESP,0xFFFFFFC0 + * 005E3926 83EC 34 SUB ESP,0x34 + * 005E3929 53 PUSH EBX + * 005E392A 8B5D 08 MOV EBX,DWORD PTR SS:[EBP+0x8] + * 005E392D 817B 04 00010000 CMP DWORD PTR DS:[EBX+0x4],0x100 + * 005E3934 56 PUSH ESI + * 005E3935 57 PUSH EDI + * 005E3936 8B7D 0C MOV EDI,DWORD PTR SS:[EBP+0xC] + * 005E3939 8BF0 MOV ESI,EAX + * 005E393B EB 67 JMP SHORT .005E39A4 ; jichi: here modified point#1, change to always jump to 5e39a4, when enabled it will change font size + * 005E393D 8D4424 28 LEA EAX,DWORD PTR SS:[ESP+0x28] + * 005E3941 50 PUSH EAX + * 005E3942 8D4C24 30 LEA ECX,DWORD PTR SS:[ESP+0x30] + * + * 004EEA6E CC INT3 + * 004EEA6F CC INT3 + * 004EEA70 6A FF PUSH -0x1 + * 004EEA72 68 E8267000 PUSH .007026E8 + * 004EEA77 64:A1 00000000 MOV EAX,DWORD PTR FS:[0] + * 004EEA7D 50 PUSH EAX + * 004EEA7E 83EC 20 SUB ESP,0x20 + * 004EEA81 A1 DCC47700 MOV EAX,DWORD PTR DS:[0x77C4DC] + * 004EEA86 33C4 XOR EAX,ESP + * 004EEA88 894424 1C MOV DWORD PTR SS:[ESP+0x1C],EAX + * 004EEA8C 53 PUSH EBX + * 004EEA8D 55 PUSH EBP + * 004EEA8E 56 PUSH ESI + * 004EEA8F 57 PUSH EDI + * 004EEA90 A1 DCC47700 MOV EAX,DWORD PTR DS:[0x77C4DC] + * 004EEA95 33C4 XOR EAX,ESP + * 004EEA97 50 PUSH EAX + * 004EEA98 8D4424 34 LEA EAX,DWORD PTR SS:[ESP+0x34] + * 004EEA9C 64:A3 00000000 MOV DWORD PTR FS:[0],EAX + * 004EEAA2 8B4424 44 MOV EAX,DWORD PTR SS:[ESP+0x44] + * 004EEAA6 8BF1 MOV ESI,ECX + * 004EEAA8 E8 8346FBFF CALL .004A3130 + * 004EEAAD 8BE8 MOV EBP,EAX + * 004EEAAF 33DB XOR EBX,EBX + * 004EEAB1 3BEB CMP EBP,EBX + * 004EEAB3 75 07 JNZ SHORT .004EEABC + * 004EEAB5 32C0 XOR AL,AL + * 004EEAB7 E9 92000000 JMP .004EEB4E + * 004EEABC 8B06 MOV EAX,DWORD PTR DS:[ESI] + * 004EEABE 8B10 MOV EDX,DWORD PTR DS:[EAX] + * 004EEAC0 8BCE MOV ECX,ESI + * 004EEAC2 FFD2 CALL EDX + * 004EEAC4 8BC8 MOV ECX,EAX + * 004EEAC6 C74424 28 0F0000>MOV DWORD PTR SS:[ESP+0x28],0xF + * 004EEACE 895C24 24 MOV DWORD PTR SS:[ESP+0x24],EBX + * 004EEAD2 885C24 14 MOV BYTE PTR SS:[ESP+0x14],BL + * 004EEAD6 8D71 01 LEA ESI,DWORD PTR DS:[ECX+0x1] + * 004EEAD9 8DA424 00000000 LEA ESP,DWORD PTR SS:[ESP] + * 004EEAE0 8A11 MOV DL,BYTE PTR DS:[ECX] + * 004EEAE2 41 INC ECX + * 004EEAE3 3AD3 CMP DL,BL + * 004EEAE5 ^75 F9 JNZ SHORT .004EEAE0 + * 004EEAE7 2BCE SUB ECX,ESI + * 004EEAE9 50 PUSH EAX + * 004EEAEA 8BF9 MOV EDI,ECX + * 004EEAEC 8D7424 18 LEA ESI,DWORD PTR SS:[ESP+0x18] + * 004EEAF0 E8 CB27F1FF CALL .004012C0 + * 004EEAF5 8B7C24 48 MOV EDI,DWORD PTR SS:[ESP+0x48] + * 004EEAF9 895C24 3C MOV DWORD PTR SS:[ESP+0x3C],EBX + * 004EEAFD 8B75 3C MOV ESI,DWORD PTR SS:[EBP+0x3C] + * 004EEB00 E8 1B4A0100 CALL .00503520 + * 004EEB05 8BF8 MOV EDI,EAX + * 004EEB07 8DB7 E4000000 LEA ESI,DWORD PTR DS:[EDI+0xE4] + * 004EEB0D 8D4424 14 LEA EAX,DWORD PTR SS:[ESP+0x14] + * 004EEB11 8BD6 MOV EDX,ESI + * 004EEB13 E8 985CF1FF CALL .004047B0 + * 004EEB18 BD 10000000 MOV EBP,0x10 + * 004EEB1D 84C0 TEST AL,AL + * 004EEB1F 75 18 JNZ SHORT .004EEB39 + * 004EEB21 895E 10 MOV DWORD PTR DS:[ESI+0x10],EBX + * 004EEB24 396E 14 CMP DWORD PTR DS:[ESI+0x14],EBP + * 004EEB27 72 02 JB SHORT .004EEB2B + * 004EEB29 8B36 MOV ESI,DWORD PTR DS:[ESI] + * 004EEB2B 8D4424 14 LEA EAX,DWORD PTR SS:[ESP+0x14] + * 004EEB2F 50 PUSH EAX + * 004EEB30 8BCF MOV ECX,EDI + * 004EEB32 881E MOV BYTE PTR DS:[ESI],BL + * 004EEB34 E9 13B62100 JMP .0070A14C ; jichi: here hookpoint#2, name is modified here, scenario and names are here accessed char by char on the top of the stack + * 004EEB39 396C24 28 CMP DWORD PTR SS:[ESP+0x28],EBP + * 004EEB3D 72 0D JB SHORT .004EEB4C + * 004EEB3F 8B4C24 14 MOV ECX,DWORD PTR SS:[ESP+0x14] + * 004EEB43 51 PUSH ECX + * 004EEB44 E8 42DC1900 CALL .0068C78B + * 004EEB49 83C4 04 ADD ESP,0x4 + * 004EEB4C B0 01 MOV AL,0x1 + * 004EEB4E 8B4C24 34 MOV ECX,DWORD PTR SS:[ESP+0x34] + * 004EEB52 64:890D 00000000 MOV DWORD PTR FS:[0],ECX + * 004EEB59 59 POP ECX + * 004EEB5A 5F POP EDI + * 004EEB5B 5E POP ESI + * 004EEB5C 5D POP EBP + * 004EEB5D 5B POP EBX + * 004EEB5E 8B4C24 1C MOV ECX,DWORD PTR SS:[ESP+0x1C] + * 004EEB62 33CC XOR ECX,ESP + * 004EEB64 E8 9CD61900 CALL .0068C205 + * 004EEB69 83C4 2C ADD ESP,0x2C + * 004EEB6C C3 RETN + * 004EEB6D CC INT3 + * 004EEB6E CC INT3 + * + * 005C70EE CC INT3 + * 005C70EF CC INT3 + * 005C70F0 83EC 18 SUB ESP,0x18 + * 005C70F3 A1 DCC47700 MOV EAX,DWORD PTR DS:[0x77C4DC] + * 005C70F8 33C4 XOR EAX,ESP + * 005C70FA 894424 14 MOV DWORD PTR SS:[ESP+0x14],EAX + * 005C70FE 53 PUSH EBX + * 005C70FF 8B5C24 20 MOV EBX,DWORD PTR SS:[ESP+0x20] + * 005C7103 55 PUSH EBP + * 005C7104 8B6C24 2C MOV EBP,DWORD PTR SS:[ESP+0x2C] + * 005C7108 8B45 1C MOV EAX,DWORD PTR SS:[EBP+0x1C] + * 005C710B 56 PUSH ESI + * 005C710C 8BF2 MOV ESI,EDX + * 005C710E 57 PUSH EDI + * 005C710F 8BF9 MOV EDI,ECX + * 005C7111 897424 10 MOV DWORD PTR SS:[ESP+0x10],ESI + * 005C7115 83F8 44 CMP EAX,0x44 + * 005C7118 77 7A JA SHORT .005C7194 + * 005C711A 0FB680 7C735C00 MOVZX EAX,BYTE PTR DS:[EAX+0x5C737C] + * 005C7121 FF2485 60735C00 JMP DWORD PTR DS:[EAX*4+0x5C7360] + * 005C7128 8B4B 0C MOV ECX,DWORD PTR DS:[EBX+0xC] + * 005C712B 8B4424 30 MOV EAX,DWORD PTR SS:[ESP+0x30] + * 005C712F C1E9 02 SHR ECX,0x2 + * 005C7132 3BC1 CMP EAX,ECX + * 005C7134 73 5E JNB SHORT .005C7194 + * 005C7136 837B 0C 00 CMP DWORD PTR DS:[EBX+0xC],0x0 + * 005C713A 75 1C JNZ SHORT .005C7158 + * 005C713C 33DB XOR EBX,EBX + * 005C713E 5F POP EDI + * 005C713F 893483 MOV DWORD PTR DS:[EBX+EAX*4],ESI + * 005C7142 5E POP ESI + * 005C7143 5D POP EBP + * 005C7144 B0 01 MOV AL,0x1 + * 005C7146 5B POP EBX + * 005C7147 8B4C24 14 MOV ECX,DWORD PTR SS:[ESP+0x14] + * 005C714B 33CC XOR ECX,ESP + * 005C714D E8 B3500C00 CALL .0068C205 + * 005C7152 83C4 18 ADD ESP,0x18 + * 005C7155 C2 0C00 RETN 0xC + * 005C7158 8B5B 08 MOV EBX,DWORD PTR DS:[EBX+0x8] + * 005C715B 5F POP EDI + * 005C715C 893483 MOV DWORD PTR DS:[EBX+EAX*4],ESI + * 005C715F 5E POP ESI + * 005C7160 5D POP EBP + * 005C7161 B0 01 MOV AL,0x1 + * 005C7163 5B POP EBX + * 005C7164 8B4C24 14 MOV ECX,DWORD PTR SS:[ESP+0x14] + * 005C7168 33CC XOR ECX,ESP + * 005C716A E8 96500C00 CALL .0068C205 + * 005C716F 83C4 18 ADD ESP,0x18 + * 005C7172 C2 0C00 RETN 0xC + * 005C7175 F3:0F104424 10 MOVSS XMM0,DWORD PTR SS:[ESP+0x10] + * 005C717B 51 PUSH ECX + * 005C717C 8B4C24 34 MOV ECX,DWORD PTR SS:[ESP+0x34] + * 005C7180 8BC3 MOV EAX,EBX + * 005C7182 F3:0F110424 MOVSS DWORD PTR SS:[ESP],XMM0 + * 005C7187 E8 14C7FFFF CALL .005C38A0 + * 005C718C 84C0 TEST AL,AL + * 005C718E 0F85 B2010000 JNZ .005C7346 + * 005C7194 5F POP EDI + * 005C7195 5E POP ESI + * 005C7196 5D POP EBP + * 005C7197 32C0 XOR AL,AL + * 005C7199 5B POP EBX + * 005C719A 8B4C24 14 MOV ECX,DWORD PTR SS:[ESP+0x14] + * 005C719E 33CC XOR ECX,ESP + * 005C71A0 E8 60500C00 CALL .0068C205 + * 005C71A5 83C4 18 ADD ESP,0x18 + * 005C71A8 C2 0C00 RETN 0xC + * 005C71AB 8B4C24 30 MOV ECX,DWORD PTR SS:[ESP+0x30] + * 005C71AF 8D5424 10 LEA EDX,DWORD PTR SS:[ESP+0x10] + * 005C71B3 52 PUSH EDX + * 005C71B4 8BC3 MOV EAX,EBX + * 005C71B6 E8 25C7FFFF CALL .005C38E0 + * 005C71BB 84C0 TEST AL,AL + * 005C71BD ^74 D5 JE SHORT .005C7194 + * 005C71BF 8B4C24 10 MOV ECX,DWORD PTR SS:[ESP+0x10] + * 005C71C3 8BC7 MOV EAX,EDI + * 005C71C5 E8 D6F0FFFF CALL .005C62A0 + * 005C71CA 8BD8 MOV EBX,EAX + * 005C71CC 8BCE MOV ECX,ESI + * 005C71CE 8BC7 MOV EAX,EDI + * 005C71D0 E8 CBF0FFFF CALL .005C62A0 + * 005C71D5 85DB TEST EBX,EBX + * 005C71D7 ^74 BB JE SHORT .005C7194 + * 005C71D9 85C0 TEST EAX,EAX + * 005C71DB ^74 B7 JE SHORT .005C7194 + * 005C71DD 50 PUSH EAX + * 005C71DE 8BC3 MOV EAX,EBX + * 005C71E0 E8 2BCFFFFF CALL .005C4110 ; original function call + * //005C71E0 E9 482F1400 JMP .0070A12D ; jichi: here hookpoint#3, text is modified here, text in [[esp]+0x8]], length in [esp]+0xc + * 005C71E5 ^EB A5 JMP SHORT .005C718C + * 005C71E7 8B47 08 MOV EAX,DWORD PTR DS:[EDI+0x8] + * 005C71EA 8B4F 0C MOV ECX,DWORD PTR DS:[EDI+0xC] + * 005C71ED 2BC8 SUB ECX,EAX + * 005C71EF C1F9 02 SAR ECX,0x2 + * 005C71F2 3BF1 CMP ESI,ECX + * 005C71F4 ^73 9E JNB SHORT .005C7194 + * 005C71F6 8B34B0 MOV ESI,DWORD PTR DS:[EAX+ESI*4] + * 005C71F9 85F6 TEST ESI,ESI + * 005C71FB ^74 97 JE SHORT .005C7194 + * + * 005B640E CC INT3 + * 005B640F CC INT3 + * 005B6410 53 PUSH EBX + * 005B6411 56 PUSH ESI + * 005B6412 B9 FCFFFFFF MOV ECX,-0x4 + * 005B6417 57 PUSH EDI + * 005B6418 8BF8 MOV EDI,EAX + * 005B641A 018F B0020000 ADD DWORD PTR DS:[EDI+0x2B0],ECX + * 005B6420 8B87 B0020000 MOV EAX,DWORD PTR DS:[EDI+0x2B0] + * 005B6426 8B30 MOV ESI,DWORD PTR DS:[EAX] + * 005B6428 018F B0020000 ADD DWORD PTR DS:[EDI+0x2B0],ECX + * 005B642E 8B87 B0020000 MOV EAX,DWORD PTR DS:[EDI+0x2B0] + * 005B6434 8B08 MOV ECX,DWORD PTR DS:[EAX] + * 005B6436 8B87 E0010000 MOV EAX,DWORD PTR DS:[EDI+0x1E0] + * 005B643C 2B87 DC010000 SUB EAX,DWORD PTR DS:[EDI+0x1DC] + * 005B6442 C1F8 02 SAR EAX,0x2 + * 005B6445 3BF0 CMP ESI,EAX + * 005B6447 73 0D JNB SHORT .005B6456 + * 005B6449 8B87 DC010000 MOV EAX,DWORD PTR DS:[EDI+0x1DC] + * 005B644F 8B14B0 MOV EDX,DWORD PTR DS:[EAX+ESI*4] + * 005B6452 85D2 TEST EDX,EDX + * 005B6454 75 13 JNZ SHORT .005B6469 + * 005B6456 68 70757200 PUSH .00727570 + * 005B645B 8BCF MOV ECX,EDI + * 005B645D E8 AEC9FFFF CALL .005B2E10 + * 005B6462 83C4 04 ADD ESP,0x4 + * 005B6465 5F POP EDI + * 005B6466 5E POP ESI + * 005B6467 5B POP EBX + * 005B6468 C3 RETN + * 005B6469 8B9F E0010000 MOV EBX,DWORD PTR DS:[EDI+0x1E0] + * 005B646F 2BD8 SUB EBX,EAX + * 005B6471 C1FB 02 SAR EBX,0x2 + * 005B6474 3BCB CMP ECX,EBX + * 005B6476 73 07 JNB SHORT .005B647F + * 005B6478 8B0488 MOV EAX,DWORD PTR DS:[EAX+ECX*4] + * 005B647B 85C0 TEST EAX,EAX + * 005B647D 75 14 JNZ SHORT .005B6493 + * 005B647F 51 PUSH ECX + * 005B6480 68 A0757200 PUSH .007275A0 + * 005B6485 8BCF MOV ECX,EDI + * 005B6487 E8 84C9FFFF CALL .005B2E10 + * 005B648C 83C4 08 ADD ESP,0x8 + * 005B648F 5F POP EDI + * 005B6490 5E POP ESI + * 005B6491 5B POP EBX + * 005B6492 C3 RETN + * 005B6493 52 PUSH EDX + * 005B6494 E8 77DC0000 CALL .005C4110 + * //005B6494 E9 103D1500 JMP .0070A1A9 ; jichi: here hookpoint#4 + * 005B6499 84C0 TEST AL,AL + * 005B649B 75 16 JNZ SHORT .005B64B3 + * 005B649D 68 D4757200 PUSH .007275D4 + * 005B64A2 B9 F0757200 MOV ECX,.007275F0 ; ASCII "S_ASSIGN" + * 005B64A7 E8 84C8FFFF CALL .005B2D30 + * 005B64AC 83C4 04 ADD ESP,0x4 + * 005B64AF 5F POP EDI + * 005B64B0 5E POP ESI + * 005B64B1 5B POP EBX + * 005B64B2 C3 RETN + * 005B64B3 8B8F B0020000 MOV ECX,DWORD PTR DS:[EDI+0x2B0] + * 005B64B9 8931 MOV DWORD PTR DS:[ECX],ESI + * 005B64BB 8387 B0020000 04 ADD DWORD PTR DS:[EDI+0x2B0],0x4 + * 005B64C2 5F POP EDI + * 005B64C3 5E POP ESI + * 005B64C4 5B POP EBX + * 005B64C5 C3 RETN + * 005B64C6 CC INT3 + * 005B64C7 CC INT3 + * 005B64C8 CC INT3 + * + * Slightly modified #4 in AliceRunPatch.dll + * 101B6C10 5B POP EBX + * 101B6C11 59 POP ECX + * 101B6C12 C3 RETN + * 101B6C13 52 PUSH EDX + * 101B6C14 8BC1 MOV EAX,ECX + * 101B6C16 E9 4E7D1600 JMP .1031E969 ; jichi: hook here + * 101B6C1B 84C0 TEST AL,AL + * 101B6C1D 75 18 JNZ SHORT .101B6C37 + * 101B6C1F 68 FCB53310 PUSH .1033B5FC + * 101B6C24 B9 18B63310 MOV ECX,.1033B618 ; ASCII "S_ASSIGN" + * 101B6C29 E8 92B8FFFF CALL .101B24C0 + * 101B6C2E 83C4 04 ADD ESP,0x4 + * 101B6C31 5F POP EDI + * 101B6C32 5E POP ESI + * 101B6C33 5D POP EBP + * 101B6C34 5B POP EBX + * 101B6C35 59 POP ECX + * 101B6C36 C3 RETN + * 101B6C37 53 PUSH EBX + * 101B6C38 56 PUSH ESI + * 101B6C39 E8 E29C0100 CALL .101D0920 + * 101B6C3E 5F POP EDI + * 101B6C3F 5E POP ESI + * 101B6C40 5D POP EBP + * 101B6C41 5B POP EBX + * 101B6C42 59 POP ECX + * 101B6C43 C3 RETN + * 101B6C44 CC INT3 + * 101B6C45 CC INT3 + * 101B6C46 CC INT3 + * + * The function get called to paint string of names for hookpoint #2, text in arg1: + * 0050B69E CC INT3 + * 0050B69F CC INT3 + * 0050B6A0 55 PUSH EBP + * 0050B6A1 8BEC MOV EBP,ESP + * 0050B6A3 83E4 F8 AND ESP,0xFFFFFFF8 + * 0050B6A6 6A FF PUSH -0x1 + * 0050B6A8 68 F8277000 PUSH .007027F8 + * 0050B6AD 64:A1 00000000 MOV EAX,DWORD PTR FS:[0] + * 0050B6B3 50 PUSH EAX + * 0050B6B4 83EC 18 SUB ESP,0x18 + * 0050B6B7 53 PUSH EBX + * 0050B6B8 56 PUSH ESI + * 0050B6B9 57 PUSH EDI + * 0050B6BA A1 DCC47700 MOV EAX,DWORD PTR DS:[0x77C4DC] + * 0050B6BF 33C4 XOR EAX,ESP + * 0050B6C1 50 PUSH EAX + * 0050B6C2 8D4424 28 LEA EAX,DWORD PTR SS:[ESP+0x28] + * 0050B6C6 64:A3 00000000 MOV DWORD PTR FS:[0],EAX + * 0050B6CC 8BF9 MOV EDI,ECX + * 0050B6CE 57 PUSH EDI + * 0050B6CF E8 5CEAFFFF CALL .0050A130 + * 0050B6D4 8B45 08 MOV EAX,DWORD PTR SS:[EBP+0x8] + * 0050B6D7 6A FF PUSH -0x1 + * 0050B6D9 33DB XOR EBX,EBX + * 0050B6DB 53 PUSH EBX + * 0050B6DC 8DB7 E4000000 LEA ESI,DWORD PTR DS:[EDI+0xE4] + * 0050B6E2 50 PUSH EAX + * 0050B6E3 E8 886BEFFF CALL .00402270 + * 0050B6E8 895C24 14 MOV DWORD PTR SS:[ESP+0x14],EBX + * 0050B6EC 895C24 18 MOV DWORD PTR SS:[ESP+0x18],EBX + * 0050B6F0 895C24 1C MOV DWORD PTR SS:[ESP+0x1C],EBX + * 0050B6F4 56 PUSH ESI + * 0050B6F5 8D4C24 18 LEA ECX,DWORD PTR SS:[ESP+0x18] + * 0050B6F9 51 PUSH ECX + * 0050B6FA 57 PUSH EDI + * 0050B6FB 895C24 3C MOV DWORD PTR SS:[ESP+0x3C],EBX + * 0050B6FF E8 6C290000 CALL .0050E070 + * 0050B704 8D5424 14 LEA EDX,DWORD PTR SS:[ESP+0x14] + * 0050B708 8BCF MOV ECX,EDI + * 0050B70A E8 B1010000 CALL .0050B8C0 + * 0050B70F 8B7424 14 MOV ESI,DWORD PTR SS:[ESP+0x14] + * 0050B713 C687 E0000000 01 MOV BYTE PTR DS:[EDI+0xE0],0x1 + * 0050B71A 3BF3 CMP ESI,EBX + * 0050B71C 74 14 JE SHORT .0050B732 + * 0050B71E 8B7C24 18 MOV EDI,DWORD PTR SS:[ESP+0x18] + * 0050B722 8BC6 MOV EAX,ESI + * 0050B724 E8 7751F0FF CALL .004108A0 + * 0050B729 56 PUSH ESI + * 0050B72A E8 5C101800 CALL .0068C78B + * 0050B72F 83C4 04 ADD ESP,0x4 + * 0050B732 8B4C24 28 MOV ECX,DWORD PTR SS:[ESP+0x28] + * 0050B736 64:890D 00000000 MOV DWORD PTR FS:[0],ECX + * 0050B73D 59 POP ECX + * 0050B73E 5F POP EDI + * 0050B73F 5E POP ESI + * 0050B740 5B POP EBX + * 0050B741 8BE5 MOV ESP,EBP + * 0050B743 5D POP EBP + * 0050B744 C2 0400 RETN 0x4 + * 0050B747 CC INT3 + * 0050B748 CC INT3 + * 0050B749 CC INT3 + * 0050B74A CC INT3 + * 0050B74B CC INT3 + * 0050B74C CC INT3 + * + * Function get called for hookpoint #3, text in [arg1+0x10], length in arg1+0xc, only for scenario, function call is looped + * 005C410D CC INT3 + * 005C410E CC INT3 + * 005C410F CC INT3 + * 005C4110 53 PUSH EBX + * 005C4111 8B5C24 08 MOV EBX,DWORD PTR SS:[ESP+0x8] + * 005C4115 837B 0C 00 CMP DWORD PTR DS:[EBX+0xC],0x0 + * 005C4119 56 PUSH ESI + * 005C411A 57 PUSH EDI + * 005C411B 8BF0 MOV ESI,EAX + * 005C411D 74 07 JE SHORT .005C4126 + * 005C411F 8B43 08 MOV EAX,DWORD PTR DS:[EBX+0x8] + * 005C4122 85C0 TEST EAX,EAX + * 005C4124 75 04 JNZ SHORT .005C412A + * 005C4126 33C0 XOR EAX,EAX + * 005C4128 EB 0F JMP SHORT .005C4139 + * 005C412A 8D50 01 LEA EDX,DWORD PTR DS:[EAX+0x1] + * 005C412D 8D49 00 LEA ECX,DWORD PTR DS:[ECX] + * 005C4130 8A08 MOV CL,BYTE PTR DS:[EAX] + * 005C4132 40 INC EAX + * 005C4133 84C9 TEST CL,CL + * 005C4135 ^75 F9 JNZ SHORT .005C4130 + * 005C4137 2BC2 SUB EAX,EDX + * 005C4139 8D78 01 LEA EDI,DWORD PTR DS:[EAX+0x1] + * 005C413C 3B7E 0C CMP EDI,DWORD PTR DS:[ESI+0xC] + * 005C413F 76 0F JBE SHORT .005C4150 + * 005C4141 E8 FAF7FFFF CALL .005C3940 + * 005C4146 84C0 TEST AL,AL + * 005C4148 75 06 JNZ SHORT .005C4150 + * 005C414A 5F POP EDI + * 005C414B 5E POP ESI + * 005C414C 5B POP EBX + * 005C414D C2 0400 RETN 0x4 + * 005C4150 837B 0C 00 CMP DWORD PTR DS:[EBX+0xC],0x0 + * 005C4154 75 04 JNZ SHORT .005C415A + * 005C4156 33C9 XOR ECX,ECX + * 005C4158 EB 03 JMP SHORT .005C415D + * 005C415A 8B4B 08 MOV ECX,DWORD PTR DS:[EBX+0x8] + * 005C415D 837E 0C 00 CMP DWORD PTR DS:[ESI+0xC],0x0 + * 005C4161 75 15 JNZ SHORT .005C4178 + * 005C4163 57 PUSH EDI + * 005C4164 33C0 XOR EAX,EAX + * 005C4166 51 PUSH ECX + * 005C4167 50 PUSH EAX + * 005C4168 E8 33400D00 CALL .006981A0 + * 005C416D 83C4 0C ADD ESP,0xC + * 005C4170 5F POP EDI + * 005C4171 5E POP ESI + * 005C4172 B0 01 MOV AL,0x1 + * 005C4174 5B POP EBX + * 005C4175 C2 0400 RETN 0x4 + * 005C4178 8B46 08 MOV EAX,DWORD PTR DS:[ESI+0x8] + * 005C417B 57 PUSH EDI + * 005C417C 51 PUSH ECX + * 005C417D 50 PUSH EAX + * 005C417E E8 1D400D00 CALL .006981A0 + * 005C4183 83C4 0C ADD ESP,0xC + * 005C4186 5F POP EDI + * 005C4187 5E POP ESI + * 005C4188 B0 01 MOV AL,0x1 + * 005C418A 5B POP EBX + * 005C418B C2 0400 RETN 0x4 + * 005C418E CC INT3 + */ +static bool InsertSystem43NewHook(ULONG startAddress, ULONG stopAddress, LPCSTR hookName) +{ + const BYTE bytes[] = { + 0xe8, XX4, // 004eeb34 e8 67cb0100 call .0050b6a0 ; jichi: hook here, text on the top of the stack + 0x39,0x6c,0x24, 0x28, // 004eeb39 396c24 28 cmp dword ptr ss:[esp+0x28],ebp + 0x72, 0x0d, // 004eeb3d 72 0d jb short .004eeb4c + 0x8b,0x4c,0x24, 0x14, // 004eeb3f 8b4c24 14 mov ecx,dword ptr ss:[esp+0x14] + 0x51, // 004eeb43 51 push ecx + 0xe8 //, XX4, // 004eeb44 e8 42dc1900 call .0068c78b + }; + enum { addr_offset = 0 }; + ULONG addr = MemDbg::matchBytes(bytes, sizeof(bytes), startAddress, stopAddress); + //GROWL_DWORD(addr); + if (!addr) { + ConsoleOutput("vnreng:System43+: pattern not found"); + return false; + } + + //addr = *(DWORD *)(addr+1) + addr + 5; // change to hook to the actual address of function being called + + HookParam hp = {}; + hp.address = addr; + hp.type = NO_CONTEXT|USING_STRING|USING_SPLIT|SPLIT_INDIRECT; + //hp.type = NO_CONTEXT|USING_STRING|FIXING_SPLIT; + hp.split_index = 0x10; // use [[esp]+0x10] to differentiate name and thread + //hp.offset = 4 * 1; // text in arg1 + + // Only name can be modified here, where the value of split is 0x6, and text in 0x2 + + ConsoleOutput("vnreng: INSERT System43+"); + NewHook(hp, hookName); + + ConsoleOutput("vnreng:System43+: disable GDI hooks"); // disable hooking to TextOutA, which is cached + DisableGDIHooks(); + return true; +} + +bool InsertSystem43Hook() +{ + //bool patched = IthCheckFile(L"AliceRunPatch.dll"); + bool patched = ::GetModuleHandleA("AliceRunPatch.dll"); + ULONG startAddress, stopAddress; + if (patched ? + !NtInspect::getModuleMemoryRange(L"AliceRunPatch.dll", &startAddress, &stopAddress) : + !NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { + ConsoleOutput("vnreng:System43: failed to get memory range"); + return false; + } + // Insert new hook first + bool ok = InsertSystem43OldHook(startAddress, stopAddress, patched ? "AliceRunPatch43" : "System43"); + ok = InsertSystem43NewHook(startAddress, stopAddress, "System43+") || ok; + return ok; +} + +/******************************************************************************************** +AtelierKaguya hook: + Game folder contains message.dat. Used by AtelierKaguya games. + Usually has font caching issue with TextOutA. + Game engine uses EBP to set up stack frame so we can easily trace back. + Keep step out until it's in main game module. We notice that either register or + stack contains string pointer before call instruction. But it's not quite stable. + In-depth analysis of the called function indicates that there's a loop traverses + the string one character by one. We can set a hook there. + This search process is too complex so I just make use of some characteristic + instruction(add esi,0x40) to locate the right point. +********************************************************************************************/ +bool InsertAtelierHook() +{ + //SafeFillRange(process_name_, &base, &size); + //size=size-base; + //DWORD sig = 0x40c683; // add esi,0x40 + //i=module_base_+SearchPattern(module_base_,module_limit_-module_base_,&sig,3); + DWORD i; + for (i = module_base_; i < module_limit_ - 4; i++) { + DWORD sig = *(DWORD *)i & 0xffffff; + if (0x40c683 == sig) // add esi,0x40 + break; + } + if (i < module_limit_ - 4) + for (DWORD j=i-0x200; i>j; i--) + if (*(DWORD *)i == 0xff6acccc) { // find the function entry + HookParam hp = {}; + hp.address = i+2; + hp.offset = 8; + hp.split = -0x18; + hp.length_offset = 1; + hp.type = USING_SPLIT; + ConsoleOutput("vnreng: INSERT Aterlier KAGUYA"); + NewHook(hp, "Atelier KAGUYA"); + //RegisterEngineType(ENGINE_ATELIER); + return true; + } + + ConsoleOutput("vnreng:Aterlier: failed"); + return false; + //ConsoleOutput("Unknown Atelier KAGUYA engine."); +} +/******************************************************************************************** +CIRCUS hook: + Game folder contains advdata folder. Used by CIRCUS games. + Usually has font caching issues. But trace back from GetGlyphOutline gives a hook + which generate repetition. + If we study circus engine follow Freaka's video, we can easily discover that + in the game main module there is a static buffer, which is filled by new text before + it's drawing to screen. By setting a hardware breakpoint there we can locate the + function filling the buffer. But we don't have to set hardware breakpoint to search + the hook address if we know some characteristic instruction(cmp al,0x24) around there. +********************************************************************************************/ +bool InsertCircusHook1() // jichi 10/2/2013: Change return type to bool +{ + for (DWORD i = module_base_ + 0x1000; i < module_limit_ - 4; i++) + if (*(WORD *)i == 0xa3c) //cmp al, 0xA; je + for (DWORD j = i; j < i + 0x100; j++) { + BYTE c = *(BYTE *)j; + if (c == 0xc3) + break; + if (c == 0xe8) { + DWORD k = *(DWORD *)(j+1)+j+5; + if (k > module_base_ && k < module_limit_) { + HookParam hp = {}; + hp.address = k; + hp.offset = 0xc; + hp.split = -0x18; + hp.length_offset = 1; + hp.type = DATA_INDIRECT|USING_SPLIT; + ConsoleOutput("vnreng: INSERT CIRCUS#1"); + NewHook(hp, "Circus1"); + //RegisterEngineType(ENGINE_CIRCUS); + return true; + } + } + } + //break; + //ConsoleOutput("Unknown CIRCUS engine"); + ConsoleOutput("vnreng:CIRCUS1: failed"); + return false; +} + +/** + * jichi 6/5/2014: Sample function from DC3 at 0x4201d0 + * 004201ce cc int3 + * 004201cf cc int3 + * 004201d0 /$ 8b4c24 08 mov ecx,dword ptr ss:[esp+0x8] + * 004201d4 |. 8a01 mov al,byte ptr ds:[ecx] + * 004201d6 |. 84c0 test al,al + * 004201d8 |. 74 1c je short dc3.004201f6 + * 004201da |. 8b5424 04 mov edx,dword ptr ss:[esp+0x4] + * 004201de |. 8bff mov edi,edi + * 004201e0 |> 3c 24 /cmp al,0x24 + * 004201e2 |. 75 05 |jnz short dc3.004201e9 + * 004201e4 |. 83c1 02 |add ecx,0x2 + * 004201e7 |. eb 04 |jmp short dc3.004201ed + * 004201e9 |> 8802 |mov byte ptr ds:[edx],al + * 004201eb |. 42 |inc edx + * 004201ec |. 41 |inc ecx + * 004201ed |> 8a01 |mov al,byte ptr ds:[ecx] + * 004201ef |. 84c0 |test al,al + * 004201f1 |.^75 ed \jnz short dc3.004201e0 + * 004201f3 |. 8802 mov byte ptr ds:[edx],al + * 004201f5 |. c3 retn + * 004201f6 |> 8b4424 04 mov eax,dword ptr ss:[esp+0x4] + * 004201fa |. c600 00 mov byte ptr ds:[eax],0x0 + * 004201fd \. c3 retn + */ +bool InsertCircusHook2() // jichi 10/2/2013: Change return type to bool +{ + for (DWORD i = module_base_ + 0x1000; i < module_limit_ -4; i++) + if ((*(DWORD *)i & 0xffffff) == 0x75243c) { // cmp al, 24; je + if (DWORD j = SafeFindEntryAligned(i, 0x80)) { + HookParam hp = {}; + hp.address = j; + hp.offset = 0x8; + //hp.filter_fun = CharNewLineFilter; // \n\s* is used to remove new line + hp.type = USING_STRING; + ConsoleOutput("vnreng: INSERT CIRCUS#2"); + //GROWL_DWORD(hp.address); // jichi 6/5/2014: 0x4201d0 for DC3 + NewHook(hp, "Circus"); + //RegisterEngineType(ENGINE_CIRCUS); + return true; + } + break; + } + //ConsoleOutput("Unknown CIRCUS engine."); + ConsoleOutput("vnreng:CIRCUS: failed"); + return false; +} + +/******************************************************************************************** +ShinaRio hook: + Game folder contains rio.ini. + Problem of default hook GetTextExtentPoint32A is that the text repeat one time. + But KF just can't resolve the issue. ShinaRio engine always perform integrity check. + So it's very difficult to insert a hook into the game module. Freaka suggests to refine + the default hook by adding split parameter on the stack. So far there is 2 different + version of ShinaRio engine that needs different split parameter. Seems this value is + fixed to the last stack frame. We just navigate to the entry. There should be a + sub esp,* instruction. This value plus 4 is just the offset we need. + + New ShinaRio engine (>=2.48) uses different approach. +********************************************************************************************/ +namespace { // unnamed +// jichi 3/1/2015: hook for new ShinaRio games +void SpecialHookShina2(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD ptr = *(DWORD*)(esp_base-0x20); ; // jichi: esi + *split = ptr; // [esi] + char* str = *(char**)(ptr+0x160); + strcpy(text_buffer, str); + int skip = 0; + for (str = text_buffer; *str; str++) + if (str[0] == 0x5f) { // jichi 7/10/2015: Skip _r (new line) + if (str[1] == 0x72) // jichi 7/10/2015: Skip _t until / + str[0] = str[1]=1; + else if (str[1] == 0x74) { + while (str[0] != 0x2f) + *str++ = 1; + *str=1; + } + } + + for (str = text_buffer; str[skip];) + if (str[skip] == 1) + skip++; + else { + str[0]=str[skip]; + str++; + } + + str[0] = 0; + if (strcmp(text_buffer, text_buffer_prev) == 0) + *len=0; + else { + for (skip = 0; text_buffer[skip]; skip++) + text_buffer_prev[skip] = text_buffer[skip]; + text_buffer_prev[skip] = 0; + *data = (DWORD)text_buffer_prev; + *len = skip; + } +} + +// jichi 3/1/2015: hook for old ShinaRio games +// Used to merge correct text thread. +// 1. Only keep threads with 0 and -1 split +// 2. Skip the thread withb 0 split and with minimum return address +void SpecialHookShina1(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + static DWORD min_retaddr = -1; + DWORD s = *(DWORD *)(esp_base + hp->split); + if (s == 0 || (s & 0xffff) == 0xffff) { // only keep threads with 0 and -1 split + if (s == 0 && retof(esp_base) <= min_retaddr) { + min_retaddr = retof(esp_base); + return; + } + *split = FIXED_SPLIT_VALUE; + // Follow the same logic as the hook. + *data = *(DWORD *)*data; // DATA_INDIRECT + *len = LeadByteTable[*data & 0xff]; + } +} + +// jichi 8/27/2013 +// Return ShinaRio version number +// The head of Rio.ini usually looks like: +// [椎名里�v2.49] +// This function will return 49 in the above case. +// +// Games from アトリエさく�do not have Rio.ini, but $procname.ini. +int GetShinaRioVersion() +{ + int ret = 0; + HANDLE hFile = IthCreateFile(L"RIO.INI", FILE_READ_DATA, FILE_SHARE_READ, FILE_OPEN); + if (hFile == INVALID_HANDLE_VALUE) { + size_t len = ::wcslen(process_name_); + if (len > 3) { + wchar_t fname[MAX_PATH]; + ::wcscpy(fname, process_name_); + fname[len -1] = 'i'; + fname[len -2] = 'n'; + fname[len -3] = 'i'; + hFile = IthCreateFile(fname, FILE_READ_DATA, FILE_SHARE_READ, FILE_OPEN); + } + } + + if (hFile != INVALID_HANDLE_VALUE) { + IO_STATUS_BLOCK ios; + //char *buffer,*version;//,*ptr; + enum { BufferSize = 0x40 }; + char buffer[BufferSize]; + NtReadFile(hFile, 0, 0, 0, &ios, buffer, BufferSize, 0, 0); + NtClose(hFile); + if (buffer[0] == '[') { + buffer[0x3f] = 0; // jichi 8/24/2013: prevent strstr from overflow + if (char *version = ::strstr(buffer, "v2.")) + ::sscanf(version + 3, "%d", &ret); // +3 to skip "v2." + } + } + return ret; +} + +} // unnamed namespace + +// jichi 8/24/2013: Rewrite ShinaRio logic. +// Test games: ���×S�� (PK), version ShinaRio 2.47 +bool InsertShinaHook() +{ + int ver = GetShinaRioVersion(); + if (ver >= 48) { // v2.48, v2.49 + HookParam hp = {}; + hp.address = (DWORD)::GetTextExtentPoint32A; + hp.text_fun = SpecialHookShina2; + hp.type = USING_STRING; + ConsoleOutput("vnreng: INSERT ShinaRio > 2.47"); + NewHook(hp, "ShinaRio"); + //RegisterEngineType(ENGINE_SHINA); + return true; + + } else if (ver > 40) { // <= v2.47. Older games like あやかしびと does not require hcode + // jichi 3/13/2015: GetGlyphOutlineA is not hooked, which might produce correct text + // BOOL GetTextExtentPoint32(HDC hdc, LPCTSTR lpString, int c, LPSIZE lpSize); + enum stack { // current stack + arg0_retaddr = 0 // pseudo arg + , arg1_hdc = 4 * 1 + , arg2_lpString = 4 * 2 + , arg3_c = 4 * 3 + , arg4_lpSize = 4 * 4 + }; + + HookParam hp = {}; + hp.address = (DWORD)::GetTextExtentPoint32A; + hp.offset = arg2_lpString; // 0x8 + hp.length_offset = 1; + hp.type = DATA_INDIRECT|USING_SPLIT; + + enum { sub_esp = 0xec81 }; // jichi: caller pattern: sub esp = 0x81,0xec + if (DWORD s = Util::FindCallAndEntryBoth((DWORD)GetTextExtentPoint32A, module_limit_ - module_base_, module_base_, sub_esp)) { + ConsoleOutput("vnreng: INSERT ShinaRio <= 2.47 dynamic split"); + hp.split = *(DWORD *)(s + 2) + 4; + //RegisterEngineType(ENGINE_SHINA); + NewHook(hp, "ShinaRio"); + + } else { + // jichi 3/13/2015: GetTextExtentPoint32A is not statically invoked in ���×S�� (PK) + // See: http://sakuradite.com/topic/671 + // See: http://www.hongfire.com/forum/showthread.php/36807-AGTH-text-extraction-tool-for-games-translation/page347 + // + // [Guilty+]Rin x Sen �Hakudaku Onna Kyoushi to Yaroudomo /HB8*0:44@0:GDI32.dll:GetTextExtentPoint32A /Ftext@4339A2:0;choices@4339A2:ffffff + // + // addr: 0 , text_fun: 0x0 , function: 135408591 , hook_len: 0 , ind: 0 , length_of + // fset: 1 , module: 1409538707 , off: 8 , recover_len: 0 , split: 68 , split_ind: + // 0 , type: 216 + // + // Message speed needs to be set to something slower then fastest(instant) or text wont show up in agth. + // Last edited by Freaka; 09-29-2009 at 11:48 AM. + + // Issues: + // 1. The text speed must NOT to be set to the fastest. + // 2. There might be a wrong text thread that is almost correct, except that its first character is chopped. + // Otherwise, the first character will be split in another thread + ConsoleOutput("vnreng: INSERT ShinaRio <= 2.47 static split"); + hp.split = 0x44; + //hp.type |= FIXING_SPLIT|NO_CONTEXT; // merge all threads + //hp.text_fun = SpecialHookShina1; + NewHook(hp, "ShinaRio2"); // jichi: mark as ShinaRio2 so that VNR is able to warn user about the text speed issue + } + return true; + } + ConsoleOutput("vnreng:ShinaRio: unknown version"); + return false; +} + +bool InsertWaffleDynamicHook(LPVOID addr, DWORD frame, DWORD stack) +{ + if (addr != ::GetTextExtentPoint32A) + return false; + + DWORD handler; + __asm + { + mov eax,fs:[0] + mov eax,[eax] + mov eax,[eax] + mov eax,[eax] + mov eax,[eax] + mov eax,[eax] + mov ecx, [eax + 4] + mov handler, ecx + } + + union { + DWORD i; + BYTE *ib; + DWORD *id; + }; + // jichi 9/30/2013: Fix the bug in ITH logic where j is uninitialized + for (i = module_base_ + 0x1000; i < module_limit_ - 4; i++) + if (*id == handler && *(ib - 1) == 0x68) + if (DWORD t = SafeFindEntryAligned(i, 0x40)) { + HookParam hp = {}; + hp.address = t; + hp.offset = 8; + hp.index = 4; + hp.length_offset = 1; + hp.type = DATA_INDIRECT; + ConsoleOutput("vnreng: INSERT Dynamic Waffle"); + NewHook(hp, "Waffle"); + return true; + } + ConsoleOutput("vnreng:DynamicWaffle: failed"); + //ConsoleOutput("Unknown waffle engine."); + return true; // jichi 12/25/2013: return true +} +// DWORD retn,limit,str; +// WORD ch; +// NTSTATUS status; +// MEMORY_BASIC_INFORMATION info; +// str = *(DWORD*)(stack+0xC); +// ch = *(WORD*)str; +// if (ch<0x100) return false; +// limit = (stack | 0xFFFF) + 1; +// __asm int 3 +// for (stack += 0x10; stack < limit; stack += 4) +// { +// str = *(DWORD*)stack; +// if ((str >> 16) != (stack >> 16)) +// { +// status = NtQueryVirtualMemory(NtCurrentProcess(),(PVOID)str,MemoryBasicInformation,&info,sizeof(info),0); +// if (!NT_SUCCESS(status) || info.Protect & PAGE_NOACCESS) continue; //Accessible +// } +// if (*(WORD*)(str + 4) == ch) break; +// } +// if (stack < limit) +// { +// for (limit = stack + 0x100; stack < limit ; stack += 4) +// if (*(DWORD*)stack == -1) +// { +// retn = *(DWORD*)(stack + 4); +// if (retn > module_base_ && retn < module_limit_) +// { +// HookParam hp = {}; +// hp.address = retn + *(DWORD*)(retn - 4); +// hp.length_offset = 1; +// hp.offset = -0x20; +// hp.index = 4; +// //hp.split = 0x1E8; +// hp.type = DATA_INDIRECT; +// NewHook(hp, "WAFFLE"); +// //RegisterEngineType(ENGINE_WAFFLE); +// return true; +// } +// +// } +// +// } + +/** jichi 8/18/2015 + * Sample game: 完全時間停止 体験版 + * GDI text: TextOutA and GetTextExtentPoint32A + */ +void InsertWaffleHook() +{ + for (DWORD i = module_base_ + 0x1000; i < module_limit_ - 4; i++) + if (*(DWORD *)i == 0xac68) { + HookParam hp = {}; + hp.address = i; + hp.length_offset = 1; + hp.offset = -0x20; + hp.index = 4; + hp.split = 0x1e8; + hp.type = DATA_INDIRECT|USING_SPLIT; + ConsoleOutput("vnreng: INSERT WAFFLE"); + NewHook(hp, "WAFFLE"); + return; + } + //ConsoleOutput("Probably Waffle. Wait for text."); + trigger_fun_ = InsertWaffleDynamicHook; + SwitchTrigger(true); + //ConsoleOutput("vnreng:WAFFLE: failed"); +} + +void InsertTinkerBellHook() +{ + //DWORD s1,s2,i; + //DWORD ch=0x8141; + DWORD i; + WORD count; + count = 0; + HookParam hp = {}; + hp.length_offset = 1; + hp.type = BIG_ENDIAN|NO_CONTEXT; + for (i = module_base_; i< module_limit_ - 4; i++) { + if (*(DWORD*)i == 0x8141) { + BYTE t = *(BYTE*)(i - 1); + if (t == 0x3d || t == 0x2d) { + hp.offset = -0x8; + hp.address = i - 1; + } else if (*(BYTE*)(i-2) == 0x81) { + t &= 0xf8; + if (t == 0xf8 || t == 0xe8) { + hp.offset = -8 - ((*(BYTE*)(i-1) & 7) << 2); + hp.address = i - 2; + } + } + if (hp.address) { + char hook_name[0x20]; + ::strcpy(hook_name, "TinkerBell"); // size = 0xa + hook_name[0xa] = '0' + count; + hook_name[0xb] = 0; + ConsoleOutput("vnreng:INSERT TinkerBell"); + NewHook(hp, hook_name); + count++; + hp.address = 0; + } + } + } + ConsoleOutput("vnreng:TinkerBell: failed"); +} + +// s1=SearchPattern(module_base_,module_limit_-module_base_-4,&ch,4); +// if (s1) +// { +// for (i=s1;i>s1-0x400;i--) +// { +// if (*(WORD*)(module_base_+i)==0xec83) +// { +// hp.address=module_base_+i; +// NewHook(hp, "C.System"); +// break; +// } +// } +// } +// s2=s1+SearchPattern(module_base_+s1+4,module_limit_-s1-8,&ch,4); +// if (s2) +// { +// for (i=s2;i>s2-0x400;i--) +// { +// if (*(WORD*)(module_base_+i)==0xec83) +// { +// hp.address=module_base_+i; +// NewHook(hp, "TinkerBell"); +// break; +// } +// } +// } +// //if (count) + //RegisterEngineType(ENGINE_TINKER); + +// jichi 3/19/2014: Insert both hooks +//void InsertLuneHook() +bool InsertMBLHook() +{ + enum : DWORD { fun = 0xec8b55 }; // jichi 10/20/2014: mov ebp,esp, sub esp,* + bool ret = false; + if (DWORD c = Util::FindCallOrJmpAbs((DWORD)::ExtTextOutA, module_limit_ - module_base_, module_base_, true)) + if (DWORD addr = Util::FindCallAndEntryRel(c, module_limit_ - module_base_, module_base_, fun)) { + HookParam hp = {}; + hp.address = addr; + hp.offset = 4; + hp.type = USING_STRING; + ConsoleOutput("vnreng:INSERT MBL-Furigana"); + NewHook(hp, "MBL-Furigana"); + ret = true; + } + if (DWORD c = Util::FindCallOrJmpAbs((DWORD)::GetGlyphOutlineA, module_limit_ - module_base_, module_base_, true)) + if (DWORD addr = Util::FindCallAndEntryRel(c, module_limit_ - module_base_, module_base_, fun)) { + HookParam hp = {}; + hp.address = addr; + hp.offset = 4; + hp.split = -0x18; + hp.length_offset = 1; + hp.type = BIG_ENDIAN|USING_SPLIT; + ConsoleOutput("vnreng:INSERT MBL"); + NewHook(hp, "MBL"); + ret = true; + } + if (!ret) + ConsoleOutput("vnreng:MBL: failed"); + return ret; +} + +/** jichi 7/26/2014: E.A.G.L.S engine for TechArts games (SQUEEZ, May-Be Soft) + * Sample games: [May-Be Soft] ちぽ�んじ� * Should also work for SQUEEZ's 孕ませシリーズ + * + * Two functions calls to GetGlyphOutlineA are responsible for painting. + * - 0x4094ef + * - 0x409e35 + * However, by default, one of the thread is like: scenario namename scenario + * The other thread have infinite loop. + */ +bool InsertEaglsHook() +{ + // DWORD GetGlyphOutline(HDC hdc, UINT uChar, UINT uFormat, LPGLYPHMETRICS lpgm, DWORD cbBuffer, LPVOID lpvBuffer, const MAT2 *lpmat2); + enum stack { // current stack + arg0_retaddr = 0 // pseudo arg + , arg1_hdc = 4 * 1 + , arg2_uChar = 4 * 2 + , arg3_uFormat = 4 * 3 + , arg4_lpgm = 4 * 4 + , arg5_cbBuffer = 4 * 5 + , arg6_lpvBuffer = 4 * 6 + , arg7_lpmat2 = 4 * 7 + }; + + // Modify the split for GetGlyphOutlineA + HookParam hp = {}; + hp.address = (DWORD)::GetGlyphOutlineA; + hp.type = BIG_ENDIAN|USING_SPLIT; // the only difference is the split value + hp.offset = arg2_uChar; + hp.split = arg4_lpgm; + //hp.split = arg7_lpmat2; + hp.length_offset = 1; + ConsoleOutput("vnreng:INSERT EAGLS"); + NewHook(hp, "EAGLS"); + return true; +} + +/******************************************************************************************** +YU-RIS hook: + Becomes common recently. I first encounter this game in Whirlpool games. + Problem is name is repeated multiple times. + Step out of function call to TextOuA, just before call to this function, + there should be a piece of code to calculate the length of the name. + This length is 2 for single character name and text, + For a usual name this value is greater than 2. +********************************************************************************************/ + +//bool InsertWhirlpoolHook() // jichi: 12/27/2014: Renamed to YU-RIS +static bool InsertYuris1Hook() +{ + //IthBreak(); + DWORD entry = Util::FindCallAndEntryBoth((DWORD)TextOutA, module_limit_ - module_base_, module_base_, 0xec83); + //GROWL_DWORD(entry); + if (!entry) { + ConsoleOutput("vnreng:YU-RIS: function entry does not exist"); + return false; + } + entry = Util::FindCallAndEntryRel(entry - 4, module_limit_ - module_base_, module_base_, 0xec83); + //GROWL_DWORD(entry); + if (!entry) { + ConsoleOutput("vnreng:YU-RIS: function entry does not exist"); + return false; + } + entry = Util::FindCallOrJmpRel(entry - 4,module_limit_ - module_base_ - 0x10000, module_base_ + 0x10000, false); + DWORD i, + t = 0; + //GROWL_DWORD(entry); + ITH_TRY { // jichi 12/27/2014 + for (i = entry - 4; i > entry - 0x100; i--) + if (::IsBadReadPtr((LPCVOID)i, 4)) { // jichi 12/27/2014: might raise in new YU-RIS, 4 = sizeof(DWORD) + ConsoleOutput("vnreng:YU-RIS: do not have read permission"); + return false; + } else if (*(WORD *)i == 0xc085) { + t = *(WORD *)(i + 2); + if ((t & 0xff) == 0x76) { + t = 4; + break; + } else if ((t & 0xffff) == 0x860f) { + t = 8; + break; + } + } + + } ITH_EXCEPT { + ConsoleOutput("vnreng:YU-RIS: illegal access exception"); + return false; + } + if (i == entry - 0x100) { + ConsoleOutput("vnreng:YU-RIS: pattern not exist"); + return false; + } + //GROWL_DWORD2(i,t); + HookParam hp = {}; + hp.address = i + t; + hp.offset = -0x24; + hp.split = -0x8; + hp.type = USING_STRING|USING_SPLIT; + ConsoleOutput("vnreng: INSERT YU-RIS"); + //GROWL_DWORD(hp.address); + NewHook(hp, "YU-RIS"); + //RegisterEngineType(ENGINE_WHIRLPOOL); + return true; +} + +/** jichi 12/27/2014 + * + * Sample game: [Whirlpool] [150217] 鯨神�ヂ�アスヂ�ラ + * Call site of TextOutA. + * 00441811 90 nop + * 00441812 90 nop + * 00441813 90 nop + * 00441814 8b4424 04 mov eax,dword ptr ss:[esp+0x4] + * 00441818 8b5424 08 mov edx,dword ptr ss:[esp+0x8] + * 0044181c 8b4c24 0c mov ecx,dword ptr ss:[esp+0xc] + * 00441820 57 push edi + * 00441821 56 push esi + * 00441822 55 push ebp + * 00441823 53 push ebx + * 00441824 83ec 50 sub esp,0x50 + * 00441827 8bf9 mov edi,ecx + * 00441829 897c24 1c mov dword ptr ss:[esp+0x1c],edi + * 0044182d 8bda mov ebx,edx + * 0044182f 8be8 mov ebp,eax + * 00441831 8b349d 603f7b00 mov esi,dword ptr ds:[ebx*4+0x7b3f60] + * 00441838 807c24 74 01 cmp byte ptr ss:[esp+0x74],0x1 + * 0044183d b9 00000000 mov ecx,0x0 + * 00441842 0f94c1 sete cl + * 00441845 8d041b lea eax,dword ptr ds:[ebx+ebx] + * 00441848 03c3 add eax,ebx + * 0044184a 0fafc1 imul eax,ecx + * 0044184d 03c3 add eax,ebx + * 0044184f 894424 0c mov dword ptr ss:[esp+0xc],eax + * 00441853 897424 10 mov dword ptr ss:[esp+0x10],esi + * 00441857 8bc3 mov eax,ebx + * 00441859 8bd7 mov edx,edi + * 0044185b 0fbe4c24 70 movsx ecx,byte ptr ss:[esp+0x70] + * 00441860 e8 0c030000 call .00441b71 + * 00441865 0fbec8 movsx ecx,al + * 00441868 83f9 ff cmp ecx,-0x1 + * 0044186b 0f84 db020000 je .00441b4c + * 00441871 8bce mov ecx,esi + * 00441873 0fafc9 imul ecx,ecx + * 00441876 a1 64365d00 mov eax,dword ptr ds:[0x5d3664] + * 0044187b 8bf9 mov edi,ecx + * 0044187d c1ff 02 sar edi,0x2 + * 00441880 c1ef 1d shr edi,0x1d + * 00441883 03f9 add edi,ecx + * 00441885 c1ff 03 sar edi,0x3 + * 00441888 68 ff000000 push 0xff + * 0044188d 57 push edi + * 0044188e ff3485 70b48300 push dword ptr ds:[eax*4+0x83b470] + * 00441895 ff15 a4355d00 call dword ptr ds:[0x5d35a4] ; .00401c88 + * 0044189b 83c4 0c add esp,0xc + * 0044189e 8b0d 64365d00 mov ecx,dword ptr ds:[0x5d3664] + * 004418a4 ff348d b4b48300 push dword ptr ds:[ecx*4+0x83b4b4] + * 004418ab ff348d d4b48300 push dword ptr ds:[ecx*4+0x83b4d4] + * 004418b2 ff15 54e05800 call dword ptr ds:[0x58e054] ; gdi32.selectobject + * 004418b8 a3 b0b48300 mov dword ptr ds:[0x83b4b0],eax + * 004418bd 8b0d 64365d00 mov ecx,dword ptr ds:[0x5d3664] + * 004418c3 ff348d 30b48300 push dword ptr ds:[ecx*4+0x83b430] + * 004418ca ff348d d4b48300 push dword ptr ds:[ecx*4+0x83b4d4] + * 004418d1 ff15 54e05800 call dword ptr ds:[0x58e054] ; gdi32.selectobject + * 004418d7 a3 2cb48300 mov dword ptr ds:[0x83b42c],eax + * 004418dc 8b3d 64365d00 mov edi,dword ptr ds:[0x5d3664] + * 004418e2 33c9 xor ecx,ecx + * 004418e4 880cbd f5b48300 mov byte ptr ds:[edi*4+0x83b4f5],cl + * 004418eb 880cbd f6b48300 mov byte ptr ds:[edi*4+0x83b4f6],cl + * 004418f2 0fb64d 00 movzx ecx,byte ptr ss:[ebp] + * 004418f6 0fb689 a0645b00 movzx ecx,byte ptr ds:[ecx+0x5b64a0] + * 004418fd 41 inc ecx + * 004418fe 0fbec9 movsx ecx,cl + * 00441901 51 push ecx + * 00441902 55 push ebp + * 00441903 33c9 xor ecx,ecx + * 00441905 51 push ecx + * 00441906 51 push ecx + * 00441907 ff34bd d4b48300 push dword ptr ds:[edi*4+0x83b4d4] + * 0044190e ff15 74e05800 call dword ptr ds:[0x58e074] ; gdi32.textouta, jichi: TextOutA here + * 00441914 0fb67d 00 movzx edi,byte ptr ss:[ebp] + * 00441918 0fb68f a0645b00 movzx ecx,byte ptr ds:[edi+0x5b64a0] + * 0044191f 41 inc ecx + * 00441920 0fbef9 movsx edi,cl + * 00441923 8b0d 64365d00 mov ecx,dword ptr ds:[0x5d3664] + * 00441929 03c9 add ecx,ecx + * 0044192b 8d8c09 f4b48300 lea ecx,dword ptr ds:[ecx+ecx+0x83b4f4] + * + * Runtime stack: The first dword after arguments on the stack seems to be good split value. + */ +static bool InsertYuris2Hook() +{ + ULONG addr = MemDbg::findCallAddress((ULONG)::TextOutA, module_base_, module_limit_); + if (!addr) { + ConsoleOutput("vnreng:YU-RIS2: failed"); + return false; + } + + // BOOL TextOut( + // _In_ HDC hdc, + // _In_ int nXStart, + // _In_ int nYStart, + // _In_ LPCTSTR lpString, + // _In_ int cchString + // ); + enum stack { // current stack + arg1_hdc = 4 * 0 // starting from 0 before function call + , arg2_nXStart = 4 * 1 + , arg3_nYStart = 4 * 2 + , arg4_lpString = 4 * 3 + , arg5_cchString = 4 * 4 + , arg6_split = 4 * 5 // dummy argument + }; + + HookParam hp = {}; + hp.address = addr; + hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; // disable context that will cause thread split + hp.offset = arg4_lpString; + hp.split = arg6_split; + + ConsoleOutput("vnreng: INSERT YU-RIS 2"); + NewHook(hp, "YU-RIS2"); + return true; +} + +bool InsertYurisHook() +{ return InsertYuris1Hook() || InsertYuris2Hook(); } + +bool InsertCotophaHook() +{ + // jichi 7/12/2014: Change to accurate memory ranges + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng:Cotopha: failed to get memory range"); + return false; + } + enum : DWORD { ins = 0xec8b55 }; // mov ebp,esp, sub esp,* ; jichi 7/12/2014 + ULONG addr = MemDbg::findCallerAddress((ULONG)::GetTextMetricsA, ins, startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:Cotopha: pattern not exist"); + return false; + } + HookParam hp = {}; + hp.address = addr; + hp.offset = 4; + hp.split = -0x1c; // jichi: esp? + hp.type = USING_UNICODE|USING_SPLIT|USING_STRING; + ConsoleOutput("vnreng: INSERT Cotopha"); + NewHook(hp, "Cotopha"); + //RegisterEngineType(ENGINE_COTOPHA); + return true; +} + +// jichi 5/10/2014 +// See also: http://bbs.sumisora.org/read.php?tid=11044704&fpage=2 +// +// Old engine: グリザイアの迷宮 +// 0053cc4e cc int3 +// 0053cc4f cc int3 +// 0053cc50 6a ff push -0x1 ; jichi: hook here +// 0053cc52 68 6b486000 push .0060486b +// 0053cc57 64:a1 00000000 mov eax,dword ptr fs:[0] +// 0053cc5d 50 push eax +// 0053cc5e 81ec 24020000 sub esp,0x224 +// 0053cc64 a1 f8647600 mov eax,dword ptr ds:[0x7664f8] +// 0053cc69 33c4 xor eax,esp +// 0053cc6b 898424 20020000 mov dword ptr ss:[esp+0x220],eax +// 0053cc72 53 push ebx +// 0053cc73 55 push ebp +// 0053cc74 56 push esi +// 0053cc75 57 push edi +// +// Stack: +// 0544e974 0053d593 return to .0053d593 from .0053cc50 +// 0544e978 045cc820 +// 0544e97c 00008dc5 : jichi: text +// 0544e980 00000016 +// 0544e984 0452f2e4 +// 0544e988 00000000 +// 0544e98c 00000001 +// 0544e990 0544ea94 +// 0544e994 04513840 +// 0544e998 0452f2b8 +// 0544e99c 04577638 +// 0544e9a0 04620450 +// 0544e9a4 00000080 +// 0544e9a8 00000080 +// 0544e9ac 004914f3 return to .004914f3 from .0055c692 +// +// Registers: +// edx 0 +// ebx 00000016 +// +// +// New engine: イノセントガール +// Stack: +// 051ae508 0054e9d1 return to .0054e9d1 from .0054e310 +// 051ae50c 04361650 +// 051ae510 00008ca9 ; jichi: text +// 051ae514 0000001a +// 051ae518 04343864 +// 051ae51c 00000000 +// 051ae520 00000001 +// 051ae524 051ae62c +// 051ae528 041edc20 +// 051ae52c 04343830 +// 051ae530 0434a8b0 +// 051ae534 0434a7f0 +// 051ae538 00000080 +// 051ae53c 00000080 +// 051ae540 3f560000 +// 051ae544 437f8000 +// 051ae548 4433e000 +// 051ae54c 16f60c00 +// 051ae550 051ae650 +// 051ae554 042c4c20 +// 051ae558 0000002c +// 051ae55c 00439bc5 return to .00439bc5 from .0043af60 +// +// Registers & stack: +// Scenario: +// eax 04361650 +// ecx 04357640 +// edx 04343864 +// ebx 0000001a +// esp 051ae508 +// ebp 00008169 +// esi 04357640 +// edi 051ae62c +// eip 0054e310 .0054e310 +// +// 051ae508 0054e9d1 return to .0054e9d1 from .0054e310 +// 051ae50c 04361650 +// 051ae510 00008169 +// 051ae514 0000001a +// 051ae518 04343864 +// 051ae51c 00000000 +// 051ae520 00000001 +// 051ae524 051ae62c +// 051ae528 041edc20 +// 051ae52c 04343830 +// 051ae530 0434a8b0 +// 051ae534 0434a7f0 +// 051ae538 00000080 +// 051ae53c 00000080 +// 051ae540 3f560000 +// 051ae544 437f8000 +// 051ae548 4433e000 +// 051ae54c 16f60c00 +// 051ae550 051ae650 +// 051ae554 042c4c20 +// 051ae558 0000002c +// +// Name: +// +// eax 04362430 +// ecx 17025230 +// edx 0430b6e4 +// ebx 0000001a +// esp 051ae508 +// ebp 00008179 +// esi 17025230 +// edi 051ae62c +// eip 0054e310 .0054e310 +// +// 051ae508 0054e9d1 return to .0054e9d1 from .0054e310 +// 051ae50c 04362430 +// 051ae510 00008179 +// 051ae514 0000001a +// 051ae518 0430b6e4 +// 051ae51c 00000000 +// 051ae520 00000001 +// 051ae524 051ae62c +// 051ae528 041edae0 +// 051ae52c 0430b6b0 +// 051ae530 0434a790 +// 051ae534 0434a910 +// 051ae538 00000080 +// 051ae53c 00000080 +// 051ae540 3efa0000 +// 051ae544 4483f000 +// 051ae548 44322000 +// 051ae54c 16f60aa0 +// 051ae550 051ae650 +// 051ae554 042c4c20 +// 051ae558 0000002c + +static void SpecialHookCatSystem3(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + //DWORD ch = *data = *(DWORD *)(esp_base + hp->offset); // arg2 + DWORD ch = *data = argof(2, esp_base); + *len = LeadByteTable[(ch >> 8) & 0xff]; // BIG_ENDIAN + *split = regof(edx, esp_base) >> 16; +} + +bool InsertCatSystemHook() +{ + //DWORD search=0x95EB60F; + //DWORD j,i=SearchPattern(module_base_,module_limit_-module_base_,&search,4); + //if (i==0) return; + //i+=module_base_; + //for (j=i-0x100;i>j;i--) + // if (*(DWORD*)i==0xcccccccc) break; + //if (i==j) return; + //hp.address=i+4; + //hp.offset=-0x8; + //hp.index=4; + //hp.split=4; + //hp.split_index=0x18; + //hp.type =BIG_ENDIAN|DATA_INDIRECT|USING_SPLIT|SPLIT_INDIRECT; + //hp.length_offset=1; + + // jichi 7/12/2014: Change to accurate memory ranges + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng:CatSystem2: failed to get memory range"); + return false; + } + enum { beg = 0xff6acccc }; // jichi 7/12/2014: beginning of the function + enum { addr_offset = 2 }; // skip two leading 0xcc + ULONG addr = MemDbg::findCallerAddress((ULONG)::GetTextMetricsA, beg, startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:CatSystem2: pattern not exist"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; // skip 1 push? + hp.offset = 4 * 2; // text character is in arg2 + hp.length_offset = 1; // only 1 character + + // jichi 12/23/2014: Modify split for new catsystem + bool newEngine = IthCheckFile(L"cs2conf.dll"); + if (newEngine) { + hp.text_fun = SpecialHookCatSystem3; // type not needed + NewHook(hp, "CatSystem3"); + ConsoleOutput("vnreng: INSERT CatSystem3"); + } else { + hp.type = BIG_ENDIAN|USING_SPLIT; + hp.split = pusha_edx_off - 4; // -0x10 + NewHook(hp, "CatSystem2"); + ConsoleOutput("vnreng: INSERT CatSystem2"); + } + //RegisterEngineType(ENGINE_CATSYSTEM); + return true; +} + +bool InsertNitroplusHook() +{ + const BYTE bytes[] = {0xb0, 0x74, 0x53}; + DWORD addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + if (!addr) { + ConsoleOutput("vnreng:Nitroplus: pattern not exist"); + return false; + } + enum : WORD { sub_esp = 0xec83 }; // caller pattern: sub esp = 0x83,0xec + BYTE b = *(BYTE *)(addr + 3) & 3; + while (*(WORD *)addr != sub_esp) + addr--; + HookParam hp = {}; + hp.address = addr; + hp.offset = -0x14+ (b << 2); + hp.length_offset = 1; + hp.type = BIG_ENDIAN; + ConsoleOutput("vnreng: INSERT Nitroplus"); + NewHook(hp, "Nitroplus"); + //RegisterEngineType(ENGINE_Nitroplus); + return true; +} + +// jichi 6/21/2015 +namespace { // unnamed + +void SpecialHookRetouch1(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD text = argof(1, esp_base); + *data = text; + *len = ::strlen((LPCSTR)text); + *split = + regof(eax,esp_base) == 0 ? FIXED_SPLIT_VALUE * 2 : // name + regof(ebx,esp_base) == 0 ? FIXED_SPLIT_VALUE * 1 : // scenario + FIXED_SPLIT_VALUE * 3 ; // other +} + +bool InsertRetouch1Hook() +{ + HMODULE hModule = ::GetModuleHandleA("resident.dll"); + if (!hModule) { + ConsoleOutput("vnreng:Retouch: failed, dll handle not loaded"); + return false; + } + // private: bool __thiscall RetouchPrintManager::printSub(char const *,class UxPrintData &,unsigned long) 0x10050650 0x00050650 2904 (0xb58) resident.dll C:\Local\箱庭ロジヂ�\resident.dll Exported Function + const char *sig = "?printSub@RetouchPrintManager@@AAE_NPBDAAVUxPrintData@@K@Z"; + DWORD addr = (DWORD)::GetProcAddress(hModule, sig); + if (!addr) { + ConsoleOutput("vnreng:Retouch: failed, procedure not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.offset = 4; + hp.type = USING_STRING|NO_CONTEXT; + hp.text_fun = SpecialHookRetouch1; + ConsoleOutput("vnreng: INSERT Retouch"); + NewHook(hp, "Retouch"); + return true; +} + +bool InsertRetouch2Hook() +{ + HMODULE hModule = ::GetModuleHandleA("resident.dll"); + if (!hModule) { + ConsoleOutput("vnreng:Retouch2: failed, dll handle not loaded"); + return false; + } + // private: void __thiscall RetouchPrintManager::printSub(char const *,unsigned long,int &,int &) 0x10046560 0x00046560 2902 (0xb56) resident.dll C:\Local\箱庭ロジヂ�\resident.dll Exported Function + const char *sig = "?printSub@RetouchPrintManager@@AAEXPBDKAAH1@Z"; + DWORD addr = (DWORD)::GetProcAddress(hModule, sig); + if (!addr) { + ConsoleOutput("vnreng:Retouch2: failed, procedure not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.offset = 4; + hp.type = USING_STRING; + ConsoleOutput("vnreng: INSERT Retouch"); + NewHook(hp, "Retouch"); + return true; +} + +} // unnamed namespace +bool InsertRetouchHook() +{ + bool ok = InsertRetouch1Hook(); + ok = InsertRetouch2Hook() || ok; + return ok; +} + +namespace { // unnamed Malie +/******************************************************************************************** +Malie hook: + Process name is malie.exe. + This is the most complicate code I have made. Malie engine store text string in + linked list. We need to insert a hook to where it travels the list. At that point + EBX should point to a structure. We can find character at -8 and font size at +10. + Also need to enable ITH suppress function. +********************************************************************************************/ +bool InsertMalieHook1() +{ + const DWORD sig1 = 0x05e3c1; + enum { sig1_size = 3 }; + DWORD i = SearchPattern(module_base_, module_limit_ - module_base_, &sig1, sig1_size); + if (!i) { + ConsoleOutput("vnreng:MalieHook1: pattern i not exist"); + return false; + } + + const WORD sig2 = 0xc383; + enum { sig2_size = 2 }; + DWORD j = i + module_base_ + sig1_size; + i = SearchPattern(j, module_limit_ - j, &sig2, sig2_size); + //if (!j) + if (!i) { // jichi 8/19/2013: Change the condition fro J to I + ConsoleOutput("vnreng:MalieHook1: pattern j not exist"); + return false; + } + HookParam hp = {}; + hp.address = j + i; + hp.offset = -0x14; + hp.index = -0x8; + hp.split = -0x14; + hp.split_index = 0x10; + hp.length_offset = 1; + hp.type = USING_UNICODE|USING_SPLIT|DATA_INDIRECT|SPLIT_INDIRECT; + ConsoleOutput("vnreng: INSERT MalieHook1"); + NewHook(hp, "Malie"); + //RegisterEngineType(ENGINE_MALIE); + return true; +} + +DWORD malie_furi_flag_; // jichi 8/20/2013: Make it global so that it can be reset +void SpecialHookMalie(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD ch = *(DWORD *)(esp_base - 0x8) & 0xffff, + ptr = *(DWORD *)(esp_base - 0x24); + *data = ch; + *len = 2; + if (malie_furi_flag_) { + DWORD index = *(DWORD *)(esp_base - 0x10); + if (*(WORD *)(ptr + index * 2 - 2) < 0xa) + malie_furi_flag_ = 0; + } + else if (ch == 0xa) { + malie_furi_flag_ = 1; + len = 0; + } + *split = malie_furi_flag_; +} + +bool InsertMalieHook2() // jichi 8/20/2013: Change return type to boolean +{ + const BYTE bytes[] = {0x66,0x3d,0x1,0x0}; + DWORD start = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + if (!start) { + ConsoleOutput("vnreng:MalieHook2: pattern not exist"); + return false; + } + BYTE *ptr = (BYTE *)start; + while (true) { + if (*(WORD *)ptr == 0x3d66) { + ptr += 4; + if (ptr[0] == 0x75) { + ptr += ptr[1]+2; + continue; + } + if (*(WORD *)ptr == 0x850f) { + ptr += *(DWORD *)(ptr + 2) + 6; + continue; + } + } + break; + } + malie_furi_flag_ = 0; // reset old malie flag + HookParam hp = {}; + hp.address = (DWORD)ptr + 4; + hp.offset = -8; + hp.length_offset = 1; + hp.text_fun = SpecialHookMalie; + hp.type = USING_SPLIT|USING_UNICODE|NO_CONTEXT; + hp.type = NO_CONTEXT; + ConsoleOutput("vnreng: INSERT MalieHook2"); + NewHook(hp, "Malie"); + //RegisterEngineType(ENGINE_MALIE); + ConsoleOutput("vnreng:Malie2: disable GDI hooks"); + DisableGDIHooks(); + return true; +} + +/** + * jichi 12/17/2013: Added for Electro Arms + * Observations from Electro Arms: + * 1. split = 0xC can handle most texts and its dwRetn is always zero + * 2. The text containing furigana needed to split has non-zero dwRetn when split = 0 + * + * 3/15/2015: logic modified as the plus operation would create so many threads + */ +void SpecialHookMalie2(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + CC_UNUSED(data); + //*len = GetHookDataLength(*hp, esp_base, (DWORD)data); + *len = 2; + + DWORD s1 = *(DWORD *)(esp_base + 0xc), // base split, which is stable + s2 = (*(DWORD *)esp_base); // used to split out furigana, but un stable + // http://www.binaryhexconverter.com/decimal-to-binary-converter + //enum : DWORD { mask = 0x14 }; + *split = s1 + (s2 ? 1 : 0); +} + +// static DWORD last_split; // FIXME: This makes the special function stateful +// DWORD s1 = *(DWORD *)esp_base; // current split at 0x0 +// if (!s1) +// *split = last_split; +// else { +// DWORD s2 = *(DWORD *)(esp_base + 0xc); // second split +// *split = last_split = s1 + s2; // not sure if plus is a good way +// } + +/** + * jichi 8/20/2013: Add hook for sweet light BRAVA!! + * See: http://www.hongfire.com/forum/printthread.php?t=36807&pp=10&page=680 + * + * BRAVA!! /H code: "/HWN-4:C@1A3DF4:malie.exe" + * - addr: 1719796 = 0x1a3df4 + * - text_fun: 0x0 + * - function: 0 + * - hook_len: 0 + * - ind: 0 + * - length_offset: 1 + * - module: 751199171 = 0x2cc663c3 + * - off: 4294967288 = 0xfffffff8L = -0x8 + * - recover_len: 0 + * - split: 12 = 0xc + * - split_ind: 0 + * - type: 1106 = 0x452 + */ +bool InsertMalie2Hook() +{ + // 001a3dee 6900 70000000 imul eax,dword ptr ds:[eax],70 + // 001a3df4 0200 add al,byte ptr ds:[eax] ; this is the place to hook + // 001a3df6 50 push eax + // 001a3df7 0069 00 add byte ptr ds:[ecx],ch + // 001a3dfa 0000 add byte ptr ds:[eax],al + const BYTE bytes1[] = { + 0x40, // inc eax + 0x89,0x56, 0x08, // mov dword ptr ds:[esi+0x8],edx + 0x33,0xd2, // xor edx,edx + 0x89,0x46, 0x04 // mov dword ptr ds:[esi+0x4],eax + }; + ULONG range1 = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes1, sizeof(bytes1), module_base_, module_base_ + range1); + //reladdr = 0x1a3df4; + if (!addr) { + //ITH_MSG(0, "Wrong1", "t", 0); + //ConsoleOutput("Not malie2 engine"); + ConsoleOutput("vnreng:Malie2Hook: pattern p not exist"); + return false; + } + + addr += sizeof(bytes1); // skip bytes1 + //const BYTE bytes2[] = { 0x85, 0xc0 }; // test eax,eax + const WORD bytes2 = 0xc085; // test eax,eax + enum { range2 = 0x200 }; + addr = MemDbg::findBytes(&bytes2, sizeof(bytes2), addr, addr + range2); + if (!addr) { + //ConsoleOutput("Not malie2 engine"); + ConsoleOutput("vnreng:Malie2Hook: pattern q not exist"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.offset = -8; // pusha_eax_off - 4 + hp.length_offset = 1; + //hp.split = 0xc; // jichi 12/17/2013: Subcontext removed + //hp.split = -0xc; // jichi 12/17/2013: This could split the furigana, but will mess up the text + //hp.type = USING_SPLIT|USING_UNICODE|NO_CONTEXT; + // jichi 12/17/2013: Need extern func for Electro Arms + // Though the hook parameter is quit similar to Malie, the original extern function does not work + hp.type = USING_SPLIT|NO_CONTEXT|USING_UNICODE; + hp.text_fun = SpecialHookMalie2; + ConsoleOutput("vnreng: INSERT Malie2"); + NewHook(hp, "Malie2"); + + //GROWL_DWORD2(hp.address, reladdr); + //RegisterEngineType(ENGINE_MALIE); + return true; +} + +// jichi 2/8/3014: Return the beginning and the end of the text +// Remove the leading illegal characters +enum { _MALIE3_MAX_LENGTH = VNR_TEXT_CAPACITY }; +LPCWSTR _Malie3LTrim(LPCWSTR p) +{ + if (p) + for (int count = 0; count < _MALIE3_MAX_LENGTH; count++, + p++) + if (p[0] == L'v' && p[1] == L'_') { // ex. v_akr0001, v_mzk0001 + p += 9; + return p; // must return otherwise trimming more will break the ITH repetition elimination + } else if (p[0] >= 0xa) // ltrim illegal characters less than 0xa + return p; + return nullptr; +} +// Remove the trailing illegal characters +LPCWSTR _Malie3RTrim(LPCWSTR p) +{ + if (p) + for (int count = 0; count < _MALIE3_MAX_LENGTH; count++, + p--) + if (p[-1] >= 0xa) { // trim illegal characters less than 0xa + if (p[-1] >= L'0' && p[-1] <= L'9'&& p[-1-7] == L'_') + p -= 9; + else + return p; + } + return nullptr; +} + +// Example section in memory: +// 0D7D7E00 07 00 08 00 76 00 5F 00 7A 00 65 00 70 00 30 00 v_zep0 +// 0D7D7E10 30 00 37 00 35 00 00 00 0C 30 42 30 41 30 01 30 075.「あぁ�// 0D7D7E20 41 30 26 20 26 20 07 00 09 00 07 00 06 00 07 00 ぁ……. +// 0D7D7E30 08 00 76 00 5F 00 7A 00 65 00 70 00 30 00 30 00 v_zep00 +// 0D7D7E40 37 00 36 00 00 00 46 30 01 30 42 30 01 30 41 30 76.぀�あ、ぁ +// 0D7D7E50 41 30 41 30 26 20 26 20 26 20 26 20 01 30 63 30 ぁぁ…………、っ +// 0D7D7E60 07 00 09 00 0D 30 07 00 06 00 0A 00 0A 00 00 30 .�.. +// 0D7D7E70 16 60 44 30 01 30 16 60 44 30 01 30 4A 30 5E 30 怖い、怖い、お�// 0D7D7E80 7E 30 57 30 44 30 02 30 55 4F 4C 30 16 60 44 30 ましい。何が怖い +// 0D7D7E90 6E 30 4B 30 55 30 48 30 01 30 06 52 4B 30 89 30 のかさえ、�から +// 0D7D7EA0 6A 30 44 30 02 30 07 00 06 00 0A 00 00 30 8B 89 な぀. �// 0D7D7EB0 8B 30 6A 30 88 30 02 30 8B 89 8B 30 6A 30 02 30 るなよ。見るな�// 0D7D7EC0 07 00 06 00 8B 89 8B 30 6A 30 01 30 8B 89 8B 30 見るな、見る +// 0D7D7ED0 6A 30 8B 89 8B 30 6A 30 8B 89 8B 30 6A 30 01 30 な見るな見るな�// 0D7D7EE0 1F 75 4D 30 66 30 66 30 AA 60 44 30 4B 30 88 30 生きてて悪ぁ��// 0D7D7EF0 02 30 C5 60 51 30 6A 30 44 30 63 30 66 30 07 00 。情けなぁ�て +// 0D7D7F00 01 00 E4 55 0A 00 8F 30 89 30 00 00 46 30 6A 30 嗤.わら.ぁ� +// 0D7D7F10 88 30 02 30 07 00 06 00 BE 7C 00 4E 6F 67 6A 30 よ�精一杯な +// 0D7D7F20 93 30 60 30 8B 89 03 90 57 30 66 30 4F 30 8C 30 んだ見送�てくれ +// 0D7D7F30 02 30 4A 30 58 98 44 30 57 30 7E 30 59 30 01 30 。お願いします�// 0D7D7F40 60 30 4B 30 89 30 69 30 46 30 4B 30 5D 30 6E 30 �からどぁ�そ� +// 0D7D7F50 EE 76 92 30 84 30 81 30 66 30 01 30 4F 30 60 30 目をやめて、く� +// 0D7D7F60 55 30 44 30 01 30 5D 30 93 30 6A 30 02 30 07 00 さい、そんな� +// 0D7D7F70 06 00 0A 00 00 30 07 00 01 00 BA 87 50 5B 0A 00 . 螺� +// 0D7D7F80 59 30 4C 30 00 00 8B 30 88 30 46 30 6A 30 EE 76 すが.るよぁ�目 +// 0D7D7F90 67 30 00 25 00 25 07 00 06 00 BF 30 01 30 B9 30 で──タ、ス +// 0D7D7FA0 01 30 B1 30 01 30 C6 30 01 30 6A 30 93 30 66 30 、ケ、テ、なんて +// 0D7D7FB0 02 30 07 00 06 00 00 00 00 00 00 00 00 00 00 00 �..... +// 0D7D7FC0 FC D8 C0 22 00 00 00 80 74 00 00 00 00 00 00 00 .耀t... +// +// Return the end of the line +LPCWSTR _Malie3GetEOL(LPCWSTR p) +{ + if (p) + for (int count = 0; count < _MALIE3_MAX_LENGTH; count++, + p++) + switch (*p) { + case 0: + case 0xa: // stop at \0, or \n where the text after 0xa is furigana + return p; + case 0x7: + // \x07\x00\x01\x00 is used to split furigana, which we want to keep + // \x07\x00\x04\x00 is used to split sentences, observed in シルヴァリオ ヴェンヂ�ヂ� + // \x07\x00\x06\x00 is used to split paragraph, observed in シルヴァリオ ヴェンヂ�ヂ� + if (p[1] < 0xa && p[1] != 0x1) + return p; + } + return nullptr; +} + +/** + * jichi 3/8/2014: Add hook for 相州戦神館學�八命陣 + * See: http://sakuradite.com/topic/157 + * check 0x5b51ed for ecx+edx*2 + * Also need to skip furigana. + */ + +void SpecialHookMalie3(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + CC_UNUSED(split); + DWORD ecx = regof(ecx, esp_base), // *(DWORD *)(esp_base + pusha_ecx_off - 4), + edx = regof(edx, esp_base); // *(DWORD *)(esp_base + pusha_edx_off - 4); + //*data = ecx + edx*2; // [ecx+edx*2]; + //*len = wcslen((LPCWSTR)data) << 2; + // There are garbage characters + LPCWSTR start = _Malie3LTrim((LPCWSTR)(ecx + edx*2)), + stop = _Malie3RTrim(_Malie3GetEOL(start)); + *data = (DWORD)start; + *len = max(0, stop - start) * 2; + *split = FIXED_SPLIT_VALUE; + //GROWL_DWORD5((DWORD)start, (DWORD)stop, *len, (DWORD)*start, (DWORD)_Malie3GetEOL(start)); +} + +/** + * jichi 8/20/2013: Add hook for 相州戦神館學�八命陣 + * See: http://sakuradite.com/topic/157 + * Credits: @ok123 + * + * Debugging method: insert hardware breakpoint into text + * There are four matches of text in the memory + * + * Sample game: シルヴァリオ ヴェンヂ�ヂ� + * 0065478B 90 NOP + * 0065478C 90 NOP + * 0065478D 90 NOP + * 0065478E 90 NOP + * 0065478F 90 NOP + * 00654790 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+0x4] + * 00654794 56 PUSH ESI + * 00654795 57 PUSH EDI + * 00654796 8B50 08 MOV EDX,DWORD PTR DS:[EAX+0x8] + * 00654799 8B08 MOV ECX,DWORD PTR DS:[EAX] + * 0065479B 33F6 XOR ESI,ESI + * 0065479D 66:8B3451 MOV SI,WORD PTR DS:[ECX+EDX*2] ; jichi: text accessed here + * 006547A1 42 INC EDX + * 006547A2 8970 04 MOV DWORD PTR DS:[EAX+0x4],ESI + * 006547A5 8950 08 MOV DWORD PTR DS:[EAX+0x8],EDX + * 006547A8 8B50 04 MOV EDX,DWORD PTR DS:[EAX+0x4] + * 006547AB 83FA 01 CMP EDX,0x1 + * 006547AE 75 2C JNZ SHORT malie.006547DC + * 006547B0 8B50 08 MOV EDX,DWORD PTR DS:[EAX+0x8] + * 006547B3 33F6 XOR ESI,ESI + * 006547B5 66:8B3451 MOV SI,WORD PTR DS:[ECX+EDX*2] + * 006547B9 42 INC EDX + * 006547BA 8970 04 MOV DWORD PTR DS:[EAX+0x4],ESI + * 006547BD 33F6 XOR ESI,ESI + * 006547BF 8950 08 MOV DWORD PTR DS:[EAX+0x8],EDX + * 006547C2 66:8B3451 MOV SI,WORD PTR DS:[ECX+EDX*2] + * 006547C6 8970 04 MOV DWORD PTR DS:[EAX+0x4],ESI + * 006547C9 42 INC EDX + * 006547CA 33F6 XOR ESI,ESI + * 006547CC 8950 08 MOV DWORD PTR DS:[EAX+0x8],EDX + * 006547CF 66:8B3451 MOV SI,WORD PTR DS:[ECX+EDX*2] + * 006547D3 42 INC EDX + * 006547D4 8970 04 MOV DWORD PTR DS:[EAX+0x4],ESI + * 006547D7 8950 08 MOV DWORD PTR DS:[EAX+0x8],EDX + * 006547DA ^EB BF JMP SHORT malie.0065479B + * 006547DC 83FA 02 CMP EDX,0x2 + * 006547DF 0F84 59010000 JE malie.0065493E + * 006547E5 83FA 03 CMP EDX,0x3 + * 006547E8 75 12 JNZ SHORT malie.006547FC + * 006547EA 8B50 08 MOV EDX,DWORD PTR DS:[EAX+0x8] + * 006547ED 33F6 XOR ESI,ESI + * 006547EF 66:8B3451 MOV SI,WORD PTR DS:[ECX+EDX*2] + * 006547F3 42 INC EDX + * 006547F4 8970 04 MOV DWORD PTR DS:[EAX+0x4],ESI + * 006547F7 8950 08 MOV DWORD PTR DS:[EAX+0x8],EDX + * 006547FA ^EB 9F JMP SHORT malie.0065479B + * 006547FC 83FA 04 CMP EDX,0x4 + * 006547FF 0F84 39010000 JE malie.0065493E + * 00654805 83FA 07 CMP EDX,0x7 + * 00654808 0F85 27010000 JNZ malie.00654935 + * 0065480E 8B50 08 MOV EDX,DWORD PTR DS:[EAX+0x8] + * 00654811 33F6 XOR ESI,ESI + * 00654813 66:8B3451 MOV SI,WORD PTR DS:[ECX+EDX*2] + * 00654817 8970 04 MOV DWORD PTR DS:[EAX+0x4],ESI + * 0065481A 8D72 01 LEA ESI,DWORD PTR DS:[EDX+0x1] + * 0065481D 8B50 04 MOV EDX,DWORD PTR DS:[EAX+0x4] + * 00654820 8970 08 MOV DWORD PTR DS:[EAX+0x8],ESI + * 00654823 8D7A FF LEA EDI,DWORD PTR DS:[EDX-0x1] + * 00654826 83FF 3B CMP EDI,0x3B + * 00654829 ^0F87 79FFFFFF JA malie.006547A8 + * 0065482F 33D2 XOR EDX,EDX + * 00654831 8A97 9C496500 MOV DL,BYTE PTR DS:[EDI+0x65499C] + * 00654837 FF2495 80496500 JMP DWORD PTR DS:[EDX*4+0x654980] + * 0065483E 8B50 0C MOV EDX,DWORD PTR DS:[EAX+0xC] + * 00654841 85D2 TEST EDX,EDX + * 00654843 0F8F 2B010000 JG malie.00654974 + * 00654849 33D2 XOR EDX,EDX + * 0065484B 66:8B1471 MOV DX,WORD PTR DS:[ECX+ESI*2] + * 0065484F 46 INC ESI + * 00654850 85D2 TEST EDX,EDX + * 00654852 8950 04 MOV DWORD PTR DS:[EAX+0x4],EDX + * 00654855 8970 08 MOV DWORD PTR DS:[EAX+0x8],ESI + * 00654858 0F84 E0000000 JE malie.0065493E + * 0065485E 8B50 08 MOV EDX,DWORD PTR DS:[EAX+0x8] + * 00654861 33F6 XOR ESI,ESI + * 00654863 66:8B3451 MOV SI,WORD PTR DS:[ECX+EDX*2] + * 00654867 42 INC EDX + * 00654868 8950 08 MOV DWORD PTR DS:[EAX+0x8],EDX + * 0065486B 8BD6 MOV EDX,ESI + * 0065486D 85D2 TEST EDX,EDX + * 0065486F 8970 04 MOV DWORD PTR DS:[EAX+0x4],ESI + * 00654872 ^75 EA JNZ SHORT malie.0065485E + * 00654874 8B50 08 MOV EDX,DWORD PTR DS:[EAX+0x8] + */ +bool InsertMalie3Hook() +{ + // i.e. 8b44240456578b50088b0833f6668b345142 + const BYTE bytes[] = { + // 0x90 nop + 0x8b,0x44,0x24, 0x04, // 5b51e0 mov eax,dword ptr ss:[esp+0x4] ; jichi: function starts + 0x56, // 5b51e4 push esi + 0x57, // 5b51e5 push edi + 0x8b,0x50, 0x08, // 5b51e6 mov edx,dword ptr ds:[eax+0x8] + 0x8b,0x08, // 5b51e9 mov ecx,dword ptr ds:[eax] + 0x33,0xf6, // 5b51eb xor esi,esi + 0x66,0x8b,0x34,0x51, // 5b51ed mov si,word ptr ds:[ecx+edx*2] // jichi: hook here + 0x42 // 5b51f1 inc edx + }; + enum {addr_offset = 0x5b51ed - 0x5b51e0}; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + if (!addr) { + ConsoleOutput("vnreng:Malie3: pattern not found"); + return false; + } + HookParam hp = {}; + hp.address = addr + addr_offset; + //GROWL(hp.address); + //hp.address = 0x5b51ed; + //hp.address = 0x5b51f1; + //hp.address = 0x5b51f2; + // jichi 3/15/2015: Remove 0704 in シルヴァリオ ヴェンッ�タ + hp.filter_fun = IllegalWideCharsFilter; // remove illegal control chars such as 0x07,0x01 + hp.text_fun = SpecialHookMalie3; + hp.type = NO_CONTEXT|USING_UNICODE; + //hp.filter_fun = Malie3Filter; + ConsoleOutput("vnreng: INSERT Malie3"); + NewHook(hp, "Malie3"); + ConsoleOutput("vnreng:Malie3: disable GDI hooks"); + DisableGDIHooks(); + return true; +} + +// jichi 3/12/2015: Return guessed Malie engine year +//int GetMalieYear() +//{ +// if (Util::SearchResourceString(L"2013 light")) +// return 2013; +// if (Util::SearchResourceString(L"2014 light")) +// return 2014; +// return 2015; +//} + +} // unnamed Malie + +bool InsertMalieHook() +{ + if (IthCheckFile(L"tools.dll")) + return InsertMalieHook1(); // jichi 3/5/2015: For old light games such as Dies irae. + + else { // For old Malie games before 2015 + // jichi 8/20/2013: Add hook for sweet light engine + // Insert both malie and malie2 hook. + bool ok = false; + + // jichi 3/12/2015: Disable MalieHook2 which will crash シルヴァリオ ヴェンッ�タ + //if (!IthCheckFile(L"gdiplus.dll")) + if (IthFindFile(L"System\\*")) { // Insert old Malie hook. There are usually System/cursor.cur + ok = InsertMalieHook2() || ok; + ok = InsertMalie2Hook() || ok; // jichi 8/20/2013 + } + + // The main disadvantage of Malie3 is that it cannot find character name + ok = InsertMalie3Hook() || ok; // jichi 3/7/2014 + + if (ok) { + ConsoleOutput("vnreng:Malie: disable GDI hooks"); + DisableGDIHooks(); + } + return ok; + } +} + +/******************************************************************************************** +EMEHook hook: (Contributed by Freaka) + EmonEngine is used by LoveJuice company and TakeOut. Earlier builds were apparently + called Runrun engine. String parsing varies a lot depending on the font settings and + speed setting. E.g. without antialiasing (which very early versions did not have) + uses TextOutA, fast speed triggers different functions then slow/normal. The user can + set his own name and some odd control characters are used (0x09 for line break, 0x0D + for paragraph end) which is parsed and put together on-the-fly while playing so script + can't be read directly. +********************************************************************************************/ +bool InsertEMEHook() +{ + ULONG addr = MemDbg::findCallAddress((ULONG)::IsDBCSLeadByte, module_base_, module_limit_); + // no needed as first call to IsDBCSLeadByte is correct, but sig could be used for further verification + //WORD sig = 0x51C3; + //while (c && (*(WORD*)(c-2)!=sig)) + //{ + // //-0x1000 as FindCallOrJmpAbs always uses an offset of 0x1000 + // c = Util::FindCallOrJmpAbs((DWORD)IsDBCSLeadByte,module_limit_-c-0x1000+4,c-0x1000+4,false); + //} + if (!addr) { + ConsoleOutput("vnreng:EME: pattern does not exist"); + return false; + } + HookParam hp = {}; + hp.address = addr; + hp.offset = -0x8; + hp.length_offset = 1; + hp.type = NO_CONTEXT|DATA_INDIRECT; + ConsoleOutput("vnreng: INSERT EmonEngine"); + NewHook(hp, "EmonEngine"); + //ConsoleOutput("EmonEngine, hook will only work with text speed set to slow or normal!"); + //else ConsoleOutput("Unknown EmonEngine engine"); + return true; +} +static void SpecialRunrunEngine(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + CC_UNUSED(split); + DWORD eax = regof(eax, esp_base), // *(DWORD *)(esp_base - 0x8), + edx = regof(edx, esp_base); // *(DWORD *)(esp_base - 0x10); + DWORD addr = eax + edx; // eax + edx + *data = *(WORD *)(addr); + *len = 2; +} +bool InsertRREHook() +{ + ULONG addr = MemDbg::findCallAddress((ULONG)::IsDBCSLeadByte, module_base_, module_limit_); + if (!addr) { + ConsoleOutput("vnreng:RRE: function call does not exist"); + return false; + } + WORD sig = 0x51c3; + HookParam hp = {}; + hp.address = addr; + hp.length_offset = 1; + hp.type = NO_CONTEXT|DATA_INDIRECT; + if ((*(WORD *)(addr-2) != sig)) { + hp.text_fun = SpecialRunrunEngine; + ConsoleOutput("vnreng: INSERT Runrun#1"); + NewHook(hp, "RunrunEngine Old"); + } else { + hp.offset = -0x8; + ConsoleOutput("vnreng: INSERT Runrun#2"); + NewHook(hp, "RunrunEngine"); + } + return true; + //ConsoleOutput("RunrunEngine, hook will only work with text speed set to slow or normal!"); + //else ConsoleOutput("Unknown RunrunEngine engine"); +} +bool InsertMEDHook() +{ + for (DWORD i = module_base_; i < module_limit_ - 4; i++) + if (*(DWORD *)i == 0x8175) //cmp *, 8175 + for (DWORD j = i, k = i + 0x100; j < k; j++) + if (*(BYTE *)j == 0xe8) { + DWORD t = j + 5 + *(DWORD *)(j + 1); + if (t > module_base_ && t < module_limit_) { + HookParam hp = {}; + hp.address = t; + hp.offset = -0x8; + hp.length_offset = 1; + hp.type = BIG_ENDIAN; + ConsoleOutput("vnreng: INSERT MED"); + NewHook(hp, "MED"); + //RegisterEngineType(ENGINE_MED); + return true; + } + } + + //ConsoleOutput("Unknown MED engine."); + ConsoleOutput("vnreng:MED: failed"); + return false; +} +/******************************************************************************************** +AbelSoftware hook: + The game folder usually is made up many no extended name files(file name doesn't have '.'). + And these files have common prefix which is the game name, and 2 digit in order. + + +********************************************************************************************/ +/** 7/31/2015 + * Sample game オタカ� * Hooked address: 0x4413b0 + * + * GDI functions are cached: TextOutA and GetTextExtentPoint32A + * + * 004413AB 90 NOP + * 004413AC 90 NOP + * 004413AD 90 NOP + * 004413AE 90 NOP + * 004413AF 90 NOP + * 004413B0 6A FF PUSH -0x1 ; jichi: text in arg1, but text painted character by character + * 004413B2 68 D0714900 PUSH .004971D0 + * 004413B7 64:A1 00000000 MOV EAX,DWORD PTR FS:[0] + * 004413BD 50 PUSH EAX + * 004413BE 64:8925 00000000 MOV DWORD PTR FS:[0],ESP + * 004413C5 83EC 4C SUB ESP,0x4C + * 004413C8 A1 C00B4B00 MOV EAX,DWORD PTR DS:[0x4B0BC0] + * 004413CD 53 PUSH EBX + * 004413CE 55 PUSH EBP + * 004413CF 56 PUSH ESI + * 004413D0 57 PUSH EDI + * 004413D1 8BF1 MOV ESI,ECX + * 004413D3 894424 48 MOV DWORD PTR SS:[ESP+0x48],EAX + * 004413D7 894424 4C MOV DWORD PTR SS:[ESP+0x4C],EAX + * 004413DB 894424 58 MOV DWORD PTR SS:[ESP+0x58],EAX + * 004413DF 8B4424 6C MOV EAX,DWORD PTR SS:[ESP+0x6C] + * 004413E3 33DB XOR EBX,EBX + * 004413E5 50 PUSH EAX + * 004413E6 8D4C24 4C LEA ECX,DWORD PTR SS:[ESP+0x4C] + * 004413EA 895C24 68 MOV DWORD PTR SS:[ESP+0x68],EBX + * 004413EE E8 74520400 CALL .00486667 + * 004413F3 8B4C24 78 MOV ECX,DWORD PTR SS:[ESP+0x78] + * 004413F7 51 PUSH ECX + * 004413F8 8D4C24 50 LEA ECX,DWORD PTR SS:[ESP+0x50] + * 004413FC E8 66520400 CALL .00486667 + * 00441401 8B5424 7C MOV EDX,DWORD PTR SS:[ESP+0x7C] + * 00441405 8D4C24 58 LEA ECX,DWORD PTR SS:[ESP+0x58] + * 00441409 52 PUSH EDX + * 0044140A E8 58520400 CALL .00486667 + * 0044140F 8B4424 70 MOV EAX,DWORD PTR SS:[ESP+0x70] + * 00441413 894424 50 MOV DWORD PTR SS:[ESP+0x50],EAX + * 00441417 8B4424 74 MOV EAX,DWORD PTR SS:[ESP+0x74] + * 0044141B 83F8 FF CMP EAX,-0x1 + * 0044141E 75 06 JNZ SHORT .00441426 + * 00441420 895C24 54 MOV DWORD PTR SS:[ESP+0x54],EBX + * 00441424 EB 2E JMP SHORT .00441454 + * 00441426 8BC8 MOV ECX,EAX + * 00441428 33D2 XOR EDX,EDX + * 0044142A 81E1 FF000000 AND ECX,0xFF + * 00441430 8AD4 MOV DL,AH + * 00441432 81C9 00FFFFFF OR ECX,0xFFFFFF00 + * 00441438 81E2 FF000000 AND EDX,0xFF + * 0044143E C1E1 08 SHL ECX,0x8 + * 00441441 0BCA OR ECX,EDX + * 00441443 C1E8 10 SHR EAX,0x10 + * 00441446 C1E1 08 SHL ECX,0x8 + * 00441449 25 FF000000 AND EAX,0xFF + * 0044144E 0BC8 OR ECX,EAX + * 00441450 894C24 54 MOV DWORD PTR SS:[ESP+0x54],ECX + * 00441454 8B4424 48 MOV EAX,DWORD PTR SS:[ESP+0x48] + * 00441458 3958 F8 CMP DWORD PTR DS:[EAX-0x8],EBX + * 0044145B 0F84 7A030000 JE .004417DB + * 00441461 8B8E 08020000 MOV ECX,DWORD PTR DS:[ESI+0x208] + * 00441467 83F9 20 CMP ECX,0x20 + * 0044146A 0F8D 35030000 JGE .004417A5 + * 00441470 0FBE00 MOVSX EAX,BYTE PTR DS:[EAX] + * 00441473 83E8 09 SUB EAX,0x9 + * 00441476 0F84 29030000 JE .004417A5 + * 0044147C 48 DEC EAX + * 0044147D 0F84 0A030000 JE .0044178D + * 00441483 83E8 03 SUB EAX,0x3 + * 00441486 0F84 19030000 JE .004417A5 + * 0044148C 8BBE 38010000 MOV EDI,DWORD PTR DS:[ESI+0x138] + * 00441492 68 80C84A00 PUSH .004AC880 + * 00441497 8BCF MOV ECX,EDI + * 00441499 E8 E2E9FDFF CALL .0041FE80 + * 0044149E 3BC3 CMP EAX,EBX + * 004414A0 7D 0F JGE SHORT .004414B1 + * 004414A2 53 PUSH EBX + * 004414A3 53 PUSH EBX + * 004414A4 53 PUSH EBX + * 004414A5 53 PUSH EBX + * 004414A6 8D4C24 48 LEA ECX,DWORD PTR SS:[ESP+0x48] + * 004414AA E8 916DFDFF CALL .00418240 + * 004414AF EB 06 JMP SHORT .004414B7 + * 004414B1 8B4F 24 MOV ECX,DWORD PTR DS:[EDI+0x24] + * 004414B4 8B0481 MOV EAX,DWORD PTR DS:[ECX+EAX*4] + * 004414B7 8B48 04 MOV ECX,DWORD PTR DS:[EAX+0x4] + * 004414BA 8B10 MOV EDX,DWORD PTR DS:[EAX] + * 004414BC 894C24 24 MOV DWORD PTR SS:[ESP+0x24],ECX + * 004414C0 895424 20 MOV DWORD PTR SS:[ESP+0x20],EDX + * 004414C4 8B50 08 MOV EDX,DWORD PTR DS:[EAX+0x8] + * 004414C7 8B40 0C MOV EAX,DWORD PTR DS:[EAX+0xC] + * 004414CA 8D4C24 10 LEA ECX,DWORD PTR SS:[ESP+0x10] + * 004414CE 895424 28 MOV DWORD PTR SS:[ESP+0x28],EDX + * 004414D2 51 PUSH ECX + * 004414D3 8BCE MOV ECX,ESI + * 004414D5 894424 30 MOV DWORD PTR SS:[ESP+0x30],EAX + * 004414D9 E8 52F3FFFF CALL .00440830 + * 004414DE 8B5424 50 MOV EDX,DWORD PTR SS:[ESP+0x50] + * 004414E2 33C9 XOR ECX,ECX + * 004414E4 894C24 78 MOV DWORD PTR SS:[ESP+0x78],ECX + * 004414E8 B8 B0B64900 MOV EAX,.0049B6B0 + * 004414ED 3B10 CMP EDX,DWORD PTR DS:[EAX] + * 004414EF 7E 0B JLE SHORT .004414FC + * 004414F1 83C0 04 ADD EAX,0x4 + * 004414F4 41 INC ECX + * 004414F5 3D C0B64900 CMP EAX,.0049B6C0 + * 004414FA ^72 F1 JB SHORT .004414ED + * 004414FC 8B5424 48 MOV EDX,DWORD PTR SS:[ESP+0x48] + * 00441500 8D4424 18 LEA EAX,DWORD PTR SS:[ESP+0x18] + * 00441504 894C24 78 MOV DWORD PTR SS:[ESP+0x78],ECX + * 00441508 8B4C8E 3C MOV ECX,DWORD PTR DS:[ESI+ECX*4+0x3C] + * 0044150C 52 PUSH EDX + * 0044150D 50 PUSH EAX + * 0044150E E8 3D34FCFF CALL .00404950 + * 00441513 8B46 38 MOV EAX,DWORD PTR DS:[ESI+0x38] + * 00441516 895C24 70 MOV DWORD PTR SS:[ESP+0x70],EBX + * 0044151A 3BC3 CMP EAX,EBX + * 0044151C 0F84 F9000000 JE .0044161B + * 00441522 8B50 08 MOV EDX,DWORD PTR DS:[EAX+0x8] + * 00441525 8B4E 78 MOV ECX,DWORD PTR DS:[ESI+0x78] + * 00441528 3BCA CMP ECX,EDX + * 0044152A 0F8D EB000000 JGE .0044161B + * 00441530 8B50 04 MOV EDX,DWORD PTR DS:[EAX+0x4] + * 00441533 8B4424 10 MOV EAX,DWORD PTR SS:[ESP+0x10] + * 00441537 8B7E 74 MOV EDI,DWORD PTR DS:[ESI+0x74] + * 0044153A 8B2C8A MOV EBP,DWORD PTR DS:[EDX+ECX*4] + * 0044153D 8B4C24 18 MOV ECX,DWORD PTR SS:[ESP+0x18] + * 00441541 897C24 7C MOV DWORD PTR SS:[ESP+0x7C],EDI + * 00441545 8B55 00 MOV EDX,DWORD PTR SS:[EBP] + * 00441548 8D1C01 LEA EBX,DWORD PTR DS:[ECX+EAX] + * 0044154B 8BCD MOV ECX,EBP + * 0044154D FF52 08 CALL DWORD PTR DS:[EDX+0x8] + * 00441550 3BF8 CMP EDI,EAX + * 00441552 0F8D C3000000 JGE .0044161B + * 00441558 EB 04 JMP SHORT .0044155E + * 0044155A 8B7C24 7C MOV EDI,DWORD PTR SS:[ESP+0x7C] + * 0044155E 8B45 00 MOV EAX,DWORD PTR SS:[EBP] + * 00441561 57 PUSH EDI + * 00441562 8BCD MOV ECX,EBP + * 00441564 FF50 04 CALL DWORD PTR DS:[EAX+0x4] + * 00441567 8BF8 MOV EDI,EAX + * 00441569 8BCF MOV ECX,EDI + * 0044156B 8B17 MOV EDX,DWORD PTR DS:[EDI] + * 0044156D FF52 0C CALL DWORD PTR DS:[EDX+0xC] + * 00441570 85C0 TEST EAX,EAX + * 00441572 0F84 A3000000 JE .0044161B + * 00441578 8B07 MOV EAX,DWORD PTR DS:[EDI] + * 0044157A 8D4C24 6C LEA ECX,DWORD PTR SS:[ESP+0x6C] + * 0044157E 51 PUSH ECX + * 0044157F 8BCF MOV ECX,EDI + * 00441581 FF50 10 CALL DWORD PTR DS:[EAX+0x10] + * 00441584 8B5424 6C MOV EDX,DWORD PTR SS:[ESP+0x6C] + * 00441588 8B4C24 78 MOV ECX,DWORD PTR SS:[ESP+0x78] + * 0044158C 8D4424 30 LEA EAX,DWORD PTR SS:[ESP+0x30] + * 00441590 52 PUSH EDX + * 00441591 8B4C8E 3C MOV ECX,DWORD PTR DS:[ESI+ECX*4+0x3C] + * 00441595 50 PUSH EAX + * 00441596 C64424 6C 01 MOV BYTE PTR SS:[ESP+0x6C],0x1 + * 0044159B E8 B033FCFF CALL .00404950 + * 004415A0 8B10 MOV EDX,DWORD PTR DS:[EAX] + * 004415A2 8B86 E4030000 MOV EAX,DWORD PTR DS:[ESI+0x3E4] + * 004415A8 03DA ADD EBX,EDX + * 004415AA 8B5424 6C MOV EDX,DWORD PTR SS:[ESP+0x6C] + * 004415AE 52 PUSH EDX + * 004415AF 50 PUSH EAX + * 004415B0 E8 BB020000 CALL .00441870 + * 004415B5 83C4 08 ADD ESP,0x8 + * 004415B8 85C0 TEST EAX,EAX + * 004415BA 74 08 JE SHORT .004415C4 + * 004415BC 3B5C24 28 CMP EBX,DWORD PTR SS:[ESP+0x28] + * 004415C0 7F 43 JG SHORT .00441605 + * 004415C2 EB 18 JMP SHORT .004415DC + * 004415C4 8B4C24 6C MOV ECX,DWORD PTR SS:[ESP+0x6C] + * 004415C8 8B86 E0030000 MOV EAX,DWORD PTR DS:[ESI+0x3E0] + * 004415CE 51 PUSH ECX + * 004415CF 50 PUSH EAX + * 004415D0 E8 9B020000 CALL .00441870 + * 004415D5 83C4 08 ADD ESP,0x8 + * 004415D8 85C0 TEST EAX,EAX + * 004415DA 74 31 JE SHORT .0044160D + * 004415DC 8D4C24 6C LEA ECX,DWORD PTR SS:[ESP+0x6C] + * 004415E0 C64424 64 00 MOV BYTE PTR SS:[ESP+0x64],0x0 + * 004415E5 E8 404F0400 CALL .0048652A + * 004415EA 8B7C24 7C MOV EDI,DWORD PTR SS:[ESP+0x7C] + * 004415EE 8B55 00 MOV EDX,DWORD PTR SS:[EBP] + * 004415F1 47 INC EDI + * 004415F2 8BCD MOV ECX,EBP + * 004415F4 897C24 7C MOV DWORD PTR SS:[ESP+0x7C],EDI + * 004415F8 FF52 08 CALL DWORD PTR DS:[EDX+0x8] + * 004415FB 3BF8 CMP EDI,EAX + * 004415FD ^0F8C 57FFFFFF JL .0044155A + * 00441603 EB 16 JMP SHORT .0044161B + * 00441605 C74424 70 010000>MOV DWORD PTR SS:[ESP+0x70],0x1 + * 0044160D 8D4C24 6C LEA ECX,DWORD PTR SS:[ESP+0x6C] + * 00441611 C64424 64 00 MOV BYTE PTR SS:[ESP+0x64],0x0 + * 00441616 E8 0F4F0400 CALL .0048652A + * 0044161B 8B4424 10 MOV EAX,DWORD PTR SS:[ESP+0x10] + * 0044161F 8B4C24 18 MOV ECX,DWORD PTR SS:[ESP+0x18] + * 00441623 03C8 ADD ECX,EAX + * 00441625 8B4424 28 MOV EAX,DWORD PTR SS:[ESP+0x28] + * 00441629 3BC8 CMP ECX,EAX + * 0044162B 7E 18 JLE SHORT .00441645 + * 0044162D 8B5424 48 MOV EDX,DWORD PTR SS:[ESP+0x48] + * 00441631 8B86 E0030000 MOV EAX,DWORD PTR DS:[ESI+0x3E0] + * 00441637 52 PUSH EDX + * 00441638 50 PUSH EAX + * 00441639 E8 32020000 CALL .00441870 + * 0044163E 83C4 08 ADD ESP,0x8 + * 00441641 85C0 TEST EAX,EAX + * 00441643 74 08 JE SHORT .0044164D + * 00441645 8B4424 70 MOV EAX,DWORD PTR SS:[ESP+0x70] + * 00441649 85C0 TEST EAX,EAX + * 0044164B 74 3F JE SHORT .0044168C + * 0044164D 8B8E 08020000 MOV ECX,DWORD PTR DS:[ESI+0x208] + * 00441653 41 INC ECX + * 00441654 8BC1 MOV EAX,ECX + * 00441656 898E 08020000 MOV DWORD PTR DS:[ESI+0x208],ECX + * 0044165C 83F8 20 CMP EAX,0x20 + * 0044165F 0F8D 40010000 JGE .004417A5 + * 00441665 83EC 10 SUB ESP,0x10 + * 00441668 8B15 D0B04A00 MOV EDX,DWORD PTR DS:[0x4AB0D0] + * 0044166E 8BDC MOV EBX,ESP + * 00441670 33C0 XOR EAX,EAX + * 00441672 8B3D D4B04A00 MOV EDI,DWORD PTR DS:[0x4AB0D4] + * 00441678 33C9 XOR ECX,ECX + * 0044167A 8903 MOV DWORD PTR DS:[EBX],EAX + * 0044167C 894B 04 MOV DWORD PTR DS:[EBX+0x4],ECX + * 0044167F 8BCE MOV ECX,ESI + * 00441681 8953 08 MOV DWORD PTR DS:[EBX+0x8],EDX + * 00441684 897B 0C MOV DWORD PTR DS:[EBX+0xC],EDI + * 00441687 E8 7418FCFF CALL .00402F00 + * 0044168C 8B86 08020000 MOV EAX,DWORD PTR DS:[ESI+0x208] + * 00441692 6A 00 PUSH 0x0 + * 00441694 8D0CC5 00000000 LEA ECX,DWORD PTR DS:[EAX*8] + * 0044169B 2BC8 SUB ECX,EAX + * 0044169D 8B948E 78040000 MOV EDX,DWORD PTR DS:[ESI+ECX*4+0x478] + * 004416A4 8DAC8E 70040000 LEA EBP,DWORD PTR DS:[ESI+ECX*4+0x470] + * 004416AB 52 PUSH EDX + * 004416AC 8BCD MOV ECX,EBP + * 004416AE E8 7D8A0000 CALL .0044A130 + * 004416B3 8BD8 MOV EBX,EAX + * 004416B5 8D4424 48 LEA EAX,DWORD PTR SS:[ESP+0x48] + * 004416B9 50 PUSH EAX + * 004416BA 8D7B 08 LEA EDI,DWORD PTR DS:[EBX+0x8] + * 004416BD 8BCF MOV ECX,EDI + * 004416BF E8 534F0400 CALL .00486617 + * 004416C4 8D4C24 4C LEA ECX,DWORD PTR SS:[ESP+0x4C] + * 004416C8 51 PUSH ECX + * 004416C9 8D4F 04 LEA ECX,DWORD PTR DS:[EDI+0x4] + * 004416CC E8 464F0400 CALL .00486617 + * 004416D1 8B5424 50 MOV EDX,DWORD PTR SS:[ESP+0x50] + * 004416D5 8D4C24 58 LEA ECX,DWORD PTR SS:[ESP+0x58] + * 004416D9 8957 08 MOV DWORD PTR DS:[EDI+0x8],EDX + * 004416DC 8B4424 54 MOV EAX,DWORD PTR SS:[ESP+0x54] + * 004416E0 51 PUSH ECX + * 004416E1 8D4F 10 LEA ECX,DWORD PTR DS:[EDI+0x10] + * 004416E4 8947 0C MOV DWORD PTR DS:[EDI+0xC],EAX + * 004416E7 E8 2B4F0400 CALL .00486617 + * 004416EC 8B45 08 MOV EAX,DWORD PTR SS:[EBP+0x8] + * 004416EF 85C0 TEST EAX,EAX + * 004416F1 74 04 JE SHORT .004416F7 + * 004416F3 8918 MOV DWORD PTR DS:[EAX],EBX + * 004416F5 EB 03 JMP SHORT .004416FA + * 004416F7 895D 04 MOV DWORD PTR SS:[EBP+0x4],EBX + * 004416FA 83EC 10 SUB ESP,0x10 + * 004416FD 895D 08 MOV DWORD PTR SS:[EBP+0x8],EBX + * 00441700 8B4424 20 MOV EAX,DWORD PTR SS:[ESP+0x20] + * 00441704 8B5424 28 MOV EDX,DWORD PTR SS:[ESP+0x28] + * 00441708 8B7C24 2C MOV EDI,DWORD PTR SS:[ESP+0x2C] + * 0044170C 8BDC MOV EBX,ESP + * 0044170E 8D4C02 02 LEA ECX,DWORD PTR DS:[EDX+EAX+0x2] + * 00441712 8B5424 24 MOV EDX,DWORD PTR SS:[ESP+0x24] + * 00441716 8903 MOV DWORD PTR DS:[EBX],EAX + * 00441718 8D7C3A 02 LEA EDI,DWORD PTR DS:[EDX+EDI+0x2] + * 0044171C 8953 04 MOV DWORD PTR DS:[EBX+0x4],EDX + * 0044171F 894B 08 MOV DWORD PTR DS:[EBX+0x8],ECX + * 00441722 8BCE MOV ECX,ESI + * 00441724 897B 0C MOV DWORD PTR DS:[EBX+0xC],EDI + * 00441727 E8 D417FCFF CALL .00402F00 + * 0044172C 8B4424 4C MOV EAX,DWORD PTR SS:[ESP+0x4C] + * 00441730 8B48 F8 MOV ECX,DWORD PTR DS:[EAX-0x8] + * 00441733 85C9 TEST ECX,ECX + * 00441735 74 6E JE SHORT .004417A5 + * 00441737 8B4E 3C MOV ECX,DWORD PTR DS:[ESI+0x3C] + * 0044173A 50 PUSH EAX + * 0044173B 8D4424 24 LEA EAX,DWORD PTR SS:[ESP+0x24] + * 0044173F 50 PUSH EAX + * 00441740 E8 0B32FCFF CALL .00404950 + * 00441745 8B5C24 20 MOV EBX,DWORD PTR SS:[ESP+0x20] + * 00441749 8B4C24 18 MOV ECX,DWORD PTR SS:[ESP+0x18] + * 0044174D 8B7C24 24 MOV EDI,DWORD PTR SS:[ESP+0x24] + * 00441751 8BC3 MOV EAX,EBX + * 00441753 2BC1 SUB EAX,ECX + * 00441755 8BCF MOV ECX,EDI + * 00441757 99 CDQ + * 00441758 2BC2 SUB EAX,EDX + * 0044175A 8B5424 14 MOV EDX,DWORD PTR SS:[ESP+0x14] + * 0044175E F7D9 NEG ECX + * 00441760 D1F8 SAR EAX,1 + * 00441762 03CA ADD ECX,EDX + * 00441764 8B5424 10 MOV EDX,DWORD PTR SS:[ESP+0x10] + * 00441768 F7D8 NEG EAX + * 0044176A 03C2 ADD EAX,EDX + * 0044176C 83EC 10 SUB ESP,0x10 + * 0044176F 8D7C39 02 LEA EDI,DWORD PTR DS:[ECX+EDI+0x2] + * 00441773 8D5418 02 LEA EDX,DWORD PTR DS:[EAX+EBX+0x2] + * 00441777 8BDC MOV EBX,ESP + * 00441779 8903 MOV DWORD PTR DS:[EBX],EAX + * 0044177B 894B 04 MOV DWORD PTR DS:[EBX+0x4],ECX + * 0044177E 8BCE MOV ECX,ESI + * 00441780 8953 08 MOV DWORD PTR DS:[EBX+0x8],EDX + * 00441783 897B 0C MOV DWORD PTR DS:[EBX+0xC],EDI + * 00441786 E8 7517FCFF CALL .00402F00 + * 0044178B EB 18 JMP SHORT .004417A5 + * 0044178D 8D41 29 LEA EAX,DWORD PTR DS:[ECX+0x29] + * 00441790 8D14C5 00000000 LEA EDX,DWORD PTR DS:[EAX*8] + * 00441797 2BD0 SUB EDX,EAX + * 00441799 391C96 CMP DWORD PTR DS:[ESI+EDX*4],EBX + * 0044179C 74 07 JE SHORT .004417A5 + * 0044179E 41 INC ECX + * 0044179F 898E 08020000 MOV DWORD PTR DS:[ESI+0x208],ECX + * 004417A5 8B86 E8020000 MOV EAX,DWORD PTR DS:[ESI+0x2E8] + * 004417AB 33DB XOR EBX,EBX + * 004417AD 3BC3 CMP EAX,EBX + * 004417AF 74 2A JE SHORT .004417DB + * 004417B1 399E C8030000 CMP DWORD PTR DS:[ESI+0x3C8],EBX + * 004417B7 75 22 JNZ SHORT .004417DB + * 004417B9 8B86 C4030000 MOV EAX,DWORD PTR DS:[ESI+0x3C4] + * 004417BF 8BCE MOV ECX,ESI + * 004417C1 50 PUSH EAX + * 004417C2 E8 89040000 CALL .00441C50 + * 004417C7 3B86 3C020000 CMP EAX,DWORD PTR DS:[ESI+0x23C] + * 004417CD 74 06 JE SHORT .004417D5 + * 004417CF 8986 38020000 MOV DWORD PTR DS:[ESI+0x238],EAX + * 004417D5 8986 3C020000 MOV DWORD PTR DS:[ESI+0x23C],EAX + * 004417DB 399E 30020000 CMP DWORD PTR DS:[ESI+0x230],EBX + * 004417E1 75 3C JNZ SHORT .0044181F + * 004417E3 8BCE MOV ECX,ESI + * 004417E5 E8 C6040000 CALL .00441CB0 + * 004417EA 85C0 TEST EAX,EAX + * 004417EC 75 31 JNZ SHORT .0044181F + * 004417EE 399E 18020000 CMP DWORD PTR DS:[ESI+0x218],EBX + * 004417F4 74 29 JE SHORT .0044181F + * 004417F6 83BE C4020000 64 CMP DWORD PTR DS:[ESI+0x2C4],0x64 + * 004417FD 74 20 JE SHORT .0044181F + * 004417FF 8B86 08020000 MOV EAX,DWORD PTR DS:[ESI+0x208] + * 00441805 83F8 20 CMP EAX,0x20 + * 00441808 7D 1D JGE SHORT .00441827 + * 0044180A 83C0 29 ADD EAX,0x29 + * 0044180D 8D0CC5 00000000 LEA ECX,DWORD PTR DS:[EAX*8] + * 00441814 2BC8 SUB ECX,EAX + * 00441816 391C8E CMP DWORD PTR DS:[ESI+ECX*4],EBX + * 00441819 74 0C JE SHORT .00441827 + * 0044181B 6A 01 PUSH 0x1 + * 0044181D EB 01 JMP SHORT .00441820 + * 0044181F 53 PUSH EBX + * 00441820 8BCE MOV ECX,ESI + * 00441822 E8 49C5FEFF CALL .0042DD70 + * 00441827 8D4C24 58 LEA ECX,DWORD PTR SS:[ESP+0x58] + * 0044182B C74424 64 030000>MOV DWORD PTR SS:[ESP+0x64],0x3 + * 00441833 E8 F24C0400 CALL .0048652A + * 00441838 8D4C24 4C LEA ECX,DWORD PTR SS:[ESP+0x4C] + * 0044183C C64424 64 02 MOV BYTE PTR SS:[ESP+0x64],0x2 + * 00441841 E8 E44C0400 CALL .0048652A + * 00441846 8D4C24 48 LEA ECX,DWORD PTR SS:[ESP+0x48] + * 0044184A C74424 64 FFFFFF>MOV DWORD PTR SS:[ESP+0x64],-0x1 + * 00441852 E8 D34C0400 CALL .0048652A + * 00441857 8B4C24 5C MOV ECX,DWORD PTR SS:[ESP+0x5C] + * 0044185B 5F POP EDI + * 0044185C 5E POP ESI + * 0044185D 5D POP EBP + * 0044185E 64:890D 00000000 MOV DWORD PTR FS:[0],ECX + * 00441865 5B POP EBX + * 00441866 83C4 58 ADD ESP,0x58 + * 00441869 C2 1400 RETN 0x14 + * 0044186C 90 NOP + * 0044186D 90 NOP + * 0044186E 90 NOP + * 0044186F 90 NOP + * + * Another sample game: 不条琸�界の探偵令嬢 + */ +bool InsertAbelHook() +{ + // jichi: If this pattern failed again, try the following pattern instead: + // 004413D3 894424 48 MOV DWORD PTR SS:[ESP+0x48],EAX + // 004413D7 894424 4C MOV DWORD PTR SS:[ESP+0x4C],EAX + // 004413DB 894424 58 MOV DWORD PTR SS:[ESP+0x58],EAX + + const DWORD character[] = {0xc981d48a, 0xffffff00}; + if (DWORD j = SearchPattern(module_base_, module_limit_ - module_base_, character, sizeof(character))) { + j += module_base_; + for (DWORD i = j - 0x100; j > i; j--) + if (*(WORD *)j == 0xff6a) { + HookParam hp = {}; + hp.address = j; + hp.offset = 4; // text in arg1 + hp.type = USING_STRING|NO_CONTEXT; + ConsoleOutput("vnreng: INSERT Abel"); + //GROWL_DWORD(hp.address); + NewHook(hp, "Abel"); + //RegisterEngineType(ENGINE_ABEL); + return true; + } + } + ConsoleOutput("vnreng:Abel: failed"); + return false; +} +bool InsertLiveDynamicHook(LPVOID addr, DWORD frame, DWORD stack) +{ + if (addr != ::GetGlyphOutlineA || !frame) + return false; + DWORD k = *(DWORD *)frame; + k = *(DWORD *)(k + 4); + if (*(BYTE *)(k - 5) != 0xe8) + k = *(DWORD *)(frame + 4); + DWORD j = k + *(DWORD *)(k - 4); + if (j > module_base_ && j < module_limit_) { + HookParam hp = {}; + hp.address = j; + hp.offset = -0x10; + hp.length_offset = 1; + hp.type = BIG_ENDIAN; + ConsoleOutput("vnreng: INSERT DynamicLive"); + NewHook(hp, "Live"); + //RegisterEngineType(ENGINE_LIVE); + return true; + } + ConsoleOutput("vnreng:DynamicLive: failed"); + return true; // jichi 12/25/2013: return true +} +//void InsertLiveHook() +//{ +// ConsoleOutput("Probably Live. Wait for text."); +// trigger_fun_=InsertLiveDynamicHook; +// SwitchTrigger(true); +//} +bool InsertLiveHook() +{ + const BYTE ins[] = {0x64,0x89,0x20,0x8b,0x45,0x0c,0x50}; + ULONG addr = MemDbg::findBytes(ins, sizeof(ins), module_base_, module_limit_); + if (!addr) { + ConsoleOutput("vnreng:Live: pattern not found"); + return false; + } + HookParam hp = {}; + hp.address = addr; + hp.offset = -0x10; + hp.length_offset = 1; + hp.type = BIG_ENDIAN; + ConsoleOutput("vnreng: INSERT Live"); + NewHook(hp, "Live"); + //RegisterEngineType(ENGINE_LIVE); + //else ConsoleOutput("Unknown Live engine"); + return true; +} + +void InsertBrunsHook() +{ + if (IthCheckFile(L"libscr.dll")) { + HookParam hp = {}; + hp.offset = 4; + hp.length_offset = 1; + hp.type = USING_UNICODE|MODULE_OFFSET|FUNCTION_OFFSET; + // jichi 12/27/2013: This function does not work for the latest bruns games anymore + hp.function = 0x8b24c7bc; + //?push_back@?$basic_string@GU?$char_traits@G@std@@V?$allocator@G@2@@std@@QAEXG@Z + if (IthCheckFile(L"msvcp90.dll")) + hp.module = 0xc9c36a5b; // 3385027163 + else if (IthCheckFile(L"msvcp80.dll")) + hp.module = 0xa9c36a5b; // 2848156251 + else if (IthCheckFile(L"msvcp100.dll")) // jichi 8/17/2013: MSVCRT 10.0 and 11.0 + hp.module = 0xb571d760; // 3044136800; + else if (IthCheckFile(L"msvcp110.dll")) + hp.module = 0xd571d760; // 3581007712; + if (hp.module) { + ConsoleOutput("vnreng: INSERT Brus#1"); + NewHook(hp, "Bruns"); + } + } + //else + // jichi 12/21/2013: Keep both bruns hooks + // The first one does not work for games like 「オーク・キングダマモン娘繁殖�豚人王~�anymore. + { + union { + DWORD i; + DWORD *id; + WORD *iw; + BYTE *ib; + }; + DWORD k = module_limit_ - 4; + for (i = module_base_ + 0x1000; i < k; i++) { + if (*id != 0xff) //cmp reg,0xff + continue; + i += 4; + if (*iw != 0x8f0f) + continue;//jg + i += 2; + i += *id + 4; + for (DWORD j = i + 0x40; i < j; i++) { + if (*ib != 0xe8) + continue; + i++; + DWORD t = i + 4 + *id; + if (t > module_base_ && t module_base_ && t i - 0x100; j--) + if (*(DWORD *)j == 0x83ec8b55) { // push ebp, mov ebp,esp, sub esp,* + HookParam hp = {}; + hp.address = j; + hp.offset = 0x18; + hp.split = -0x18; + hp.length_offset = 1; + hp.type = DATA_INDIRECT|USING_SPLIT; + ConsoleOutput("vnreng: INSERT QLIE1"); + NewHook(hp, "QLiE"); + //RegisterEngineType(ENGINE_FRONTWING); + return true; + } + } + + ConsoleOutput("vnreng:QLIE1: failed"); + //ConsoleOutput("Unknown QLIE engine"); + return false; +} + +} // unnamed QLIE + +// jichi 8/18/2013: Add new hook +bool InsertQLIEHook() +{ return InsertQLIE1Hook() || InsertQLIE2Hook(); } + +/******************************************************************************************** +CandySoft hook: + Game folder contains many *.fpk. Engine name is SystemC. + I haven't seen this engine in other company/brand. + + AGTH /X3 will hook lstrlenA. One thread is the exactly result we want. + But the function call is difficult to located programmatically. + I find a equivalent points which is more easy to search. + The script processing function needs to find 0x5B'[', + so there should a instruction like cmp reg,5B + Find this position and navigate to function entry. + The first parameter is the string pointer. + This approach works fine with game later than つよきす2学� + + But the original つよき�is quite different. I handle this case separately. + +********************************************************************************************/ + +namespace { // unnamed Candy + +// jichi 8/23/2013: split into two different engines +//if (_wcsicmp(process_name_, L"systemc.exe")==0) +// Process name is "SystemC.exe" +bool InsertCandyHook1() +{ + for (DWORD i = module_base_ + 0x1000; i < module_limit_ - 4; i++) + if ((*(DWORD *)i&0xffffff) == 0x24f980) // cmp cl,24 + for (DWORD j = i, k = i - 0x100; j > k; j--) + if (*(DWORD *)j == 0xc0330a8a) { // mov cl,[edx]; xor eax,eax + HookParam hp = {}; + hp.address = j; + hp.offset = -0x10; // jichi: text in ecx + hp.type = USING_STRING; + ConsoleOutput("vnreng: INSERT SystemC#1"); + NewHook(hp, "SystemC"); + //RegisterEngineType(ENGINE_CANDY); + return true; + } + ConsoleOutput("vnreng:CandyHook1: failed"); + return false; +} + +// jichi 8/23/2013: Process name is NOT "SystemC.exe" +bool InsertCandyHook2() +{ + for (DWORD i = module_base_ + 0x1000; i < module_limit_ - 4 ;i++) + if (*(WORD *)i == 0x5b3c || // cmp al,0x5b + (*(DWORD *)i & 0xfff8fc) == 0x5bf880) // cmp reg,0x5B + for (DWORD j = i, k = i - 0x100; j > k; j--) + if ((*(DWORD *)j & 0xffff) == 0x8b55) { // push ebp, mov ebp,esp, sub esp,* + HookParam hp = {}; + hp.address = j; + hp.offset = 4; // jichi: text in arg1 + hp.type = USING_STRING; + ConsoleOutput("vnreng: INSERT SystemC#2"); + NewHook(hp, "SystemC"); + //RegisterEngineType(ENGINE_CANDY); + return true; + } + ConsoleOutput("vnreng:CandyHook2: failed"); + return false; +} + +/** jichi 10/2/2013: CHECKPOINT + * + * [5/31/2013] 恋もHもお勉強も、おまかせ�お姉ちも�部 + * base = 0xf20000 + * + シナリオ: /HSN-4@104A48:ANEBU.EXE + * - off: 4294967288 = 0xfffffff8 = -8 + , - type: 1025 = 0x401 + * + 選択肢: /HSN-4@104FDD:ANEBU.EXE + * - off: 4294967288 = 0xfffffff8 = -8 + * - type: 1089 = 0x441 + */ +//bool InsertCandyHook3() +//{ +// return false; // CHECKPOINT +// const BYTE ins[] = { +// 0x83,0xc4, 0x0c, // add esp,0xc ; hook here +// 0x0f,0xb6,0xc0, // movzx eax,al +// 0x85,0xc0, // test eax,eax +// 0x75, 0x0e // jnz XXOO ; it must be 0xe, or there will be duplication +// }; +// enum { addr_offset = 0 }; +// ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); +// ULONG reladdr = SearchPattern(module_base_, range, ins, sizeof(ins)); +// reladdr = 0x104a48; +// GROWL_DWORD(module_base_); +// //GROWL_DWORD3(reladdr, module_base_, range); +// if (!reladdr) +// return false; +// +// HookParam hp = {}; +// hp.address = module_base_ + reladdr + addr_offset; +// hp.offset = -8; +// hp.type = USING_STRING|NO_CONTEXT; +// NewHook(hp, "Candy"); +// return true; +//} + +} // unnamed Candy + +// jichi 10/2/2013: Add new candy hook +bool InsertCandyHook() +{ + //if (0 == _wcsicmp(process_name_, L"systemc.exe")) + if (IthCheckFile(L"SystemC.exe")) + return InsertCandyHook1(); + else + return InsertCandyHook2(); + //bool b2 = InsertCandyHook2(), + // b3 = InsertCandyHook3(); + //return b2 || b3; +} + +/******************************************************************************************** +Apricot hook: + Game folder contains arc.a*. + This engine is heavily based on new DirectX interfaces. + I can't find a good place where text is clean and not repeating. + The game processes script encoded in UTF32-like format. + I reversed the parsing algorithm of the game and implemented it partially. + Only name and text data is needed. + +********************************************************************************************/ + +/** jichi 2/15/2015: ApricoT + * + * Sample game: イセカイ・ラヴァーズ�体験版 + * Issue of the old game is that it uses esp as split, and hence has relative address + * + * 00978100 5b pop ebx + * 00978101 83c4 2c add esp,0x2c + * 00978104 c2 0400 retn 0x4 + * 00978107 33c0 xor eax,eax ; jichi: hook here + * 00978109 bb 03000000 mov ebx,0x3 + * 0097810e 895c24 30 mov dword ptr ss:[esp+0x30],ebx + * 00978112 894424 2c mov dword ptr ss:[esp+0x2c],eax + * 00978116 894424 1c mov dword ptr ss:[esp+0x1c],eax + * 0097811a 8b4e 34 mov ecx,dword ptr ds:[esi+0x34] + * 0097811d 3b4e 3c cmp ecx,dword ptr ds:[esi+0x3c] + * 00978120 894424 3c mov dword ptr ss:[esp+0x3c],eax + * 00978124 7e 3b jle short .00978161 + * 00978126 8b7e 3c mov edi,dword ptr ds:[esi+0x3c] + * 00978129 3b7e 34 cmp edi,dword ptr ds:[esi+0x34] + * 0097812c 76 05 jbe short .00978133 + * 0097812e e8 01db1500 call .00ad5c34 + * 00978133 837e 38 04 cmp dword ptr ds:[esi+0x38],0x4 + * 00978137 72 05 jb short .0097813e + * 00978139 8b46 24 mov eax,dword ptr ds:[esi+0x24] + * 0097813c eb 03 jmp short .00978141 + * 0097813e 8d46 24 lea eax,dword ptr ds:[esi+0x24] + * 00978141 8b3cb8 mov edi,dword ptr ds:[eax+edi*4] + * 00978144 016e 3c add dword ptr ds:[esi+0x3c],ebp + * 00978147 57 push edi + * 00978148 55 push ebp + * 00978149 8d4c24 20 lea ecx,dword ptr ss:[esp+0x20] + * 0097814d e8 de05feff call .00958730 + * + * Sample stack: baseaddr = 0c90000 + * 001aec2c ede50fbb + * 001aec30 0886064c + * 001aec34 08860bd0 + * 001aec38 08860620 + * 001aec3c 00000000 + * 001aec40 00000000 + * 001aec44 08860bd0 + * 001aec48 001aee18 + * 001aec4c 08860620 + * 001aec50 00000000 + * 001aec54 00cb4408 return to .00cb4408 from .00c973e0 + * 001aec58 08860bd8 + * 001aec5c 00000000 + * 001aec60 001aefd8 pointer to next seh record + * 001aec64 00e47d88 se handler + * 001aec68 ffffffff + * 001aec6c 00cb9f40 return to .00cb9f40 from .00cc8030 ; jichi: split here + */ +static void SpecialHookApricoT(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD reg_esi = *(DWORD *)(esp_base - 0x20); + DWORD base = *(DWORD *)(reg_esi + 0x24); + DWORD index = *(DWORD *)(reg_esi + 0x3c); + DWORD *script = (DWORD *)(base + index * 4); + // jichi 2/14/2015 + // Change reg_esp to the return address + //DWORD reg_esp = regof(esp, esp_base); + //*split = reg_esp; + //*split = regof(esp, esp_base); + DWORD arg = argof(16, esp_base); // return address + *split = arg > module_base_ ? arg - module_base_ : arg; // use relative split value + //*split = argof(1, esp_base); + if (script[0] == L'<') { + DWORD *end; + for (end = script; *end != L'>'; end++); // jichi 2/14/2015: i.e. = ::wcschr(script) or script + switch (script[1]) { + case L'N': + if (script[2] == L'a' && script[3] == L'm' && script[4] == L'e') { + buffer_index = 0; + for (script += 5; script < end; script++) + if (*script > 0x20) + wc_buffer[buffer_index++] = *script & 0xFFFF; + *len = buffer_index<<1; + *data = (DWORD)wc_buffer; + // jichi 1/4/2014: The way I save subconext is not able to distinguish the split value + // Change to shift 16 + //*split |= 1 << 31; + *split |= 1 << 16; // jichi: differentiate name and text script + } break; + case L'T': + if (script[2] == L'e' && script[3] == L'x' && script[4] == L't') { + buffer_index = 0; + for (script += 5; script < end; script++) { + if (*script > 0x40) { + while (*script == L'{') { + script++; + while (*script!=L'\\') { + wc_buffer[buffer_index++] = *script & 0xffff; + script++; + } + while (*script++!=L'}'); + } + wc_buffer[buffer_index++] = *script & 0xffff; + } + } + *len = buffer_index << 1; + *data = (DWORD)wc_buffer; + } break; + } + } +} + +bool InsertApricoTHook() +{ + for (DWORD i = module_base_ + 0x1000; i < module_limit_ - 4; i++) + if ((*(DWORD *)i & 0xfff8fc) == 0x3cf880) // cmp reg,0x3c + for (DWORD j = i + 3, k = i + 0x100; j < k; j++) + if ((*(DWORD *)j & 0xffffff) == 0x4c2) { // retn 4 + HookParam hp = {}; + hp.address = j + 3; + hp.text_fun = SpecialHookApricoT; + hp.type = USING_STRING|NO_CONTEXT|USING_UNICODE; + ConsoleOutput("vnreng: INSERT ApricoT"); + //GROWL_DWORD3(hp.address, module_base_, module_limit_); + NewHook(hp, "ApRicoT"); + //RegisterEngineType(ENGINE_APRICOT); + // jichi 2/14/2015: disable cached GDI functions + ConsoleOutput("vnreng:ApRicoT: disable GDI hooks"); + DisableGDIHooks(); + return true; + } + + ConsoleOutput("vnreng:ApricoT: failed"); + return false; +} +void InsertStuffScriptHook() +{ + // BOOL GetTextExtentPoint32( + // _In_ HDC hdc, + // _In_ LPCTSTR lpString, + // _In_ int c, + // _Out_ LPSIZE lpSize + // ); + HookParam hp = {}; + hp.address = (DWORD)::GetTextExtentPoint32A; + hp.offset = 0x8; // arg2 lpString + hp.split = -0x18; // jichi 8/12/2014: = -4 - pusha_esp_off + hp.type = USING_STRING | USING_SPLIT; + ConsoleOutput("vnreng: INSERT StuffScriptEngine"); + NewHook(hp, "StuffScriptEngine"); + //RegisterEngine(ENGINE_STUFFSCRIPT); +} +bool InsertTriangleHook() +{ + for (DWORD i = module_base_; i < module_limit_ - 4; i++) + if ((*(DWORD *)i & 0xffffff) == 0x75403c) // cmp al,0x40; jne + for (DWORD j = i + 4 + *(BYTE*)(i+3), k = j + 0x20; j < k; j++) + if (*(BYTE*)j == 0xe8) { + DWORD t = j + 5 + *(DWORD *)(j + 1); + if (t > module_base_ && t < module_limit_) { + HookParam hp = {}; + hp.address = t; + hp.offset = 4; + hp.type = USING_STRING; + ConsoleOutput("vnreng: INSERT Triangle"); + NewHook(hp, "Triangle"); + //RegisterEngineType(ENGINE_TRIANGLE); + return true; + } + } + //ConsoleOutput("Old/Unknown Triangle engine."); + ConsoleOutput("vnreng:Triangle: failed"); + return false; +} +bool InsertPensilHook() +{ + for (DWORD i = module_base_; i < module_limit_ - 4; i++) + if (*(DWORD *)i == 0x6381) // cmp *,8163 + if (DWORD j = SafeFindEntryAligned(i, 0x100)) { + HookParam hp = {}; + hp.address = j; + hp.offset = 8; + hp.length_offset = 1; + ConsoleOutput("vnreng: INSERT Pensil"); + NewHook(hp, "Pensil"); + return true; + //RegisterEngineType(ENGINE_PENSIL); + } + //ConsoleOutput("Unknown Pensil engine."); + ConsoleOutput("vnreng:Pensil: failed"); + return false; +} +#if 0 // jich 3/8/2015: disabled +bool IsPensilSetup() +{ + HANDLE hFile = IthCreateFile(L"PSetup.exe", FILE_READ_DATA, FILE_SHARE_READ, FILE_OPEN); + FILE_STANDARD_INFORMATION info; + IO_STATUS_BLOCK ios; + LPVOID buffer = nullptr; + NtQueryInformationFile(hFile, &ios, &info, sizeof(info), FileStandardInformation); + NtAllocateVirtualMemory(NtCurrentProcess(), &buffer, 0, + &info.AllocationSize.LowPart, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + NtReadFile(hFile, 0,0,0, &ios, buffer, info.EndOfFile.LowPart, 0, 0); + NtClose(hFile); + BYTE *b = (BYTE *)buffer; + DWORD len = info.EndOfFile.LowPart & ~1; + if (len == info.AllocationSize.LowPart) + len -= 2; + b[len] = 0; + b[len + 1] = 0; + bool ret = wcsstr((LPWSTR)buffer, L"PENSIL") || wcsstr((LPWSTR)buffer, L"Pensil"); + NtFreeVirtualMemory(NtCurrentProcess(), &buffer, &info.AllocationSize.LowPart, MEM_RELEASE); + return ret; +} +#endif // if 0 +namespace { // unnamed +void SpecialHookDebonosuScenario(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD retn = *(DWORD *)esp_base; + if (*(WORD *)retn == 0xc483) // add esp, $ old Debonosu game + hp->offset = 4; // text in arg1 + else // new Debonosu game + hp->offset = -0x8; // text in ecx instead + //hp->type ^= EXTERN_HOOK; + hp->text_fun = nullptr; + *data = *(DWORD*)(esp_base + hp->offset); + *len = ::strlen((char*)*data); + *split = FIXED_SPLIT_VALUE; +} +bool InsertDebonosuScenarioHook() +{ + DWORD fun; + if (!GetFunctionAddr("lstrcatA", &fun, 0, 0, 0)) { + ConsoleOutput("vnreng:Debonosu: failed to find lstrcatA"); + return false; + } + DWORD addr = Util::FindImportEntry(module_base_, fun); + if (!addr) { + ConsoleOutput("vnreng:Debonosu: lstrcatA is not called"); + return false; + } + DWORD search = 0x15ff | (addr << 16); // jichi 10/20/2014: call dword ptr ds + addr >>= 16; + for (DWORD i = module_base_; i < module_limit_ - 4; i++) + if (*(DWORD *)i == search && + *(WORD *)(i + 4) == addr && // call dword ptr lstrcatA + *(BYTE *)(i - 5) == 0x68) { // push $ + DWORD push = *(DWORD *)(i - 4); + for (DWORD j = i + 6, k = j + 0x10; j < k; j++) + if (*(BYTE *)j == 0xb8 && + *(DWORD *)(j + 1) == push) + if (DWORD hook_addr = SafeFindEntryAligned(i, 0x200)) { + HookParam hp = {}; + hp.address = hook_addr; + hp.text_fun = SpecialHookDebonosuScenario; + //hp.type = USING_STRING; + hp.type = USING_STRING|NO_CONTEXT|USING_SPLIT|FIXING_SPLIT; // there is only one thread + ConsoleOutput("vnreng: INSERT Debonosu"); + NewHook(hp, "Debonosu"); + //RegisterEngineType(ENGINE_DEBONOSU); + ConsoleOutput("vnreng:Debonosu: disable GDI+ hooks"); + DisableGDIPlusHooks(); + return true; + } + } + + ConsoleOutput("vnreng:Debonosu: failed"); + //ConsoleOutput("Unknown Debonosu engine."); + return false; +} +void SpecialHookDebonosuName(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD text = regof(ecx, esp_base); + if (!text) + return; + *data = text; + *len = ::strlen((LPCSTR)text); + *split = FIXED_SPLIT_VALUE << 1; +} +bool InsertDebonosuNameHook() +{ + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng:Silkys: failed to get memory range"); + return false; + } + const BYTE bytes[] = { + // 0032f659 32c0 xor al,al + // 0032f65b 5b pop ebx + // 0032f65c 8be5 mov esp,ebp + // 0032f65e 5d pop ebp + // 0032f65f c3 retn + 0x55, // 0032f660 55 push ebp ; jichi: name text in ecx, which could be zero though + 0x8b,0xec, // 0032f661 8bec mov ebp,esp + 0x81,0xec, XX4, // 0032f663 81ec 2c080000 sub esp,0x82c + 0x8b,0x45, 0x08, // 0032f669 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + 0x53, // 0032f66c 53 push ebx + 0x56, // 0032f66d 56 push esi + 0x8b,0xf1, // 0032f66e 8bf1 mov esi,ecx + 0x85,0xc0, // 0032f670 85c0 test eax,eax + 0x8d,0x4d, 0xf0, // 0032f672 8d4d f0 lea ecx,dword ptr ss:[ebp-0x10] + 0x0f,0x45,0xc8, // 0032f675 0f45c8 cmovne ecx,eax + 0x57 // 0032f678 57 push edi + }; + ULONG addr = MemDbg::matchBytes(bytes, sizeof(bytes), startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:DebonosuName: pattern NOT FOUND"); + return false; + } + HookParam hp = {}; + hp.address = addr; + hp.text_fun = SpecialHookDebonosuName; + //hp.type = USING_STRING; + hp.type = USING_STRING|NO_CONTEXT|USING_SPLIT; //|FIXING_SPLIT; // there is only one thread + ConsoleOutput("vnreng: INSERT DebonosuName"); + NewHook(hp, "DebonosuName"); + return true; +} + +} // unnamed namespace + +bool InsertDebonosuHook() +{ + bool ok = InsertDebonosuScenarioHook(); + if (ok) + InsertDebonosuNameHook(); + return ok; +} + +/* 7/8/2014: The engine name is supposed to be: AoiGameSystem Engine + * See: http://capita.tistory.com/m/post/205 + * + * BUNNYBLACK Trial2 (SystemAoi4) + * baseaddr: 0x01d0000 + * + * 1002472e cc int3 + * 1002472f cc int3 + * 10024730 55 push ebp ; jichi: hook here + * 10024731 8bec mov ebp,esp + * 10024733 51 push ecx + * 10024734 c745 fc 00000000 mov dword ptr ss:[ebp-0x4],0x0 + * 1002473b 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 1002473e 0fb708 movzx ecx,word ptr ds:[eax] + * 10024741 85c9 test ecx,ecx + * 10024743 74 34 je short _8.10024779 + * 10024745 6a 00 push 0x0 + * 10024747 6a 00 push 0x0 + * 10024749 6a 01 push 0x1 + * 1002474b 8b55 14 mov edx,dword ptr ss:[ebp+0x14] + * 1002474e 52 push edx + * 1002474f 0fb645 10 movzx eax,byte ptr ss:[ebp+0x10] + * 10024753 50 push eax + * 10024754 0fb74d 0c movzx ecx,word ptr ss:[ebp+0xc] + * 10024758 51 push ecx + * 10024759 8b55 08 mov edx,dword ptr ss:[ebp+0x8] + * 1002475c 52 push edx + * 1002475d e8 8eddffff call _8.100224f0 + * 10024762 83c4 1c add esp,0x1c + * 10024765 8945 fc mov dword ptr ss:[ebp-0x4],eax + * 10024768 8b45 1c mov eax,dword ptr ss:[ebp+0x1c] + * 1002476b 50 push eax + * 1002476c 8b4d 18 mov ecx,dword ptr ss:[ebp+0x18] + * 1002476f 51 push ecx + * 10024770 8b55 fc mov edx,dword ptr ss:[ebp-0x4] + * 10024773 52 push edx + * 10024774 e8 77c6ffff call _8.10020df0 + * 10024779 8b45 fc mov eax,dword ptr ss:[ebp-0x4] + * 1002477c 8be5 mov esp,ebp + * 1002477e 5d pop ebp + * 1002477f c2 1800 retn 0x18 + * 10024782 cc int3 + * 10024783 cc int3 + * 10024784 cc int3 + * + * 2/12/2015 jichi: SystemAoi5 + * + * Note that BUNNYBLACK 3 also has SystemAoi5 version 4.1 + * + * Hooked to PgsvTd.dll for all SystemAoi engine, which contains GDI functions. + * - Old: AoiLib.dll from DrawTextExA + * - SystemAoi4: Aoi4.dll from DrawTextExW + * - SystemAoi5: Aoi5.dll from GetGlyphOutlineW + * + * Logic: + * - Find GDI function (DrawTextExW, etc.) used to paint text in PgsvTd.dll + * - Then search the function call stack, to find where the exe module invoke PgsvTd + * - Finally insert to the call address, and text is on the top of the stack. + * + * Sample hooked call in 悪魔娘�看板料理 Aoi5 + * + * 00B6D085 56 PUSH ESI + * 00B6D086 52 PUSH EDX + * 00B6D087 51 PUSH ECX + * 00B6D088 68 9E630000 PUSH 0x639E + * 00B6D08D 50 PUSH EAX + * 00B6D08E FF15 54D0BC00 CALL DWORD PTR DS:[0xBCD054] ; _12.0039E890, jichi: hook here + * 00B6D094 8B57 20 MOV EDX,DWORD PTR DS:[EDI+0x20] + * 00B6D097 89049A MOV DWORD PTR DS:[EDX+EBX*4],EAX + * 00B6D09A 8B4F 20 MOV ECX,DWORD PTR DS:[EDI+0x20] + * 00B6D09D 8B1499 MOV EDX,DWORD PTR DS:[ECX+EBX*4] + * 00B6D0A0 8D85 50FDFFFF LEA EAX,DWORD PTR SS:[EBP-0x2B0] + * 00B6D0A6 50 PUSH EAX + * 00B6D0A7 52 PUSH EDX + * 00B6D0A8 FF15 18D0BC00 CALL DWORD PTR DS:[0xBCD018] ; _12.003A14A0 + * + * Special hook is needed, since the utf16 text is like this: + * [f9S30e0u] が、それ�人間相手�話�� */ +namespace { // unnamed +void SpecialHookSystemAoi(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + *split = 0; // 8/3/2014 jichi: split is zero, so return address is used as split + if (hp->type & USING_UNICODE) { + LPCWSTR wcs = (LPWSTR)argof(1, esp_base); // jichi: text on the top of the stack + size_t size = ::wcslen(wcs); + for (DWORD i = 0; i < size; i++) + if (wcs[i] == L'>' || wcs[i] == L']') { // skip leading ] for scenario and > for name threads + i++; + if (wcs[i] == 0x3000) // \u3000 + i++; + *data = (DWORD)(wcs + i); + size -= i; + *len = size * 2; // * 2 for wstring + return; + } + } else { + LPCSTR cs = (LPCSTR)argof(1, esp_base); // jichi: text on the top of the stack + size_t size = ::strlen(cs); + for (DWORD i = 0; i < size; i++) + if (cs[i] == '>' || cs[i] == ']') { + i++; + if ((unsigned char)cs[i] == 0x81 && cs[i+1] == 0x40) // \u3000 + i += 2; + *data = (DWORD)(cs + i); + size -= i; + *len = size; + return; + } + } +} + +int GetSystemAoiVersion() // return result is cached +{ + static int ret = 0; + if (!ret) { + if (IthCheckFile(L"Aoi4.dll")) + ret = 4; + else if (IthCheckFile(L"Aoi5.dll")) + ret = 5; + else if (IthCheckFile(L"Aoi6.dll")) // not exist yet, for future version + ret = 6; + else if (IthCheckFile(L"Aoi7.dll")) // not exist yet, for future version + ret = 7; + else // AoiLib.dll, etc + ret = 3; + } + return ret; +} + +bool InsertSystemAoiDynamicHook(LPVOID addr, DWORD frame, DWORD stack) +{ + int version = GetSystemAoiVersion(); + bool utf16 = true; + if (addr == ::DrawTextExA) // < 4 + utf16 = false; + if (addr == ::DrawTextExW) // 4~5 + ; // pass + else if (addr == ::GetGlyphOutlineW && version >= 5) + ; // pass + else + return false; + + DWORD high, low; + Util::GetCodeRange(module_base_, &low, &high); + + // jichi 2/15/2015: Traverse the stack to dynamically find the ancestor call from the main module + const DWORD stop = (stack & 0xffff0000) + 0x10000; // range to traverse the stack + for (DWORD i = stack; i < stop; i += 4) { + DWORD k = *(DWORD *)i; + if (k > low && k < high && // jichi: if the stack address falls into the code region of the main exe module + ((*(WORD *)(k - 6) == 0x15ff) || *(BYTE *)(k - 5) == 0xe8)) { // jichi 10/20/2014: call dword ptr ds + + HookParam hp = {}; + hp.offset = 0x4; // target text is on the top of the stack + hp.text_fun = SpecialHookSystemAoi; // need to remove garbage + hp.type = utf16 ? USING_UNICODE : USING_STRING; + + i = *(DWORD *)(k - 4); // get function call address + if (*(DWORD *)(k - 5) == 0xe8) // short jump + hp.address = i + k; + else + hp.address = *(DWORD *)i; // jichi: long jump, this is what is happening in Aoi5 + //NewHook(hp, "SofthouseChara"); + //GROWL_DWORD(hp.address); // BUNNYBLACK: 0x10024730, base 0x01d0000 + if (hp.address) { + ConsoleOutput("vnreng: INSERT SystemAoi"); + if (addr == ::GetGlyphOutlineW) + NewHook(hp, "SystemAoi2"); // jichi 2/12/2015 + else + NewHook(hp, "SystemAoi"); // jichi 7/8/2014: renamed, see: ja.wikipedia.org/wiki/ソフトハウスキャラ + ConsoleOutput("vnreng:SystemAoi: disable GDI hooks"); + DisableGDIHooks(); + } else + ConsoleOutput("vnreng: failed to detect SystemAoi"); + //RegisterEngineType(ENGINE_SOFTHOUSE); + return true; + } + } + ConsoleOutput("vnreng:SystemAoi: failed"); + return true; // jichi 12/25/2013: return true +} + +bool InsertSystemAoiDynamic() +{ + ConsoleOutput("vnreng: DYNAMIC SystemAoi"); + //ConsoleOutput("Probably SoftHouseChara. Wait for text."); + trigger_fun_ = InsertSystemAoiDynamicHook; + SwitchTrigger(true); + return true; +} + +ULONG findAoiProc(HMODULE hModule, LPCSTR functionName, int minParamNum = 0, int maxParamNum = 10) +{ + for (int i = minParamNum; i < maxParamNum; i++) { + std::string sig; // function signature name, such as _AgsSpriteCreateText@20 + sig.push_back('_'); + sig += functionName; + sig.push_back('@'); + sig += std::to_string(4ll * i); + if (auto proc = ::GetProcAddress(hModule, sig.c_str())) + return (ULONG)proc; + } + return 0; +} + +// jichi 7/26/2015: Backport logic in vnragent to vnrhook +bool InsertSystemAoiStatic(HMODULE hModule, bool wideChar) // attach scenario +{ + ULONG addr = findAoiProc(hModule, "AgsSpriteCreateText", 1); + if (!addr) { + ConsoleOutput("vnreng:SystemAoiStatic: function found"); + return false; + } + HookParam hp = {}; + hp.address = addr; + hp.offset = 4 * 1; // arg1 + //hp.split = 4 * 2; // arg2 + hp.text_fun = SpecialHookSystemAoi; + hp.type = wideChar ? USING_UNICODE : USING_STRING; + //hp.type |= NO_CONTEXT|USING_SPLIT|SPLIT_INDIRECT; + ConsoleOutput("vnreng: INSERT static SystemAoi"); + if (wideChar) + NewHook(hp, "SystemAoiW"); + else + NewHook(hp, "SystemAoiA"); + ConsoleOutput("vnreng:SystemAoiStatic: disable GDI hooks"); + DisableGDIHooks(); + return true; +} +} // unnamed namespace + +bool InsertSystemAoiHook() // this function always returns true +{ + HMODULE hModule = ::GetModuleHandleA("Ags.dll"); + bool wideChar = true; + if (hModule) // Aoi <= 3 + wideChar = false; + else { // Aoi >= 4 + hModule = ::GetModuleHandleA("Ags5.dll"); + if (!hModule) + hModule = ::GetModuleHandleA("Ags4.dll"); + } + return hModule && InsertSystemAoiStatic(hModule, wideChar) + || InsertSystemAoiDynamic(); +} + +static void SpecialHookCaramelBox(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD reg_ecx = *(DWORD*)(esp_base + hp->offset); + BYTE *ptr = (BYTE *)reg_ecx; + buffer_index = 0; + while (ptr[0]) + if (ptr[0] == 0x28) { // Furigana format: (Kanji,Furi) + ptr++; + while (ptr[0]!=0x2c) //Copy Kanji + text_buffer[buffer_index++] = *ptr++; + while (ptr[0]!=0x29) // Skip Furi + ptr++; + ptr++; + } else if (ptr[0] == 0x5c) + ptr +=2; + else { + text_buffer[buffer_index++] = ptr[0]; + if (LeadByteTable[ptr[0]] == 2) { + ptr++; + text_buffer[buffer_index++] = ptr[0]; + } + ptr++; + } + + *len = buffer_index; + *data = (DWORD)text_buffer; + *split = 0; // 8/3/2014 jichi: use return address as split +} +// jichi 10/1/2013: Change return type to bool +bool InsertCaramelBoxHook() +{ + union { DWORD i; BYTE* pb; WORD* pw; DWORD *pd; }; + DWORD reg = -1; + for (i = module_base_ + 0x1000; i < module_limit_ - 4; i++) { + if (*pd == 0x7ff3d) // cmp eax, 7ff + reg = 0; + else if ((*pd & 0xfffff8fc) == 0x07fff880) // cmp reg, 7ff + reg = pb[1] & 0x7; + + if (reg == -1) + continue; + + DWORD flag = 0; + if (*(pb - 6) == 3) { //add reg, [ebp+$disp_32] + if (*(pb - 5) == (0x85 | (reg << 3))) + flag = 1; + } else if (*(pb - 3) == 3) { // add reg, [ebp+$disp_8] + if (*(pb - 2) == (0x45 | (reg << 3))) + flag = 1; + } else if (*(pb - 2) == 3) { // add reg, reg + if (((*(pb - 1) >> 3) & 7)== reg) + flag = 1; + } + reg = -1; + if (flag) { + for (DWORD j = i, k = i - 0x100; j > k; j--) { + if ((*(DWORD *)j & 0xffff00ff) == 0x1000b8) { // mov eax,10?? + HookParam hp = {}; + hp.address = j & ~0xf; + hp.text_fun = SpecialHookCaramelBox; + hp.type = USING_STRING; + for (i &= ~0xffff; i < module_limit_ - 4; i++) + if (pb[0] == 0xe8) { + pb++; + if (pd[0] + i + 4 == hp.address) { + pb += 4; + if ((pd[0] & 0xffffff) == 0x04c483) + hp.offset = 4; + else hp.offset = -0xc; + break; + } + } + + if (hp.offset == 0) { + ConsoleOutput("vnreng:CaramelBox: failed, zero off"); + return false; + } + ConsoleOutput("vnreng: INSERT CaramelBox"); + NewHook(hp, "CaramelBox"); + //RegisterEngineType(ENGINE_CARAMEL); + return true; + } + } + } + } + ConsoleOutput("vnreng:CaramelBox: failed"); + return false; +//_unknown_engine: + //ConsoleOutput("Unknown CarmelBox engine."); +} + +/** + * jichi 10/12/2014 + * P.S.: Another approach + * See: http://tieba.baidu.com/p/2425786155 + * Quote: + * I guess this post should go in here. I got sick of AGTH throwing a fit when entering the menus in Wolf RPG games, so I did some debugging. This is tested and working properly with lots of games. If you find one that isn't covered then please PM me and I'll look into it. + * + * Wolf RPG H-code - Use whichever closest matches your Game.exe + * /HBN*0@454C6C (2010/10/09 : 2,344KB : v1.31) + * /HBN*0@46BA03 (2011/11/22 : 2,700KB : v2.01) + * /HBN*0@470CEA (2012/05/07 : 3,020KB : v2.02) + * /HBN*0@470D5A (2012/06/10 : 3,020KB : v2.02a) + * + * ith_p.cc:Ith::parseHookCode: enter: code = "/HBN*0@470CEA" + * - addr: 4656362 , + * - length_offset: 1 + * - type: 1032 = 0x408 + * + * Use /HB instead of /HBN if you want to split dialogue text and menu text into separate threads. + * Also set the repetition trace parameters in AGTH higher or it won't work properly with text-heavy menus. 64 x 16 seems to work fine. + * + * Issues: + * AGTH still causes a bit of lag when translating menus if you have a lot of skills or items. + * Using ITH avoids this problem, but it sometimes has issues with repetition detection which can be fixed by quickly deselecting and reselecting the game window; Personally I find this preferable to menu and battle slowdown that AGTH sometimes causes, but then my PC is pretty slow so you might not have that problem. + * + * Minimising the AGTH/ITH window generally makes the game run a bit smoother as windows doesn't need to keep scrolling the text box as new text is added. + * + * RPG Maker VX H-code: + * Most games are detected automatically and if not then by using the AGTH /X or /X2 or /X3 parameters. + * + * Games that use TRGSSX.dll may have issues with detection (especially with ITH). + * If TRGSSX.dll is included with the game then this code should work: + * /HQN@D3CF:TRGSSX.dll + * + * With this code, using AGTH to start the process will not work. You must start the game normally and then hook the process afterwards. + * ITH has this functionality built into the interface. AGTH requires the /PN command line argument, for example: + * agth /PNGame.exe /HQN@D3CF:TRGSSX.dll /C + * + * Again, drop the N to split dialogue and menu text into separate threads. + */ +namespace { // WolfRPG +// jichi 10/13/2013: restored +bool InsertOldWolfHook() +{ + //__asm int 3 // debug + // jichi 10/12/2013: + // Step 1: find the address of GetTextMetricsA + // Step 2: find where this function is called + // Step 3: search "sub esp, XX" after where it is called + enum { sub_esp = 0xec81 }; // jichi: caller pattern: sub esp = 0x81,0xec + if (DWORD c1 = Util::FindCallAndEntryAbs((DWORD)GetTextMetricsA, module_limit_ - module_base_, module_base_, sub_esp)) + if (DWORD c2 = Util::FindCallOrJmpRel(c1, module_limit_ - module_base_, module_base_, 0)) { + union { + DWORD i; + WORD *k; + }; + DWORD j; + for (i = c2 - 0x100, j = c2 - 0x400; i > j; i--) + if (*k == 0xec83) { // jichi 10/12/2013: 83 EC XX sub esp, XX See: http://lists.cs.uiuc.edu/pipermail/llvm-commits/Week-of-Mon-20120312.txt + HookParam hp = {}; + hp.address = i; + hp.offset = -0xc; + hp.split = -0x18; + hp.type = DATA_INDIRECT|USING_SPLIT; + hp.length_offset = 1; + //GROWL_DWORD(hp.address); // jichi 6/5/2014: 淫乱勀��フィのRPG = 0x50a400 + ConsoleOutput("vnreng: INSERT WolfRPG"); + NewHook(hp, "WolfRPG"); + return true; + } + } + + //ConsoleOutput("Unknown WolfRPG engine."); + ConsoleOutput("vnreng:WolfRPG: failed"); + return false; +} + + +struct TextListElement // ecx, this structure saved a list of element +{ + DWORD flag1; // should be zero when text is valid + LPSTR text; + DWORD flag2; + DWORD flag3; + DWORD flag4; + int size, + capacity; // 0xe8, capacity of the data including \0 + + bool isValid() const + { + return flag1 == 0 && flag2 == 0 && flag3 == 0 && flag4 == 0 + && size > 0 && size < capacity + && !::IsBadReadPtr(text, capacity) && size == ::strlen(text); + //&& (quint8)*text > 127; + } +}; +void SpecialHookWolf2(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + auto self = (TextListElement *)regof(ecx, esp_base); // ecx is actually a list of element + if (self && self->isValid()) { + *data = (DWORD)self->text; + *len = self->size; + } +} + +#if 0 +// jichi 6/11/2015: See embed translation source code +bool InsertWolf2Hook() +{ + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng:WolfRPG2: failed to get memory range"); + return false; + } + ULONG addr = MemDbg::findCallerAddressAfterInt3((ULONG)::CharNextA, startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:WolfRPG2: failed to find target function"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.text_fun = SpecialHookWolf2; + hp.type = USING_STRING; + ConsoleOutput("vnreng: INSERT WolfRPG2"); + NewHook(hp, "WolfRPG2"); + return true; +} +#endif // 0 + +} // WolfRPG namespace + +bool InsertWolfHook() +{ + return InsertOldWolfHook(); +} + +bool InsertIGSDynamicHook(LPVOID addr, DWORD frame, DWORD stack) +{ + if (addr != GetGlyphOutlineW) + return false; + DWORD i; + i = *(DWORD *)frame; + i = *(DWORD *)(i+4); + DWORD j, k; + if (SafeFillRange(L"mscorlib.ni.dll", &j, &k)) { + while (*(BYTE *)i != 0xe8) + i++; + DWORD t = *(DWORD *)(i + 1) + i + 5; + if (t>j && t0x2000). The remain address space should be several MBs in size and + can be examined in reasonable time(less than 0.1s for P8400 Win7x64). + Characteristic sequence is 0F B7 44 50 0C, stands for movzx eax, word ptr [edx*2 + eax + C]. + Obviously this instruction extracts one unicode character from a string. + A main shortcoming is that the code is not generated if it hasn't been used yet. + So if you are in title screen this approach will fail. + +********************************************************************************************/ +namespace { // unnamed +MEMORY_WORKING_SET_LIST *GetWorkingSet() +{ + DWORD len,retl; + NTSTATUS status; + LPVOID buffer = 0; + len = 0x4000; + status = NtAllocateVirtualMemory(NtCurrentProcess(), &buffer, 0, &len, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + if (!NT_SUCCESS(status)) return 0; + status = NtQueryVirtualMemory(NtCurrentProcess(), 0, MemoryWorkingSetList, buffer, len, &retl); + if (status == STATUS_INFO_LENGTH_MISMATCH) { + len = *(DWORD*)buffer; + len = ((len << 2) & 0xfffff000) + 0x4000; + retl = 0; + NtFreeVirtualMemory(NtCurrentProcess(), &buffer, &retl, MEM_RELEASE); + buffer = 0; + status = NtAllocateVirtualMemory(NtCurrentProcess(), &buffer, 0, &len, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + if (!NT_SUCCESS(status)) return 0; + status = NtQueryVirtualMemory(NtCurrentProcess(), 0, MemoryWorkingSetList, buffer, len, &retl); + if (!NT_SUCCESS(status)) return 0; + return (MEMORY_WORKING_SET_LIST*)buffer; + } else { + retl = 0; + NtFreeVirtualMemory(NtCurrentProcess(), &buffer, &retl, MEM_RELEASE); + return 0; + } + +} +typedef struct _NSTRING +{ + PVOID vfTable; + DWORD lenWithNull; + DWORD lenWithoutNull; + WCHAR str[1]; +} NSTRING; + +// qsort correctly identifies overflow. +int cmp(const void * a, const void * b) +{ return *(int*)a - *(int*)b; } + +void SpecialHookAB2Try(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + //DWORD test = *(DWORD*)(esp_base - 0x10); + DWORD edx = regof(edx, esp_base); + if (edx != 0) + return; + + //NSTRING *s = *(NSTRING **)(esp_base - 8); + if (const NSTRING *s = (NSTRING *)regof(eax, esp_base)) { + *len = s->lenWithoutNull << 1; + *data = (DWORD)s->str; + //*split = 0; + *split = FIXED_SPLIT_VALUE; // 8/3/2014 jichi: change to single threads + } +} + +BOOL FindCharacteristInstruction(MEMORY_WORKING_SET_LIST *list) +{ + DWORD base, size; + DWORD i, j, k, addr, retl; + NTSTATUS status; + ::qsort(&list->WorkingSetList, list->NumberOfPages, 4, cmp); + base = list->WorkingSetList[0]; + size = 0x1000; + for (i = 1; i < list->NumberOfPages; i++) { + if ((list->WorkingSetList[i] & 2) == 0) + continue; + if (list->WorkingSetList[i] >> 31) + break; + if (base + size == list->WorkingSetList[i]) + size += 0x1000; + else { + if (size > 0x2000) { + addr = base & ~0xfff; + status = NtQueryVirtualMemory(NtCurrentProcess(),(PVOID)addr, + MemorySectionName,text_buffer_prev,0x1000,&retl); + if (!NT_SUCCESS(status)) { + k = addr + size - 4; + for (j = addr; j < k; j++) { + if (*(DWORD*)j == 0x5044b70f) { + if (*(WORD*)(j + 4) == 0x890c) { // movzx eax, word ptr [edx*2 + eax + 0xC]; wchar = string[i]; + HookParam hp = {}; + hp.address = j; + hp.text_fun = SpecialHookAB2Try; + hp.type = USING_STRING|NO_CONTEXT|USING_UNICODE; + ConsoleOutput("vnreng: INSERT AB2Try"); + NewHook(hp, "AB2Try"); + //ConsoleOutput("Please adjust text speed to fastest/immediate."); + //RegisterEngineType(ENGINE_AB2T); + return TRUE; + } + } + } + } + } + size = 0x1000; + base = list->WorkingSetList[i]; + } + } + return FALSE; +} +} // unnamed namespace +bool InsertAB2TryHook() +{ + MEMORY_WORKING_SET_LIST *list = GetWorkingSet(); + if (!list) { + ConsoleOutput("vnreng:AB2Try: cannot find working list"); + return false; + } + bool ret = FindCharacteristInstruction(list); + if (ret) + ConsoleOutput("vnreng:AB2Try: found characteristic sequence"); + else + ConsoleOutput("vnreng:AB2Try: cannot find characteristic sequence"); + //L"Make sure you have start the game and have seen some text on the screen."); + DWORD size = 0; + NtFreeVirtualMemory(NtCurrentProcess(), (PVOID *)&list, &size, MEM_RELEASE); + return ret; +} + +/******************************************************************************************** +C4 hook: (Contributed by Stomp) + Game folder contains C4.EXE or XEX.EXE. +********************************************************************************************/ +bool InsertC4Hook() +{ + const BYTE bytes[] = { 0x8a, 0x10, 0x40, 0x80, 0xfa, 0x5f, 0x88, 0x15 }; + //enum { addr_offset = 0 }; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + if (!addr) { + ConsoleOutput("vnreng:C4: pattern not found"); + return false; + } + HookParam hp = {}; + hp.address = addr; + hp.offset = -0x8; + hp.type = DATA_INDIRECT|NO_CONTEXT; + hp.length_offset = 1; + ConsoleOutput("vnreng: INSERT C4"); + NewHook(hp, "C4"); + //RegisterEngineType(ENGINE_C4); + return true; +} + +/** 1/18/2015 jichi Add new WillPlus + * The old hook no longer works for new game. + * Sample game: [150129] [honeybee] RE:BIRTHDAY SONG + * + * Note, WillPlus engine is migrating to UTF16 using GetGlyphOutlineW such as: + * [141218] [Guily] 手�めにされる九人の堕女 + * This engine does not work for GetGlyphOutlineW, which, however, does not need a H-code. + * + * See: http://sakuradite.com/topic/615 + * + * There WillPlus games have many hookable threads. + * But it is kind of important to find the best one. + * + * By inserting hw point: + * - There is a clean text thread with fixed memory address. + * However, it cannot extract character name like GetGlyphOutlineA. + * - This is a non-clean text thread, but it contains garbage such as %LC. + * + * By backtracking from GetGlyphOutlineA: + * - GetGlyphOutlineA sometimes can extract all text, sometimes not. + * - There are two GetGlyphOutlineA functions. + * Both of them are called statically in the same function. + * That function is hooked. + * + * Hooked function: + * 0041820c cc int3 + * 0041820d cc int3 + * 0041820e cc int3 + * 0041820f cc int3 + * 00418210 81ec b4000000 sub esp,0xb4 + * 00418216 8b8424 c4000000 mov eax,dword ptr ss:[esp+0xc4] + * 0041821d 53 push ebx + * 0041821e 8b9c24 d0000000 mov ebx,dword ptr ss:[esp+0xd0] + * 00418225 55 push ebp + * 00418226 33ed xor ebp,ebp + * 00418228 56 push esi + * 00418229 8bb424 dc000000 mov esi,dword ptr ss:[esp+0xdc] + * 00418230 03c3 add eax,ebx + * 00418232 57 push edi + * 00418233 8bbc24 d8000000 mov edi,dword ptr ss:[esp+0xd8] + * 0041823a 896c24 14 mov dword ptr ss:[esp+0x14],ebp + * 0041823e 894424 4c mov dword ptr ss:[esp+0x4c],eax + * 00418242 896c24 24 mov dword ptr ss:[esp+0x24],ebp + * 00418246 39ac24 e8000000 cmp dword ptr ss:[esp+0xe8],ebp + * 0041824d 75 29 jnz short .00418278 + * 0041824f c74424 24 010000>mov dword ptr ss:[esp+0x24],0x1 + * + * ... + * + * 00418400 56 push esi + * 00418401 52 push edx + * 00418402 ff15 64c04b00 call dword ptr ds:[0x4bc064] ; gdi32.getglyphoutlinea + * 00418408 8bf8 mov edi,eax + * + * The old WillPlus engine can also be inserted to the new games. + * But it has no effects. + * + * A split value is used to get saving message out. + * + * Runtime stack for the scenario thread: + * 0012d9ec 00417371 return to .00417371 from .00418210 + * 0012d9f0 00000003 1 + * 0012d9f4 00000000 2 + * 0012d9f8 00000130 3 + * 0012d9fc 0000001a 4 + * 0012da00 0000000b 5 + * 0012da04 00000016 6 + * 0012da08 0092fc00 .0092fc00 ms gothic ; jichi: here's font + * 0012da0c 00500aa0 .00500aa0 shun ; jichi: text is here in arg8 + * 0012da10 0217dcc0 + * + * Runtime stack for name: + * 0012d9ec 00417371 return to .00417371 from .00418210 + * 0012d9f0 00000003 + * 0012d9f4 00000000 + * 0012d9f8 00000130 + * 0012d9fc 0000001a + * 0012da00 0000000b + * 0012da04 00000016 + * 0012da08 0092fc00 .0092fc00 + * 0012da0c 00500aa0 .00500aa0 + * 0012da10 0217dcc0 + * 0012da14 00000000 + * 0012da18 00000000 + * + * Runtime stack for non-dialog scenario text. + * 0012e5bc 00438c1b return to .00438c1b from .00418210 + * 0012e5c0 00000006 + * 0012e5c4 00000000 + * 0012e5c8 000001ae + * 0012e5cc 000000c8 + * 0012e5d0 0000000c + * 0012e5d4 00000018 + * 0012e5d8 0092fc00 .0092fc00 + * 0012e5dc 0012e628 + * 0012e5e0 0b0d0020 + * 0012e5e4 004fda98 .004fda98 + * + * Runtime stack for saving message + * 0012ed44 00426003 return to .00426003 from .00418210 + * 0012ed48 000003c7 + * 0012ed4c 00000000 + * 0012ed50 000000d8 + * 0012ed54 0000012f + * 0012ed58 00000008 + * 0012ed5c 00000010 + * 0012ed60 0092fc00 .0092fc00 + * 0012ed64 00951d88 ascii "2015/01/18" + */ + +namespace { // unnamed +#if 0 +static bool InsertWillPlusHook2() // jichi 1/18/2015: Add new hook +{ + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng:WillPlus2: failed to get memory range"); + return false; + } + + // The following won't work after inserting WillPlus1, which also produces int3 + //ULONG addr = MemDbg::findCallerAddressAfterInt3((DWORD)::GetGlyphOutlineA, startAddress, stopAddress); + + // 00418210 81ec b4000000 sub esp,0xb4 + enum { sub_esp = 0xec81 }; // jichi: caller pattern: sub esp = 0x81,0xec + ULONG addr = MemDbg::findCallerAddress((ULONG)::GetGlyphOutlineA, sub_esp, startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:WillPlus2: could not find caller of GetGlyphOutlineA"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.offset = 4 * 8; // arg8, address of text + + // Strategy 1: Use caller's address as split + //hp.type = USING_STRING; // merge different scenario threads + + // Strategy 2: use arg1 as split + hp.type = USING_STRING|NO_CONTEXT|USING_SPLIT; // merge different scenario threads + hp.split = 4 * 1; // arg1 as split to get rid of saving message + + // Strategy 3: merge all threads + //hp.type = USING_STRING|NO_CONTEXT|FIXING_SPLIT; // merge different scenario threads + + ConsoleOutput("vnreng: INSERT WillPlus2"); + NewHook(hp, "WillPlus2"); + return true; +} +#endif // 0 + +void SpecialHookWillPlus(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + //static DWORD detect_offset; // jichi 1/18/2015: this makes sure it only runs once + //if (detect_offset) + // return; + DWORD i,l; + union { + DWORD retn; + WORD *pw; + BYTE *pb; + }; + retn = *(DWORD *)esp_base; // jichi 1/18/2015: dynamically find function return address + i = 0; + while (*pw != 0xc483) { // add esp, $ + l = ::disasm(pb); + if (++i == 5) + //ConsoleOutput("Fail to detect offset."); + break; + retn += l; + } + // jichi 2/11/2015: Check baddaddr which might crash the game on Windows XP. + if (*pw == 0xc483 && !::IsBadReadPtr((LPCVOID)(pb + 2), 1) && !::IsBadReadPtr((LPCVOID)(*(pb + 2) - 8), 1)) { + ConsoleOutput("vnreng: WillPlus1 pattern found"); + // jichi 1/18/2015: + // By studying [honeybee] RE:BIRTHDAY SONG, it seems the scenario text is at fixed address + // This offset might be used to find fixed address + // However, this method cannot extract character name like GetGlyphOutlineA + hp->offset = *(pb + 2) - 8; + + // Still extract the first text + //hp->type ^= EXTERN_HOOK; + char *str = *(char **)(esp_base + hp->offset); + *data = (DWORD)str; + *len = ::strlen(str); + *split = 0; // 8/3/2014 jichi: use return address as split + + } else { // jichi 1/19/2015: Try willplus2 + ConsoleOutput("vnreng: WillPlus1 pattern not found, try WillPlus2 instead"); + hp->offset = 4 * 8; // arg8, address of text + hp->type = USING_STRING|NO_CONTEXT|USING_SPLIT; // merge different scenario threads + hp->split = 4 * 1; // arg1 as split to get rid of saving message + // The first text is skipped here + //char *str = *(char **)(esp_base + hp->offset); + //*data = (DWORD)str; + //*len = ::strlen(str); + } + hp->text_fun = nullptr; // stop using text_fun any more + //detect_offset = 1; +} + +// Although the new hook also works for the old game, the old hook is still used by default for compatibility +bool InsertOldWillPlusHook() +{ + //__debugbreak(); + enum { sub_esp = 0xec81 }; // jichi: caller pattern: sub esp = 0x81,0xec byte + ULONG addr = MemDbg::findCallerAddress((ULONG)::GetGlyphOutlineA, sub_esp, module_base_, module_limit_); + if (!addr) { + ConsoleOutput("vnreng:WillPlus: function call not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.text_fun = SpecialHookWillPlus; + hp.type = USING_STRING; + ConsoleOutput("vnreng: INSERT WillPlus"); + NewHook(hp, "WillPlus"); + //RegisterEngineType(ENGINE_WILLPLUS); + return true; +} + +const char *_willplus_trim_a(const char *text, size_t *size) +{ + int textSize = ::strlen(text); + int prefix = 0; + if (text[0] == '%') { + while (prefix < textSize - 1 && text[prefix] == '%' && ::isupper(text[prefix+1])) { + prefix += 2; + while (::isupper(text[prefix])) + prefix++; + } + } + { + int pos = textSize; + for (int i = textSize - 1; i >= prefix; i--) { + char ch = text[i]; + if (::isupper(ch)) + ; + else if (ch == '%') + pos = i; + else + break; + } + int suffix = textSize - pos; + if (size) + *size = textSize - prefix - suffix; + } + return text + prefix; +} + +const wchar_t *_willplus_trim_w(const wchar_t *text, size_t *size) +{ + int textSize = ::wcslen(text); + int prefix = 0; + if (text[0] == '%') { + while (prefix < textSize - 1 && text[prefix] == '%' && ::isupper(text[prefix+1])) { + prefix += 2; + while (::isupper(text[prefix])) + prefix++; + } + } + { + int pos = textSize; + for (int i = textSize - 1; i >= prefix; i--) { + wchar_t ch = text[i]; + if (::isupper(ch)) + ; + else if (ch == '%') + pos = i; + else + break; + } + int suffix = textSize - pos; + if (size) + *size = textSize - prefix - suffix; + } + return text + prefix; +} + +void SpecialHookWillPlusA(DWORD esp_base, HookParam *, BYTE index, DWORD *data, DWORD *split, DWORD *len) +{ + auto text = (LPCSTR)regof(eax, esp_base); + if (!text) + return; + if (index) // index == 1 is name + text -= 1024; + if (!*text) + return; + text = _willplus_trim_a(text, (size_t *)len); + *data = (DWORD)text; + *split = FIXED_SPLIT_VALUE << index; +} + +bool InsertWillPlusAHook() +{ + const BYTE bytes[] = { + 0x81,0xec, 0x14,0x08,0x00,0x00 // 0042B5E0 81EC 14080000 SUB ESP,0x814 ; jichi: text in eax, name in eax - 1024, able to copy + }; + DWORD addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + if (!addr) { + ConsoleOutput("vnreng:WillPlusA: pattern not found"); + return false; + } + HookParam hp = {}; + hp.address = addr; + hp.text_fun = SpecialHookWillPlusA; + hp.type = NO_CONTEXT; + hp.extra_text_count = 1; + hp.filter_fun = NewLineStringFilter; // remove two characters of "\\n" + ConsoleOutput("vnreng: INSERT WillPlusA"); + NewHook(hp, "WillPlusA"); + return true; +} + +void SpecialHookWillPlusW(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + auto text = (LPCWSTR)regof(ecx, esp_base); + if (!text || !*text) + return; + text = _willplus_trim_w(text, (size_t *)len); + *len *= 2; + *data = (DWORD)text; + *split = FIXED_SPLIT_VALUE << hp->user_value; +} + +bool InsertWillPlusWHook() +{ + const BYTE bytes1[] = { // scenario + 0x83,0xc0, 0x20, // 00452b02 83c0 20 add eax,0x20 ; jichi: hook before here, text in ecx + 0x33,0xd2, // 00452b05 33d2 xor edx,edx + 0x8b,0xc1, // 00452b07 8bc1 mov eax,ecx + 0xc7,0x84,0x24, 0xe0,0x01,0x00,0x00, 0x07,0x00,0x00,0x00 // 00452b09 c78424 e0010000 07000000 mov dword ptr ss:[esp+0x1e0],0x7 + // 00452b14 c78424 dc010000 00000000 mov dword ptr ss:[esp+0x1dc],0x0 + }; + const BYTE bytes2[] = { // name + 0x33,0xdb, // 00453521 33db xor ebx,ebx ; jichi: hook here, text in ecx + 0x33,0xd2, // 00453523 33d2 xor edx,edx + 0x8b,0xc1, // 00453525 8bc1 mov eax,ecx + 0xc7,0x84,0x24, 0x88,0x00,0x00,0x00, 0x07,0x00,0x00,0x00 // 00453527 c78424 88000000 07000000 mov dword ptr ss:[esp+0x88],0x7 + // 00453532 899c24 84000000 mov dword ptr ss:[esp+0x84],ebx + }; + const BYTE *bytes[] = {bytes1, bytes2}; + const size_t sizes[] = {sizeof(bytes1), sizeof(bytes2)}; + for (int i = 0; i < 2; i++) { + DWORD addr = MemDbg::findBytes(bytes[i], sizes[i], module_base_, module_limit_); + if (!addr) { + ConsoleOutput("vnreng:WillPlusW: pattern not found"); + return false; + } + HookParam hp = {}; + hp.address = addr; + hp.text_fun = SpecialHookWillPlusW; + hp.type = NO_CONTEXT; + hp.user_value = i; + hp.filter_fun = NewLineWideStringFilter; // remove two characters of "\\n" + ConsoleOutput("vnreng: INSERT WillPlusW"); + NewHook(hp, "WillPlusW"); + } + return true; +} + +} // unnamed namespace + +bool InsertWillPlusHook() +{ + bool ok = InsertOldWillPlusHook(); + ok = InsertWillPlusWHook() || InsertWillPlusAHook() || ok; + return ok; +} + +/** jichi 9/14/2013 + * TanukiSoft (*.tac) + * + * Seems to be broken for new games in 2012 such like となり� + * + * 微少女: /HSN4@004983E0 + * This is the same hook as ITH + * - addr: 4817888 (0x4983e0) + * - text_fun: 0x0 + * - off: 4 + * - type: 1025 (0x401) + * + * 隣り�ぷ�さ� /HSN-8@200FE7:TONARINO.EXE + * - addr: 2101223 (0x200fe7) + * - module: 2343491905 (0x8baed941) + * - off: 4294967284 = 0xfffffff4 = -0xc + * - type: 1089 (0x441) + */ +bool InsertTanukiHook() +{ + ConsoleOutput("vnreng: trying TanukiSoft"); + for (DWORD i = module_base_; i < module_limit_ - 4; i++) + if (*(DWORD *)i == 0x8140) + if (DWORD j = SafeFindEntryAligned(i, 0x400)) { // jichi 9/14/2013: might crash the game without admin priv + //GROWL_DWORD2(i, j); + HookParam hp = {}; + hp.address = j; + hp.offset = 4; + hp.type = USING_STRING | NO_CONTEXT; + ConsoleOutput("vnreng: INSERT TanukiSoft"); + NewHook(hp, "TanukiSoft"); + return true; + } + + //ConsoleOutput("Unknown TanukiSoft engine."); + ConsoleOutput("vnreng:TanukiSoft: failed"); + return false; +} +static void SpecialHookRyokucha(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + const DWORD *base = (const DWORD *)esp_base; + for (DWORD i = 1; i < 5; i++) { + DWORD j = base[i]; + if ((j >> 16) == 0 && (j >> 8)) { + hp->offset = i << 2; + *data = j; + *len = 2; + //hp->type &= ~EXTERN_HOOK; + hp->text_fun = nullptr; + return; + } + } + *len = 0; +} +bool InsertRyokuchaDynamicHook(LPVOID addr, DWORD frame, DWORD stack) +{ + if (addr != ::GetGlyphOutlineA) + return false; + bool flag; + DWORD insert_addr; + __asm + { + mov eax,fs:[0] + mov eax,[eax] + mov eax,[eax] //Step up SEH chain for 2 nodes. + mov ecx,[eax + 0xC] + mov eax,[eax + 4] + add ecx,[ecx - 4] + mov insert_addr,ecx + cmp eax,[ecx + 3] + sete al + mov flag,al + } + if (flag) { + HookParam hp = {}; + hp.address = insert_addr; + hp.length_offset = 1; + hp.text_fun = SpecialHookRyokucha; + hp.type = BIG_ENDIAN; + ConsoleOutput("vnreng: INSERT StudioRyokucha"); + NewHook(hp, "StudioRyokucha"); + return true; + } + //else ConsoleOutput("Unknown Ryokucha engine."); + ConsoleOutput("vnreng:StudioRyokucha: failed"); + return true; +} +void InsertRyokuchaHook() +{ + //ConsoleOutput("Probably Ryokucha. Wait for text."); + trigger_fun_ = InsertRyokuchaDynamicHook; + SwitchTrigger(true); + ConsoleOutput("vnreng: TRIGGER Ryokucha"); +} + +/** + * jichi 5/11/2014: Hook to the beginning of a function + * + * Executable description shows "AVGEngineV2" + * + * Cached wrong text can also be found in GetGlyphOutlineW. + * + * 4/27/2015 old logic: + * 1. find the following location + * 00A78144 66:833C70 00 CMP WORD PTR DS:[EAX+ESI*2],0x0 + * i.e. 0x66833C7000 + * There are several matches, the first one is used. + * 2. find the first push operation after it + * 3. find the function call after push, and hook to it + * The text is in the arg1, which is character by character + * + * But in the new game since ウルスラグ� there the function call is not immediately after 0x66833C7000 any more. + * My own way to find the function to hook is as follows: + * 1. find the following location + * 00A78144 66:833C70 00 CMP WORD PTR DS:[EAX+ESI*2],0x0 + * i.e. 0x66833C7000 + * There are several matches, the first one is used. + * 2. Use Ollydbg to debug step by step until the first function call is encountered + * Then, the text character is directly on the stack + * + * Here's an example of Demonion II (reladdr = 0x18c540): + * The text is displayed character by character. + * sub_58C540 proc near + * arg_0 = dword ptr 8 // LPCSTR with 1 character + * + * 0138C540 /$ 55 PUSH EBP + * 0138C541 |. 8BEC MOV EBP,ESP + * 0138C543 |. 83E4 F8 AND ESP,0xFFFFFFF8 + * 0138C546 |. 8B43 0C MOV EAX,DWORD PTR DS:[EBX+0xC] + * 0138C549 |. 83EC 08 SUB ESP,0x8 + * 0138C54C |. 56 PUSH ESI + * 0138C54D |. 57 PUSH EDI + * 0138C54E |. 85C0 TEST EAX,EAX + * 0138C550 |. 75 04 JNZ SHORT demonion.0138C556 + * 0138C552 |. 33F6 XOR ESI,ESI + * 0138C554 |. EB 18 JMP SHORT demonion.0138C56E + * 0138C556 |> 8B4B 14 MOV ECX,DWORD PTR DS:[EBX+0x14] + * 0138C559 |. 2BC8 SUB ECX,EAX + * 0138C55B |. B8 93244992 MOV EAX,0x92492493 + * 0138C560 |. F7E9 IMUL ECX + * 0138C562 |. 03D1 ADD EDX,ECX + * 0138C564 |. C1FA 04 SAR EDX,0x4 + * 0138C567 |. 8BF2 MOV ESI,EDX + * 0138C569 |. C1EE 1F SHR ESI,0x1F + * 0138C56C |. 03F2 ADD ESI,EDX + * 0138C56E |> 8B7B 10 MOV EDI,DWORD PTR DS:[EBX+0x10] + * 0138C571 |. 8BCF MOV ECX,EDI + * 0138C573 |. 2B4B 0C SUB ECX,DWORD PTR DS:[EBX+0xC] + * 0138C576 |. B8 93244992 MOV EAX,0x92492493 + * 0138C57B |. F7E9 IMUL ECX + * 0138C57D |. 03D1 ADD EDX,ECX + * 0138C57F |. C1FA 04 SAR EDX,0x4 + * 0138C582 |. 8BC2 MOV EAX,EDX + * 0138C584 |. C1E8 1F SHR EAX,0x1F + * 0138C587 |. 03C2 ADD EAX,EDX + * 0138C589 |. 3BC6 CMP EAX,ESI + * 0138C58B |. 73 2F JNB SHORT demonion.0138C5BC + * 0138C58D |. C64424 08 00 MOV BYTE PTR SS:[ESP+0x8],0x0 + * 0138C592 |. 8B4C24 08 MOV ECX,DWORD PTR SS:[ESP+0x8] + * 0138C596 |. 8B5424 08 MOV EDX,DWORD PTR SS:[ESP+0x8] + * 0138C59A |. 51 PUSH ECX + * 0138C59B |. 8B4D 08 MOV ECX,DWORD PTR SS:[EBP+0x8] + * 0138C59E |. 52 PUSH EDX + * 0138C59F |. B8 01000000 MOV EAX,0x1 + * 0138C5A4 |. 8BD7 MOV EDX,EDI + * 0138C5A6 |. E8 F50E0000 CALL demonion.0138D4A0 + * 0138C5AB |. 83C4 08 ADD ESP,0x8 + * 0138C5AE |. 83C7 1C ADD EDI,0x1C + * 0138C5B1 |. 897B 10 MOV DWORD PTR DS:[EBX+0x10],EDI + * 0138C5B4 |. 5F POP EDI + * 0138C5B5 |. 5E POP ESI + * 0138C5B6 |. 8BE5 MOV ESP,EBP + * 0138C5B8 |. 5D POP EBP + * 0138C5B9 |. C2 0400 RETN 0x4 + * 0138C5BC |> 397B 0C CMP DWORD PTR DS:[EBX+0xC],EDI + * 0138C5BF |. 76 05 JBE SHORT demonion.0138C5C6 + * 0138C5C1 |. E8 1B060D00 CALL demonion.0145CBE1 + * 0138C5C6 |> 8B03 MOV EAX,DWORD PTR DS:[EBX] + * 0138C5C8 |. 57 PUSH EDI ; /Arg4 + * 0138C5C9 |. 50 PUSH EAX ; |Arg3 + * 0138C5CA |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+0x8] ; | + * 0138C5CD |. 50 PUSH EAX ; |Arg2 + * 0138C5CE |. 8D4C24 14 LEA ECX,DWORD PTR SS:[ESP+0x14] ; | + * 0138C5D2 |. 51 PUSH ECX ; |Arg1 + * 0138C5D3 |. 8BC3 MOV EAX,EBX ; | + * 0138C5D5 |. E8 D6010000 CALL demonion.0138C7B0 ; \demonion.0138C7B0 + * 0138C5DA |. 5F POP EDI + * 0138C5DB |. 5E POP ESI + * 0138C5DC |. 8BE5 MOV ESP,EBP + * 0138C5DE |. 5D POP EBP + * 0138C5DF \. C2 0400 RETN 0x4 + * + * 4/26/2015 ウルスラグ� * base = 0xa30000, old hook addr = 0xbe6360 + * + * 00A7813A EB 02 JMP SHORT .00A7813E + * 00A7813C 8BC7 MOV EAX,EDI + * 00A7813E 8BB3 E4020000 MOV ESI,DWORD PTR DS:[EBX+0x2E4] + * 00A78144 66:833C70 00 CMP WORD PTR DS:[EAX+ESI*2],0x0 ; jich: here's the first found segment + * 00A78149 74 36 JE SHORT .00A78181 + * 00A7814B 837F 14 08 CMP DWORD PTR DS:[EDI+0x14],0x8 + * 00A7814F 72 08 JB SHORT .00A78159 + * 00A78151 8B07 MOV EAX,DWORD PTR DS:[EDI] + * + * 00A7883A 24 3C AND AL,0x3C + * 00A7883C 50 PUSH EAX + * 00A7883D C74424 4C 000000>MOV DWORD PTR SS:[ESP+0x4C],0x0 + * 00A78845 0F5B ??? ; Unknown command + * 00A78847 C9 LEAVE + * 00A78848 F3:0F114424 44 MOVSS DWORD PTR SS:[ESP+0x44],XMM0 + * 00A7884E F3:0F114C24 48 MOVSS DWORD PTR SS:[ESP+0x48],XMM1 + * 00A78854 E8 37040000 CALL .00A78C90 ; jichi: here's the target function to hook to, text char on the stack[0] + * 00A78859 A1 888EDD00 MOV EAX,DWORD PTR DS:[0xDD8E88] + * 00A7885E A8 01 TEST AL,0x1 + * 00A78860 75 30 JNZ SHORT .00A78892 + * 00A78862 83C8 01 OR EAX,0x1 + * 00A78865 A3 888EDD00 MOV DWORD PTR DS:[0xDD8E88],EAX + * + * Here's the new function call: + * 00A78C8A CC INT3 + * 00A78C8B CC INT3 + * 00A78C8C CC INT3 + * 00A78C8D CC INT3 + * 00A78C8E CC INT3 + * 00A78C8F CC INT3 + * 00A78C90 55 PUSH EBP + * 00A78C91 8BEC MOV EBP,ESP + * 00A78C93 56 PUSH ESI + * 00A78C94 8BF1 MOV ESI,ECX + * 00A78C96 57 PUSH EDI + * 00A78C97 8B7D 08 MOV EDI,DWORD PTR SS:[EBP+0x8] + * 00A78C9A 8B4E 04 MOV ECX,DWORD PTR DS:[ESI+0x4] + * 00A78C9D 3BF9 CMP EDI,ECX + * 00A78C9F 73 76 JNB SHORT .00A78D17 + * 00A78CA1 8B06 MOV EAX,DWORD PTR DS:[ESI] + * 00A78CA3 3BC7 CMP EAX,EDI + * 00A78CA5 77 70 JA SHORT .00A78D17 + * 00A78CA7 2BF8 SUB EDI,EAX + * 00A78CA9 B8 93244992 MOV EAX,0x92492493 + * 00A78CAE F7EF IMUL EDI + * 00A78CB0 03D7 ADD EDX,EDI + * 00A78CB2 C1FA 04 SAR EDX,0x4 + * 00A78CB5 8BFA MOV EDI,EDX + * 00A78CB7 C1EF 1F SHR EDI,0x1F + * 00A78CBA 03FA ADD EDI,EDX + * 00A78CBC 3B4E 08 CMP ECX,DWORD PTR DS:[ESI+0x8] + * 00A78CBF 75 09 JNZ SHORT .00A78CCA + * 00A78CC1 6A 01 PUSH 0x1 + * 00A78CC3 8BCE MOV ECX,ESI + * 00A78CC5 E8 36030000 CALL .00A79000 + * 00A78CCA 8B56 04 MOV EDX,DWORD PTR DS:[ESI+0x4] + * 00A78CCD 8D0CFD 00000000 LEA ECX,DWORD PTR DS:[EDI*8] + * 00A78CD4 2BCF SUB ECX,EDI + * 00A78CD6 8B3E MOV EDI,DWORD PTR DS:[ESI] + * 00A78CD8 85D2 TEST EDX,EDX + * 00A78CDA 74 7B JE SHORT .00A78D57 + * 00A78CDC 66:8B048F MOV AX,WORD PTR DS:[EDI+ECX*4] + * 00A78CE0 66:8902 MOV WORD PTR DS:[EDX],AX + * 00A78CE3 8B448F 04 MOV EAX,DWORD PTR DS:[EDI+ECX*4+0x4] + * 00A78CE7 8942 04 MOV DWORD PTR DS:[EDX+0x4],EAX + * 00A78CEA 8B448F 08 MOV EAX,DWORD PTR DS:[EDI+ECX*4+0x8] + * 00A78CEE 8942 08 MOV DWORD PTR DS:[EDX+0x8],EAX + * 00A78CF1 8B448F 0C MOV EAX,DWORD PTR DS:[EDI+ECX*4+0xC] + * 00A78CF5 8942 0C MOV DWORD PTR DS:[EDX+0xC],EAX + * 00A78CF8 C742 10 00000000 MOV DWORD PTR DS:[EDX+0x10],0x0 + * 00A78CFF 8B448F 14 MOV EAX,DWORD PTR DS:[EDI+ECX*4+0x14] + * 00A78D03 8942 14 MOV DWORD PTR DS:[EDX+0x14],EAX + * 00A78D06 8A448F 18 MOV AL,BYTE PTR DS:[EDI+ECX*4+0x18] + * 00A78D0A 8842 18 MOV BYTE PTR DS:[EDX+0x18],AL + * 00A78D0D 8346 04 1C ADD DWORD PTR DS:[ESI+0x4],0x1C + * 00A78D11 5F POP EDI + * 00A78D12 5E POP ESI + * 00A78D13 5D POP EBP + * 00A78D14 C2 0400 RETN 0x4 + * 00A78D17 3B4E 08 CMP ECX,DWORD PTR DS:[ESI+0x8] + * 00A78D1A 75 09 JNZ SHORT .00A78D25 + * 00A78D1C 6A 01 PUSH 0x1 + * 00A78D1E 8BCE MOV ECX,ESI + * 00A78D20 E8 DB020000 CALL .00A79000 + * 00A78D25 8B4E 04 MOV ECX,DWORD PTR DS:[ESI+0x4] + * 00A78D28 85C9 TEST ECX,ECX + * 00A78D2A 74 2B JE SHORT .00A78D57 + * 00A78D2C 66:8B07 MOV AX,WORD PTR DS:[EDI] + * 00A78D2F 66:8901 MOV WORD PTR DS:[ECX],AX + * 00A78D32 8B47 04 MOV EAX,DWORD PTR DS:[EDI+0x4] + * 00A78D35 8941 04 MOV DWORD PTR DS:[ECX+0x4],EAX + * 00A78D38 8B47 08 MOV EAX,DWORD PTR DS:[EDI+0x8] + * 00A78D3B 8941 08 MOV DWORD PTR DS:[ECX+0x8],EAX + * 00A78D3E 8B47 0C MOV EAX,DWORD PTR DS:[EDI+0xC] + * 00A78D41 8941 0C MOV DWORD PTR DS:[ECX+0xC],EAX + * 00A78D44 C741 10 00000000 MOV DWORD PTR DS:[ECX+0x10],0x0 + * 00A78D4B 8B47 14 MOV EAX,DWORD PTR DS:[EDI+0x14] + * 00A78D4E 8941 14 MOV DWORD PTR DS:[ECX+0x14],EAX + * 00A78D51 8A47 18 MOV AL,BYTE PTR DS:[EDI+0x18] + * 00A78D54 8841 18 MOV BYTE PTR DS:[ECX+0x18],AL + * 00A78D57 8346 04 1C ADD DWORD PTR DS:[ESI+0x4],0x1C + * 00A78D5B 5F POP EDI + * 00A78D5C 5E POP ESI + * 00A78D5D 5D POP EBP + * 00A78D5E C2 0400 RETN 0x4 + * 00A78D61 CC INT3 + * 00A78D62 CC INT3 + * 00A78D63 CC INT3 + * 00A78D64 CC INT3 + * 00A78D65 CC INT3 + */ +static bool InsertGXP1Hook() +{ + union { + DWORD i; + DWORD *id; + BYTE *ib; + }; + //__asm int 3 + for (i = module_base_ + 0x1000; i < module_limit_ - 4; i++) { + // jichi example: + // 00A78144 66:833C70 00 CMP WORD PTR DS:[EAX+ESI*2],0x0 + + //find cmp word ptr [esi*2+eax],0 + if (*id != 0x703c8366) + continue; + i += 4; + if (*ib != 0) + continue; + i++; + DWORD j = i + 0x200; + j = j < (module_limit_ - 8) ? j : (module_limit_ - 8); + + DWORD flag = false; + while (i < j) { + DWORD k = disasm(ib); + if (k == 0) + break; + if (k == 1 && (*ib & 0xf8) == 0x50) { // push reg + flag = true; + break; + } + i += k; + } + if (flag) + while (i < j) { + if (*ib == 0xe8) { // jichi: find first long call after the push operation + i++; + DWORD addr = *id + i + 4; + if (addr > module_base_ && addr < module_limit_) { + HookParam hp = {}; + hp.address = addr; + //hp.type = USING_UNICODE|DATA_INDIRECT; + hp.type = USING_UNICODE|DATA_INDIRECT|NO_CONTEXT|FIXING_SPLIT; // jichi 4/25/2015: Fixing split + hp.length_offset = 1; + hp.offset = 4; + + //GROWL_DWORD3(hp.address, module_base_, hp.address - module_base_); + + //DWORD call = Util::FindCallAndEntryAbs(hp.address, module_limit_ - module_base_, module_base_, 0xec81); // zero + //DWORD call = Util::FindCallAndEntryAbs(hp.address, module_limit_ - module_base_, module_base_, 0xec83); // zero + //DWORD call = Util::FindCallAndEntryAbs(hp.address, module_limit_ - module_base_, module_base_, 0xec8b55); // zero + //GROWL_DWORD3(call, module_base_, call - module_base_); + + ConsoleOutput("vnreng: INSERT GXP"); + NewHook(hp, "GXP"); + + // jichi 5/13/2015: Disable hooking to GetGlyphOutlineW + // FIXME: GetGlyphOutlineW can extract name, but GXP cannot + ConsoleOutput("vnreng:GXP: disable GDI hooks"); + DisableGDIHooks(); + return true; + } + } + i++; + } + } + //ConsoleOutput("Unknown GXP engine."); + ConsoleOutput("vnreng:GXP: failed"); + return false; +} + +static bool InsertGXP2Hook() +{ + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { + ConsoleOutput("vnreng:GXP2: failed to get memory range"); + return false; + } + + // pattern = 0x0f5bc9f30f11442444f30f114c2448e8 + const BYTE bytes[] = { + 0x0f,0x5b, // 00A78845 0F5B ??? ; Unknown command + 0xc9, // 00A78847 C9 LEAVE + 0xf3,0x0f,0x11,0x44,0x24, 0x44, // 00A78848 F3:0F114424 44 MOVSS DWORD PTR SS:[ESP+0x44],XMM0 + 0xf3,0x0f,0x11,0x4c,0x24, 0x48, // 00A7884E F3:0F114C24 48 MOVSS DWORD PTR SS:[ESP+0x48],XMM1 + 0xe8 //37040000 // 00A78854 E8 37040000 CALL .00A78C90 ; jichi: here's the target function to hook to, text char on the stack[0] + }; + enum { addr_offset = sizeof(bytes) - 1 }; // 0x00a78854 - 0x00a78845 + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:GXP2: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.type = USING_UNICODE|NO_CONTEXT|DATA_INDIRECT|FIXING_SPLIT; + hp.length_offset = 1; + ConsoleOutput("vnreng: INSERT GXP2"); + NewHook(hp, "GXP2"); + ConsoleOutput("vnreng:GXP: disable GDI hooks"); + DisableGDIHooks(); + return true; +} + +bool InsertGXPHook() +{ + // GXP1 and GXP2 are harmless to each other + bool ok = InsertGXP1Hook(); + ok = InsertGXP2Hook() || ok; + return ok; +} + +namespace { // unnamed, for Anex86 +BYTE JIS_tableH[0x80] = { + 0x00,0x81,0x81,0x82,0x82,0x83,0x83,0x84, + 0x84,0x85,0x85,0x86,0x86,0x87,0x87,0x88, + 0x88,0x89,0x89,0x8a,0x8a,0x8b,0x8b,0x8c, + 0x8c,0x8d,0x8d,0x8e,0x8e,0x8f,0x8f,0x90, + 0x90,0x91,0x91,0x92,0x92,0x93,0x93,0x94, + 0x94,0x95,0x95,0x96,0x96,0x97,0x97,0x98, + 0x98,0x99,0x99,0x9a,0x9a,0x9b,0x9b,0x9c, + 0x9c,0x9d,0x9d,0x9e,0x9e,0xdf,0xdf,0xe0, + 0xe0,0xe1,0xe1,0xe2,0xe2,0xe3,0xe3,0xe4, + 0xe4,0xe5,0xe5,0xe6,0xe6,0xe7,0xe7,0xe8, + 0xe8,0xe9,0xe9,0xea,0xea,0xeb,0xeb,0xec, + 0xec,0xed,0xed,0xee,0xee,0xef,0xef,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; + +BYTE JIS_tableL[0x80] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x40,0x41,0x42,0x43,0x44,0x45,0x46, + 0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e, + 0x4f,0x50,0x51,0x52,0x53,0x54,0x55,0x56, + 0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e, + 0x5f,0x60,0x61,0x62,0x63,0x64,0x65,0x66, + 0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e, + 0x6f,0x70,0x71,0x72,0x73,0x74,0x75,0x76, + 0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e, + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97, + 0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x00, +}; + +void SpecialHookAnex86(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + __asm + { + mov eax, esp_base + mov ecx, [eax - 0xC] + cmp byte ptr [ecx + 0xE], 0 + jnz _fin + movzx ebx, byte ptr [ecx + 0xC] ; Low byte + movzx edx, byte ptr [ecx + 0xD] ; High byte + test edx,edx + jnz _jis_char + mov eax,data + mov [eax],ebx + mov eax, len + mov [eax], 1 + jmp _fin +_jis_char: + cmp ebx,0x7E + ja _fin + cmp edx,0x7E + ja _fin + test dl,1 + lea eax, [ebx + 0x7E] + movzx ecx, byte ptr [JIS_tableL + ebx] + cmovnz eax, ecx + mov ah, byte ptr [JIS_tableH + edx] + ror ax,8 + mov ecx, data + mov [ecx], eax + mov ecx, len + mov [ecx], 2 +_fin: + } + +} +} // unnamed namespace +bool InsertAnex86Hook() +{ + const DWORD dwords[] = {0x618ac033,0x0d418a0c}; // jichi 12/25/2013: Remove static keyword + for (DWORD i = module_base_ + 0x1000; i < module_limit_ - 8; i++) + if (*(DWORD *)i == dwords[0]) + if (*(DWORD *)(i + 4) == dwords[1]) { + HookParam hp = {}; + hp.address = i; + hp.text_fun = SpecialHookAnex86; + //hp.type = EXTERN_HOOK; + hp.length_offset = 1; + ConsoleOutput("vnreng: INSERT Anex86"); + NewHook(hp, "Anex86"); + return true; + } + ConsoleOutput("vnreng:Anex86: failed"); + return false; +} + +/** + * jichi 9/5/2013: NEXTON games with aInfo.db + * Sample games: + * - /HA-C@4D69E:InnocentBullet.exe (イノセントバレッ�) + * - /HA-C@40414C:ImoutoBancho.exe (妹番長) + * + * See: http://ja.wikipedia.org/wiki/ネクストン + * See (CaoNiMaGeBi): http://tieba.baidu.com/p/2576241908 + * + * Old: + * md5 = 85ac031f2539e1827d9a1d9fbde4023d + * hcode = /HA-C@40414C:ImoutoBancho.exe + * - addr: 4211020 (0x40414c) + * - module: 1051997988 (0x3eb43724) + * - length_offset: 1 + * - off: 4294967280 (0xfffffff0) = -0x10 + * - split: 0 + * - type: 68 (0x44) + * + * New (11/7/2013): + * /HA-20:4@583DE:MN2.EXE (NEW) + * - addr: 361438 (0x583de) + * - module: 3436540819 + * - length_offset: 1 + * - off: 4294967260 (0xffffffdc) = -0x24 + * - split: 4 + * - type: 84 (0x54) + */ + +bool InsertNextonHook() +{ +#if 0 + // 0x8944241885c00f84 + const BYTE bytes[] = { + //0xe8 //??,??,??,??, 00804147 e8 24d90100 call imoutoba.00821a70 + 0x89,0x44,0x24, 0x18, // 0080414c 894424 18 mov dword ptr ss:[esp+0x18],eax; hook here + 0x85,0xc0, // 00804150 85c0 test eax,eax + 0x0f,0x84 // 00804152 ^0f84 c0feffff je imoutoba.00804018 + }; + //enum { addr_offset = 0 }; + ULONG addr = module_base_; //- sizeof(bytes); + do { + addr += sizeof(bytes); // ++ so that each time return diff address + ULONG range = min(module_limit_ - addr, MAX_REL_ADDR); + addr = MemDbg::findBytes(bytes, sizeof(bytes), addr, addr + range); + if (!addr) { + ConsoleOutput("vnreng:NEXTON: pattern not exist"); + return false; + } + + //const BYTE hook_ins[] = { + // 0x57, // 00804144 57 push edi + // 0x8b,0xc3, // 00804145 8bc3 mov eax,ebx + // 0xe8 //??,??,??,??, 00804147 e8 24d90100 call imoutoba.00821a70 + //}; + } while(0xe8c38b57 != *(DWORD *)(addr - 8)); +#endif // 0 + const BYTE bytes[] = { + 0x57, // 0044d696 57 push edi + 0x8b,0xc3, // 0044d697 8bc3 mov eax,ebx + 0xe8, XX4, // 0044d699 e8 6249fdff call .00422000 + 0x89,0x44,0x24, 0x18, // 0044d69e 894424 18 mov dword ptr ss:[esp+0x18],eax ; jichi: this is the ith hook point + 0x85,0xc0, // 0044d6a2 85c0 test eax,eax + 0x0f,0x84 //c2feffff // 0044d6a4 ^0f84 c2feffff je .0044d56c + }; + enum { addr_offset = 0x0044d69e - 0x0044d696 }; // = 8 + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { + ConsoleOutput("vnreng:NEXTON: failed to get memory range"); + return false; + } + ULONG addr = MemDbg::matchBytes(bytes, sizeof(bytes), startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:NEXTON: pattern not exist"); + return false; + } + + //addr = MemDbg::findEnclosingAlignedFunction(addr); // range is around 50, use 80 + //if (!addr) { + // ConsoleOutput("vnreng:NEXTON: enclosing function not found"); + // return false; + //} + + //GROWL_DWORD3(module_base_, addr, *(DWORD *)(addr-8)); + //HookParam hp = {}; + //hp.address = addr; + //hp.offset = 4; // text in arg1 + //hp.split = 4; + + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.length_offset = 1; + //hp.offset = -0x10; + //hp.type = BIG_ENDIAN; // 4 + + // 魔王のくせに生イキ�っ �今度は性戦ぽ // CheatEngine search for byte array: 8944241885C00F84 + //addr = 0x4583de; // wrong + //addr = 0x5460ba; + //addr = 0x5f3d8a; + //addr = 0x768776; + //addr = 0x7a5319; + + hp.offset = -0x24; + hp.split = 4; + hp.type = BIG_ENDIAN|USING_SPLIT; // 0x54 + + // Indirect is needed for new games, + // Such as: /HA-C*0@4583DE for 「魔王のくせに生イキ�っ��� //hp.type = BIG_ENDIAN|DATA_INDIRECT; // 12 + //hp.type = USING_UNICODE; + //GROWL_DWORD3(addr, -hp.offset, hp.type); + + ConsoleOutput("vnreng: INSERT NEXTON"); + NewHook(hp, "NEXTON"); + + //ConsoleOutput("vnreng:NEXTON: disable GDI hooks"); // There are no GDI functions hooked though + //DisableGDIHooks(); // disable GetGlyphOutlineA + return true; +} + +/** jichi 8/17/2014 Nexton1 + * Sample games: + * - [Nomad][071026] 淫烙�巫女 Trial + * + * Debug method: text are prefetched into memory. Add break point to it. + * + * GetGlyphOutlineA is called, but no correct text. + * + * There are so many good hooks. The shortest function was picked,as follows: + * 0041974e cc int3 + * 0041974f cc int3 + * 00419750 56 push esi ; jichi: hook here, text in arg1 + * 00419751 8b7424 08 mov esi,dword ptr ss:[esp+0x8] + * 00419755 8bc6 mov eax,esi + * 00419757 57 push edi + * 00419758 8d78 01 lea edi,dword ptr ds:[eax+0x1] + * 0041975b eb 03 jmp short inrakutr.00419760 + * 0041975d 8d49 00 lea ecx,dword ptr ds:[ecx] + * 00419760 8a10 mov dl,byte ptr ds:[eax] ; jichi: eax is the text + * 00419762 83c0 01 add eax,0x1 + * 00419765 84d2 test dl,dl + * 00419767 ^75 f7 jnz short inrakutr.00419760 + * 00419769 2bc7 sub eax,edi + * 0041976b 50 push eax + * 0041976c 56 push esi + * 0041976d 83c1 04 add ecx,0x4 + * 00419770 e8 eb85feff call inrakutr.00401d60 + * 00419775 5f pop edi + * 00419776 5e pop esi + * 00419777 c2 0400 retn 0x4 + * 0041977a cc int3 + * 0041977b cc int3 + * 0041977c cc int3 + * + * Runtime stack: this function takes two arguments. Text address is in arg1. + * + * Other possible hooks are as follows: + * 00460caf 53 push ebx + * 00460cb0 c700 16000000 mov dword ptr ds:[eax],0x16 + * 00460cb6 e8 39feffff call inrakutr.00460af4 + * 00460cbb 83c4 14 add esp,0x14 + * 00460cbe 385d fc cmp byte ptr ss:[ebp-0x4],bl + * 00460cc1 74 07 je short inrakutr.00460cca + * 00460cc3 8b45 f8 mov eax,dword ptr ss:[ebp-0x8] + * 00460cc6 8360 70 fd and dword ptr ds:[eax+0x70],0xfffffffd + * 00460cca 33c0 xor eax,eax + * 00460ccc eb 2c jmp short inrakutr.00460cfa + * 00460cce 0fb601 movzx eax,byte ptr ds:[ecx] ; jichi: here, ecx + * 00460cd1 8b55 f4 mov edx,dword ptr ss:[ebp-0xc] + * 00460cd4 f64410 1d 04 test byte ptr ds:[eax+edx+0x1d],0x4 + * 00460cd9 74 0e je short inrakutr.00460ce9 + * 00460cdb 8d51 01 lea edx,dword ptr ds:[ecx+0x1] + * 00460cde 381a cmp byte ptr ds:[edx],bl + * 00460ce0 74 07 je short inrakutr.00460ce9 + * 00460ce2 c1e0 08 shl eax,0x8 + * 00460ce5 8bf0 mov esi,eax + * 00460ce7 8bca mov ecx,edx + * 00460ce9 0fb601 movzx eax,byte ptr ds:[ecx] + * 00460cec 03c6 add eax,esi + * 00460cee 385d fc cmp byte ptr ss:[ebp-0x4],bl + * 00460cf1 74 07 je short inrakutr.00460cfa + * 00460cf3 8b4d f8 mov ecx,dword ptr ss:[ebp-0x8] + * 00460cf6 8361 70 fd and dword ptr ds:[ecx+0x70],0xfffffffd + * 00460cfa 5e pop esi + * 00460cfb 5b pop ebx + * 00460cfc c9 leave + * 00460cfd c3 retn + * + * 00460d41 74 05 je short inrakutr.00460d48 + * 00460d43 381e cmp byte ptr ds:[esi],bl + * 00460d45 74 01 je short inrakutr.00460d48 + * 00460d47 46 inc esi + * 00460d48 8bc6 mov eax,esi + * 00460d4a 5e pop esi + * 00460d4b 5b pop ebx + * 00460d4c c3 retn + * 00460d4d 56 push esi + * 00460d4e 8b7424 08 mov esi,dword ptr ss:[esp+0x8] + * 00460d52 0fb606 movzx eax,byte ptr ds:[esi] ; jichi: esi & ebp + * 00460d55 50 push eax + * 00460d56 e8 80fcffff call inrakutr.004609db + * 00460d5b 85c0 test eax,eax + * 00460d5d 59 pop ecx + * 00460d5e 74 0b je short inrakutr.00460d6b + * 00460d60 807e 01 00 cmp byte ptr ds:[esi+0x1],0x0 + * 00460d64 74 05 je short inrakutr.00460d6b + * 00460d66 6a 02 push 0x2 + * 00460d68 58 pop eax + * 00460d69 5e pop esi + * 00460d6a c3 retn + * + * 00460d1d 53 push ebx + * 00460d1e 53 push ebx + * 00460d1f 53 push ebx + * 00460d20 53 push ebx + * 00460d21 53 push ebx + * 00460d22 c700 16000000 mov dword ptr ds:[eax],0x16 + * 00460d28 e8 c7fdffff call inrakutr.00460af4 + * 00460d2d 83c4 14 add esp,0x14 + * 00460d30 33c0 xor eax,eax + * 00460d32 eb 16 jmp short inrakutr.00460d4a + * 00460d34 0fb606 movzx eax,byte ptr ds:[esi] ; jichi: esi, ebp + * 00460d37 50 push eax + * 00460d38 e8 9efcffff call inrakutr.004609db + * 00460d3d 46 inc esi + * 00460d3e 85c0 test eax,eax + * 00460d40 59 pop ecx + * 00460d41 74 05 je short inrakutr.00460d48 + * 00460d43 381e cmp byte ptr ds:[esi],bl + * 00460d45 74 01 je short inrakutr.00460d48 + * 00460d47 46 inc esi + * + * 0042c59f cc int3 + * 0042c5a0 56 push esi + * 0042c5a1 8bf1 mov esi,ecx + * 0042c5a3 8b86 cc650000 mov eax,dword ptr ds:[esi+0x65cc] + * 0042c5a9 8b50 1c mov edx,dword ptr ds:[eax+0x1c] + * 0042c5ac 57 push edi + * 0042c5ad 8b7c24 0c mov edi,dword ptr ss:[esp+0xc] + * 0042c5b1 8d8e cc650000 lea ecx,dword ptr ds:[esi+0x65cc] + * 0042c5b7 57 push edi + * 0042c5b8 ffd2 call edx + * 0042c5ba 8bc7 mov eax,edi + * 0042c5bc 8d50 01 lea edx,dword ptr ds:[eax+0x1] + * 0042c5bf 90 nop + * 0042c5c0 8a08 mov cl,byte ptr ds:[eax] ; jichi: here eax + * 0042c5c2 83c0 01 add eax,0x1 + * 0042c5c5 84c9 test cl,cl + * 0042c5c7 ^75 f7 jnz short inrakutr.0042c5c0 + * 0042c5c9 2bc2 sub eax,edx + * 0042c5cb 50 push eax + * 0042c5cc 57 push edi + * 0042c5cd 8d8e 24660000 lea ecx,dword ptr ds:[esi+0x6624] + * 0042c5d3 e8 8857fdff call inrakutr.00401d60 + * 0042c5d8 8b86 b4660000 mov eax,dword ptr ds:[esi+0x66b4] + * 0042c5de 85c0 test eax,eax + * 0042c5e0 74 0d je short inrakutr.0042c5ef + * 0042c5e2 8b8e b8660000 mov ecx,dword ptr ds:[esi+0x66b8] + * 0042c5e8 2bc8 sub ecx,eax + * 0042c5ea c1f9 02 sar ecx,0x2 + * 0042c5ed 75 05 jnz short inrakutr.0042c5f4 + * 0042c5ef e8 24450300 call inrakutr.00460b18 + * 0042c5f4 8b96 b4660000 mov edx,dword ptr ds:[esi+0x66b4] + * 0042c5fa 8b0a mov ecx,dword ptr ds:[edx] + * 0042c5fc 8b01 mov eax,dword ptr ds:[ecx] + * 0042c5fe 8b50 30 mov edx,dword ptr ds:[eax+0x30] + * 0042c601 ffd2 call edx + * 0042c603 8b06 mov eax,dword ptr ds:[esi] + * 0042c605 8b90 f8000000 mov edx,dword ptr ds:[eax+0xf8] + * 0042c60b 6a 00 push 0x0 + * 0042c60d 68 c3164a00 push inrakutr.004a16c3 + * 0042c612 57 push edi + * 0042c613 8bce mov ecx,esi + * 0042c615 ffd2 call edx + * 0042c617 5f pop edi + * 0042c618 5e pop esi + * 0042c619 c2 0400 retn 0x4 + * 0042c61c cc int3 + * + * 0041974e cc int3 + * 0041974f cc int3 + * 00419750 56 push esi + * 00419751 8b7424 08 mov esi,dword ptr ss:[esp+0x8] + * 00419755 8bc6 mov eax,esi + * 00419757 57 push edi + * 00419758 8d78 01 lea edi,dword ptr ds:[eax+0x1] + * 0041975b eb 03 jmp short inrakutr.00419760 + * 0041975d 8d49 00 lea ecx,dword ptr ds:[ecx] + * 00419760 8a10 mov dl,byte ptr ds:[eax] ; jichi: eax + * 00419762 83c0 01 add eax,0x1 + * 00419765 84d2 test dl,dl + * 00419767 ^75 f7 jnz short inrakutr.00419760 + * 00419769 2bc7 sub eax,edi + * 0041976b 50 push eax + * 0041976c 56 push esi + * 0041976d 83c1 04 add ecx,0x4 + * 00419770 e8 eb85feff call inrakutr.00401d60 + * 00419775 5f pop edi + * 00419776 5e pop esi + * 00419777 c2 0400 retn 0x4 + * 0041977a cc int3 + * 0041977b cc int3 + * 0041977c cc int3 + * + * 0042c731 57 push edi + * 0042c732 ffd0 call eax + * 0042c734 8bc7 mov eax,edi + * 0042c736 8d50 01 lea edx,dword ptr ds:[eax+0x1] + * 0042c739 8da424 00000000 lea esp,dword ptr ss:[esp] + * 0042c740 8a08 mov cl,byte ptr ds:[eax] ; jichi: eax + * 0042c742 83c0 01 add eax,0x1 + * 0042c745 84c9 test cl,cl + * 0042c747 ^75 f7 jnz short inrakutr.0042c740 + * 0042c749 2bc2 sub eax,edx + * 0042c74b 8bf8 mov edi,eax + * 0042c74d e8 fe1d0100 call inrakutr.0043e550 + * 0042c752 8b0d 187f4c00 mov ecx,dword ptr ds:[0x4c7f18] + * 0042c758 8b11 mov edx,dword ptr ds:[ecx] + * 0042c75a 8b42 70 mov eax,dword ptr ds:[edx+0x70] + * 0042c75d ffd0 call eax + * 0042c75f 83c0 0a add eax,0xa + * 0042c762 0fafc7 imul eax,edi + * 0042c765 5f pop edi + * 0042c766 8986 60660000 mov dword ptr ds:[esi+0x6660],eax + */ +bool InsertNexton1Hook() +{ + // Use accurate stopAddress in case of running out of memory + // Since the file pattern for Nexton1 is not accurate. + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { + ConsoleOutput("vnreng:NEXTON1: failed to get memory range"); + return false; + } + const BYTE bytes[] = { + 0x56, // 00419750 56 push esi ; jichi: hook here, text in arg1 + 0x8b,0x74,0x24, 0x08, // 00419751 8b7424 08 mov esi,dword ptr ss:[esp+0x8] + 0x8b,0xc6, // 00419755 8bc6 mov eax,esi + 0x57, // 00419757 57 push edi + 0x8d,0x78, 0x01, // 00419758 8d78 01 lea edi,dword ptr ds:[eax+0x1] + 0xeb, 0x03, // 0041975b eb 03 jmp short inrakutr.00419760 + 0x8d,0x49, 0x00, // 0041975d 8d49 00 lea ecx,dword ptr ds:[ecx] + 0x8a,0x10, // 00419760 8a10 mov dl,byte ptr ds:[eax] ; jichi: eax is the text + 0x83,0xc0, 0x01, // 00419762 83c0 01 add eax,0x1 + 0x84,0xd2, // 00419765 84d2 test dl,dl + 0x75, 0xf7, // 00419767 ^75 f7 jnz short inrakutr.00419760 + 0x2b,0xc7, // 00419769 2bc7 sub eax,edi + 0x50, // 0041976b 50 push eax + 0x56, // 0041976c 56 push esi + 0x83,0xc1, 0x04 // 0041976d 83c1 04 add ecx,0x4 + //0xe8, XX4, // 00419770 e8 eb85feff call inrakutr.00401d60 + //0x5f, // 00419775 5f pop edi + //0x5e, // 00419776 5e pop esi + //0xc2, 0x04,0x00 // 00419777 c2 0400 retn 0x4 + }; + enum { addr_offset = 0 }; // distance to the beginning of the function + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress); + //GROWL_DWORD(addr); // supposed to be 0x4010e0 + if (!addr) { + ConsoleOutput("vnreng:NEXTON1: pattern not found"); + return false; + } + //GROWL_DWORD(addr); + + HookParam hp = {}; + hp.address = addr + addr_offset; + //hp.length_offset = 1; + hp.offset = 4; // [esp+4] == arg0 + hp.type = USING_STRING; + ConsoleOutput("vnreng: INSERT NEXTON1"); + NewHook(hp, "NEXTON1"); + return true; +} + +/** + * jichi 9/16/2013: a-unicorn / gesen18 + * See (CaoNiMaGeBi): http://tieba.baidu.com/p/2586681823 + * Pattern: 2bce8bf8 + * 2bce sub ecx,esi ; hook here + * 8bf8 mov eds,eax + * 8bd1 mov edx,ecx + * + * /HBN-20*0@xxoo + * - length_offset: 1 + * - off: 4294967260 (0xffffffdc) + * - type: 1032 (0x408) + */ +bool InsertUnicornHook() +{ + // pattern: 2bce8bf8 + const BYTE bytes[] = { + 0x2b,0xce, // sub ecx,esi ; hook here + 0x8b,0xf8 // mov edi,eax + }; + //enum { addr_offset = 0 }; + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + if (!addr) { + ConsoleOutput("vnreng:Unicorn: pattern not exist"); + return false; + } + + HookParam hp = {}; + hp.type = NO_CONTEXT | DATA_INDIRECT; + hp.length_offset = 1; + hp.offset = -0x24; // jichi: text in edi + hp.address = addr; + + //index = SearchPattern(module_base_, size,ins, sizeof(ins)); + //GROWL_DWORD2(base, index); + + ConsoleOutput("vnreng: INSERT Unicorn"); + NewHook(hp, "Unicorn"); + return true; +} + +/** + * jichi 10/1/2013: Artemis Engine + * See: http://www.ies-net.com/ + * See (CaoNiMaGeBi): http://tieba.baidu.com/p/2625537737 + * Pattern: + * 650a2f 83c4 0c add esp,0xc ; hook here + * 650a32 0fb6c0 movzx eax,al + * 650a35 85c0 test eax,eax + * 0fb6c0 75 0e jnz short tsugokaz.0065a47 + * + * Wrong: 0x400000 + 0x7c574 + * + * //Example: [130927]妹スパイラル /HBN-8*0:14@65589F + * Example: ヂ�ウノイイ家�Trial /HBN-8*0:14@650A2F + * Note: 0x650a2f > 40000(base) + 20000(limit) + * - addr: 0x650a2f + * - text_fun: 0x0 + * - function: 0 + * - hook_len: 0 + * - ind: 0 + * - length_offset: 1 + * - module: 0 + * - off: 4294967284 = 0xfffffff4 = -0xc + * - recover_len: 0 + * - split: 20 = 0x14 + * - split_ind: 0 + * - type: 1048 = 0x418 + * + * @CaoNiMaGeBi: + * RECENT GAMES: + * [130927]妹スパイラル /HBN-8*0:14@65589F + * [130927]サ�ライホルモン + * [131025]ヂ�ウノイイ家�/HBN-8*0:14@650A2F (for trial version) + * CLIENT ORGANIZAIONS: + * CROWD + * D:drive. + * Hands-Aid Corporation + * iMel株式会社 + * SHANNON + * SkyFish + * SNACK-FACTORY + * team flap + * Zodiac + * くらむちめ�� * まかろんソフト + * アイヂ�アファクトリー株式会社 + * カラクリズ� + * 合赼�社ファーストリー� + * 有限会社ウルクスへブン + * 有限会社ロータス + * 株式会社CUCURI + * 株式会社アバン + * 株式会社インタラクヂ�ブブレインズ + * 株式会社ウィンヂ�ール + * 株式会社エヴァンジェ + * 株式会社ポニーキャニオン + * 株式会社大福エンターヂ�ンメン� */ +bool InsertArtemisHook() +{ + const BYTE bytes[] = { + 0x83,0xc4, 0x0c, // add esp,0xc ; hook here + 0x0f,0xb6,0xc0, // movzx eax,al + 0x85,0xc0, // test eax,eax + 0x75, 0x0e // jnz XXOO ; it must be 0xe, or there will be duplication + }; + //enum { addr_offset = 0 }; + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //GROWL_DWORD3(reladdr, module_base_, range); + if (!addr) { + ConsoleOutput("vnreng:Artemis: pattern not exist"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.length_offset = 1; + hp.offset = -0xc; + hp.split = 0x14; + hp.type = NO_CONTEXT|DATA_INDIRECT|USING_SPLIT; // 0x418 + + //hp.address = 0x650a2f; + //GROWL_DWORD(hp.address); + + ConsoleOutput("vnreng: INSERT Artemis"); + NewHook(hp, "Artemis"); + //ConsoleOutput("Artemis"); + return true; +} + +/** + * jichi 1/2/2014: Taskforce2 Engine + * + * Examples: + * 神�仮)-カミサマカヂ�カリ- 路地裏繚乱編 (1.1) + * /HS-8@178872:Taskforce2.exe + * + * 00578819 . 50 push eax ; |arg1 + * 0057881a . c745 f4 cc636b>mov dword ptr ss:[ebp-0xc],taskforc.006b>; | + * 00578821 . e8 31870000 call taskforc.00580f57 ; \taskforc.00580f57 + * 00578826 . cc int3 + * 00578827 /$ 8b4c24 04 mov ecx,dword ptr ss:[esp+0x4] + * 0057882b |. 53 push ebx + * 0057882c |. 33db xor ebx,ebx + * 0057882e |. 3bcb cmp ecx,ebx + * 00578830 |. 56 push esi + * 00578831 |. 57 push edi + * 00578832 |. 74 08 je short taskforc.0057883c + * 00578834 |. 8b7c24 14 mov edi,dword ptr ss:[esp+0x14] + * 00578838 |. 3bfb cmp edi,ebx + * 0057883a |. 77 1b ja short taskforc.00578857 + * 0057883c |> e8 28360000 call taskforc.0057be69 + * 00578841 |. 6a 16 push 0x16 + * 00578843 |. 5e pop esi + * 00578844 |. 8930 mov dword ptr ds:[eax],esi + * 00578846 |> 53 push ebx + * 00578847 |. 53 push ebx + * 00578848 |. 53 push ebx + * 00578849 |. 53 push ebx + * 0057884a |. 53 push ebx + * 0057884b |. e8 6a050000 call taskforc.00578dba + * 00578850 |. 83c4 14 add esp,0x14 + * 00578853 |. 8bc6 mov eax,esi + * 00578855 |. eb 31 jmp short taskforc.00578888 + * 00578857 |> 8b7424 18 mov esi,dword ptr ss:[esp+0x18] + * 0057885b |. 3bf3 cmp esi,ebx + * 0057885d |. 75 04 jnz short taskforc.00578863 + * 0057885f |. 8819 mov byte ptr ds:[ecx],bl + * 00578861 |.^eb d9 jmp short taskforc.0057883c + * 00578863 |> 8bd1 mov edx,ecx + * 00578865 |> 8a06 /mov al,byte ptr ds:[esi] + * 00578867 |. 8802 |mov byte ptr ds:[edx],al + * 00578869 |. 42 |inc edx + * 0057886a |. 46 |inc esi + * 0057886b |. 3ac3 |cmp al,bl + * 0057886d |. 74 03 |je short taskforc.00578872 + * 0057886f |. 4f |dec edi + * 00578870 |.^75 f3 \jnz short taskforc.00578865 + * 00578872 |> 3bfb cmp edi,ebx ; jichi: hook here + * 00578874 |. 75 10 jnz short taskforc.00578886 + * 00578876 |. 8819 mov byte ptr ds:[ecx],bl + * 00578878 |. e8 ec350000 call taskforc.0057be69 + * 0057887d |. 6a 22 push 0x22 + * 0057887f |. 59 pop ecx + * 00578880 |. 8908 mov dword ptr ds:[eax],ecx + * 00578882 |. 8bf1 mov esi,ecx + * 00578884 |.^eb c0 jmp short taskforc.00578846 + * 00578886 |> 33c0 xor eax,eax + * 00578888 |> 5f pop edi + * 00578889 |. 5e pop esi + * 0057888a |. 5b pop ebx + * 0057888b \. c3 retn + * + * [131129] [Digital Cute] オトメスイッ� -OtomeSwitch- �彼が持ってる彼女のリモコン(1.1) + * /HS-8@1948E9:Taskforce2.exe + * - addr: 0x1948e9 + * - off: 4294967284 (0xfffffff4 = -0xc) + * - type: 65 (0x41) + * + * 00594890 . 50 push eax ; |arg1 + * 00594891 . c745 f4 64c56d>mov dword ptr ss:[ebp-0xc],taskforc.006d>; | + * 00594898 . e8 88880000 call taskforc.0059d125 ; \taskforc.0059d125 + * 0059489d . cc int3 + * 0059489e /$ 8b4c24 04 mov ecx,dword ptr ss:[esp+0x4] + * 005948a2 |. 53 push ebx + * 005948a3 |. 33db xor ebx,ebx + * 005948a5 |. 3bcb cmp ecx,ebx + * 005948a7 |. 56 push esi + * 005948a8 |. 57 push edi + * 005948a9 |. 74 08 je short taskforc.005948b3 + * 005948ab |. 8b7c24 14 mov edi,dword ptr ss:[esp+0x14] + * 005948af |. 3bfb cmp edi,ebx + * 005948b1 |. 77 1b ja short taskforc.005948ce + * 005948b3 |> e8 91350000 call taskforc.00597e49 + * 005948b8 |. 6a 16 push 0x16 + * 005948ba |. 5e pop esi + * 005948bb |. 8930 mov dword ptr ds:[eax],esi + * 005948bd |> 53 push ebx + * 005948be |. 53 push ebx + * 005948bf |. 53 push ebx + * 005948c0 |. 53 push ebx + * 005948c1 |. 53 push ebx + * 005948c2 |. e8 7e010000 call taskforc.00594a45 + * 005948c7 |. 83c4 14 add esp,0x14 + * 005948ca |. 8bc6 mov eax,esi + * 005948cc |. eb 31 jmp short taskforc.005948ff + * 005948ce |> 8b7424 18 mov esi,dword ptr ss:[esp+0x18] + * 005948d2 |. 3bf3 cmp esi,ebx + * 005948d4 |. 75 04 jnz short taskforc.005948da + * 005948d6 |. 8819 mov byte ptr ds:[ecx],bl + * 005948d8 |.^eb d9 jmp short taskforc.005948b3 + * 005948da |> 8bd1 mov edx,ecx + * 005948dc |> 8a06 /mov al,byte ptr ds:[esi] + * 005948de |. 8802 |mov byte ptr ds:[edx],al + * 005948e0 |. 42 |inc edx + * 005948e1 |. 46 |inc esi + * 005948e2 |. 3ac3 |cmp al,bl + * 005948e4 |. 74 03 |je short taskforc.005948e9 + * 005948e6 |. 4f |dec edi + * 005948e7 |.^75 f3 \jnz short taskforc.005948dc + * 005948e9 |> 3bfb cmp edi,ebx ; jichi: hook here + * 005948eb |. 75 10 jnz short taskforc.005948fd + * 005948ed |. 8819 mov byte ptr ds:[ecx],bl + * 005948ef |. e8 55350000 call taskforc.00597e49 + * 005948f4 |. 6a 22 push 0x22 + * 005948f6 |. 59 pop ecx + * 005948f7 |. 8908 mov dword ptr ds:[eax],ecx + * 005948f9 |. 8bf1 mov esi,ecx + * 005948fb |.^eb c0 jmp short taskforc.005948bd + * 005948fd |> 33c0 xor eax,eax + * 005948ff |> 5f pop edi + * 00594900 |. 5e pop esi + * 00594901 |. 5b pop ebx + * 00594902 \. c3 retn + * + * Use this if that hook fails, try this one for future engines: + * /HS0@44CADA + */ +bool InsertTaskforce2Hook() +{ + const BYTE bytes[] = { + 0x88,0x02, // 005948de |. 8802 |mov byte ptr ds:[edx],al + 0x42, // 005948e0 |. 42 |inc edx + 0x46, // 005948e1 |. 46 |inc esi + 0x3a,0xc3, // 005948e2 |. 3ac3 |cmp al,bl + 0x74, 0x03, // 005948e4 |. 74 03 |je short taskforc.005948e9 + 0x4f, // 005948e6 |. 4f |dec edi + 0x75, 0xf3, // 005948e7 |.^75 f3 \jnz short taskforc.005948dc + 0x3b,0xfb // 005948e9 |> 3bfb cmp edi,ebx ; jichi: hook here + }; + enum { addr_offset = sizeof(bytes) - 2 }; + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //GROWL_DWORD3(reladdr, module_base_, range); + if (!addr) { + ConsoleOutput("vnreng:Taskforce2: pattern not exist"); + //return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.offset = -0xc; // text in ecx + hp.type = BIG_ENDIAN|USING_STRING; // 0x41 + + //GROWL_DWORD(hp.address); + //hp.address = 0x1948e9 + module_base_; + + ConsoleOutput("vnreng: INSERT Taskforce2"); + NewHook(hp, "Taskforce2"); + return true; +} + +namespace { // unnamed Rejet +/** + * jichi 12/22/2013: Rejet + * See (CaoNiMaGeBi): http://www.hongfire.com/forum/printthread.php?t=36807&pp=40&page=172 + * See (CaoNiMaGeBi): http://tieba.baidu.com/p/2506179113 + * Pattern: 2bce8bf8 + * 2bce sub ecx,esi ; hook here + * 8bf8 mov eds,eax + * 8bd1 mov edx,ecx + * + * Examples: + * - Type1: ドットカレシ-We're 8bit Lovers!: /HBN-4*0@A5332:DotKareshi.exe + * length_offset: 1 + * off: 0xfffffff8 (-0x8) + * type: 1096 (0x448) + * + * module_base_ = 10e0000 (variant) + * hook_addr = module_base_ + reladdr = 0xe55332 + * 01185311 . FFF0 PUSH EAX ; beginning of a new function + * 01185313 . 0FC111 XADD DWORD PTR DS:[ECX],EDX + * 01185316 . 4A DEC EDX + * 01185317 . 85D2 TEST EDX,EDX + * 01185319 . 0F8F 45020000 JG DotKares.01185564 + * 0118531F . 8B08 MOV ECX,DWORD PTR DS:[EAX] + * 01185321 . 8B11 MOV EDX,DWORD PTR DS:[ECX] + * 01185323 . 50 PUSH EAX + * 01185324 . 8B42 04 MOV EAX,DWORD PTR DS:[EDX+0x4] + * 01185327 . FFD0 CALL EAX + * 01185329 . E9 36020000 JMP DotKares.01185564 + * 0118532E . 8B7424 20 MOV ESI,DWORD PTR SS:[ESP+0x20] + * 01185332 . E8 99A9FBFF CALL DotKares.0113FCD0 ; hook here + * 01185337 . 8BF0 MOV ESI,EAX + * 01185339 . 8D4C24 14 LEA ECX,DWORD PTR SS:[ESP+0x14] + * 0118533D . 3BF7 CMP ESI,EDI + * 0118533F . 0F84 1A020000 JE DotKares.0118555F + * 01185345 . 51 PUSH ECX ; /Arg2 + * 01185346 . 68 E4FE5501 PUSH DotKares.0155FEE4 ; |Arg1 = 0155FEE4 + * 0118534B . E8 1023F9FF CALL DotKares.01117660 ; \DotKares.00377660 + * 01185350 . 83C4 08 ADD ESP,0x8 + * 01185353 . 84C0 TEST AL,AL + * + * - Type2: ドットカレシ-We're 8bit Lovers! II: /HBN-8*0@A7AF9:dotkareshi.exe + * off: 4294967284 (0xfffffff4 = -0xc) + * length_offset: 1 + * type: 1096 (0x448) + * + * module_base_: 0x12b0000 + * + * 01357ad2 . fff0 push eax ; beginning of a new function + * 01357ad4 . 0fc111 xadd dword ptr ds:[ecx],edx + * 01357ad7 . 4a dec edx + * 01357ad8 . 85d2 test edx,edx + * 01357ada . 7f 0a jg short dotkares.01357ae6 + * 01357adc . 8b08 mov ecx,dword ptr ds:[eax] + * 01357ade . 8b11 mov edx,dword ptr ds:[ecx] + * 01357ae0 . 50 push eax + * 01357ae1 . 8b42 04 mov eax,dword ptr ds:[edx+0x4] + * 01357ae4 . ffd0 call eax + * 01357ae6 > 8b4c24 14 mov ecx,dword ptr ss:[esp+0x14] + * 01357aea . 33ff xor edi,edi + * 01357aec . 3979 f4 cmp dword ptr ds:[ecx-0xc],edi + * 01357aef . 0f84 1e020000 je dotkares.01357d13 + * 01357af5 . 8b7424 20 mov esi,dword ptr ss:[esp+0x20] + * 01357af9 . e8 7283fbff call dotkares.0130fe70 ; jichi: hook here + * 01357afe . 8bf0 mov esi,eax + * 01357b00 . 3bf7 cmp esi,edi + * 01357b02 . 0f84 0b020000 je dotkares.01357d13 + * 01357b08 . 8d5424 14 lea edx,dword ptr ss:[esp+0x14] + * 01357b0c . 52 push edx ; /arg2 + * 01357b0d . 68 cc9f7501 push dotkares.01759fcc ; |arg1 = 01759fcc + * 01357b12 . e8 e9f9f8ff call dotkares.012e7500 ; \dotkares.012c7500 + * 01357b17 . 83c4 08 add esp,0x8 + * 01357b1a . 84c0 test al,al + * 01357b1c . 74 1d je short dotkares.01357b3b + * 01357b1e . 8d46 64 lea eax,dword ptr ds:[esi+0x64] + * 01357b21 . e8 bad0f8ff call dotkares.012e4be0 + * 01357b26 . 68 28a17501 push dotkares.0175a128 ; /arg1 = 0175a128 ascii "
" + * + * - Type2: Tiny×MACHINEGUN: /HBN-8*0@4CEB8:TinyMachinegun.exe + * module_base_: 0x12f0000 + * There are two possible places to hook + * + * 0133cea0 . fff0 push eax ; beginning of a new function + * 0133cea2 . 0fc111 xadd dword ptr ds:[ecx],edx + * 0133cea5 . 4a dec edx + * 0133cea6 . 85d2 test edx,edx + * 0133cea8 . 7f 0a jg short tinymach.0133ceb4 + * 0133ceaa . 8b08 mov ecx,dword ptr ds:[eax] + * 0133ceac . 8b11 mov edx,dword ptr ds:[ecx] + * 0133ceae . 50 push eax + * 0133ceaf . 8b42 04 mov eax,dword ptr ds:[edx+0x4] + * 0133ceb2 . ffd0 call eax + * 0133ceb4 > 8b4c24 14 mov ecx,dword ptr ss:[esp+0x14] + * 0133ceb8 . 33db xor ebx,ebx ; jichi: hook here + * 0133ceba . 3959 f4 cmp dword ptr ds:[ecx-0xc],ebx + * 0133cebd . 0f84 d4010000 je tinymach.0133d097 + * 0133cec3 . 8b7424 20 mov esi,dword ptr ss:[esp+0x20] + * 0133cec7 . e8 f4f90100 call tinymach.0135c8c0 ; jichi: or hook here + * 0133cecc . 8bf0 mov esi,eax + * 0133cece . 3bf3 cmp esi,ebx + * 0133ced0 . 0f84 c1010000 je tinymach.0133d097 + * 0133ced6 . 8d5424 14 lea edx,dword ptr ss:[esp+0x14] + * 0133ceda . 52 push edx ; /arg2 + * 0133cedb . 68 44847d01 push tinymach.017d8444 ; |arg1 = 017d8444 + * 0133cee0 . e8 eb5bfdff call tinymach.01312ad0 ; \tinymach.011b2ad0 + * + * - Type 3: 剣が君: /HBN-8*0@B357D:KenGaKimi.exe + * + * 01113550 . fff0 push eax + * 01113552 . 0fc111 xadd dword ptr ds:[ecx],edx + * 01113555 . 4a dec edx + * 01113556 . 85d2 test edx,edx + * 01113558 . 7f 0a jg short kengakim.01113564 + * 0111355a . 8b08 mov ecx,dword ptr ds:[eax] + * 0111355c . 8b11 mov edx,dword ptr ds:[ecx] + * 0111355e . 50 push eax + * 0111355f . 8b42 04 mov eax,dword ptr ds:[edx+0x4] + * 01113562 . ffd0 call eax + * 01113564 8b4c24 14 mov ecx,dword ptr ss:[esp+0x14] + * 01113568 33ff xor edi,edi + * 0111356a 3979 f4 cmp dword ptr ds:[ecx-0xc],edi + * 0111356d 0f84 09020000 je kengakim.0111377c + * 01113573 8d5424 14 lea edx,dword ptr ss:[esp+0x14] + * 01113577 52 push edx + * 01113578 68 dc6a5401 push kengakim.01546adc + * 0111357d e8 3eaff6ff call kengakim.0107e4c0 ; hook here + */ +bool FindRejetHook(LPCVOID pattern, DWORD pattern_size, DWORD hook_off, DWORD hook_offset, LPCSTR hook_name = "Rejet") +{ + // Offset to the function call from the beginning of the function + //enum { addr_offset = 0x21 }; // Type1: hex(0x01185332-0x01185311) + //const BYTE pattern[] = { // Type1: Function start + // 0xff,0xf0, // 01185311 . fff0 push eax ; beginning of a new function + // 0x0f,0xc1,0x11, // 01185313 . 0fc111 xadd dword ptr ds:[ecx],edx + // 0x4a, // 01185316 . 4a dec edx + // 0x85,0xd2, // 01185317 . 85d2 test edx,edx + // 0x0f,0x8f // 01185319 . 0f8f 45020000 jg DotKares.01185564 + //}; + //GROWL_DWORD(module_base_); + ULONG addr = module_base_; //- sizeof(pattern); + do { + //addr += sizeof(pattern); // ++ so that each time return diff address + ULONG range = min(module_limit_ - addr, MAX_REL_ADDR); + addr = MemDbg::findBytes(pattern, pattern_size, addr, addr + range); + if (!addr) { + //ITH_MSG(L"failed"); + ConsoleOutput("vnreng:Rejet: pattern not found"); + return false; + } + + addr += hook_off; + //GROWL_DWORD(addr); + //GROWL_DWORD(*(DWORD *)(addr-3)); + //const BYTE hook_ins[] = { + // /*0x8b,*/0x74,0x24, 0x20, // mov esi,dword ptr ss:[esp+0x20] + // 0xe8 //??,??,??,??, 01357af9 e8 7283fbff call DotKares.0130fe70 ; jichi: hook here + //}; + } while(0xe8202474 != *(DWORD *)(addr - 3)); + + ConsoleOutput("vnreng: INSERT Rejet"); + HookParam hp = {}; + hp.address = addr; //- 0xf; + hp.type = NO_CONTEXT|DATA_INDIRECT|FIXING_SPLIT; + hp.length_offset = 1; + hp.offset = hook_offset; + //hp.offset = -0x8; // Type1 + //hp.offset = -0xc; // Type2 + + NewHook(hp, hook_name); + return true; +} +bool InsertRejetHook1() // This type of hook has varied hook address +{ + const BYTE bytes[] = { // Type1: Function start + 0xff,0xf0, // 01185311 . fff0 push eax ; beginning of a new function + 0x0f,0xc1,0x11, // 01185313 . 0fc111 xadd dword ptr ds:[ecx],edx + 0x4a, // 01185316 . 4a dec edx + 0x85,0xd2, // 01185317 . 85d2 test edx,edx + 0x0f,0x8f // 01185319 . 0f8f 45020000 jg DotKares.01185564 + }; + // Offset to the function call from the beginning of the function + enum { addr_offset = 0x21 }; // Type1: hex(0x01185332-0x01185311) + enum { hook_offset = -0x8 }; // hook parameter + return FindRejetHook(bytes, sizeof(bytes), addr_offset, hook_offset); +} +bool InsertRejetHook2() // This type of hook has static hook address +{ + const BYTE bytes[] = { // Type2 Function start + 0xff,0xf0, // 01357ad2 fff0 push eax + 0x0f,0xc1,0x11, // 01357ad4 0fc111 xadd dword ptr ds:[ecx],edx + 0x4a, // 01357ad7 4a dec edx + 0x85,0xd2, // 01357ad8 85d2 test edx,edx + 0x7f, 0x0a, // 01357ada 7f 0a jg short DotKares.01357ae6 + 0x8b,0x08, // 01357adc 8b08 mov ecx,dword ptr ds:[eax] + 0x8b,0x11, // 01357ade 8b11 mov edx,dword ptr ds:[ecx] + 0x50, // 01357ae0 50 push eax + 0x8b,0x42, 0x04, // 01357ae1 8b42 04 mov eax,dword ptr ds:[edx+0x4] + 0xff,0xd0, // 01357ae4 ffd0 call eax + 0x8b,0x4c,0x24, 0x14 // 01357ae6 8b4c24 14 mov ecx,dword ptr ss:[esp+0x14] + }; + // Offset to the function call from the beginning of the function + enum { addr_offset = 0x27 }; // Type2: hex(0x0133CEC7-0x0133CEA0) = hex(0x01357af9-0x1357ad2) + enum { hook_offset = -0xc }; // hook parameter + return FindRejetHook(bytes, sizeof(bytes), addr_offset, hook_offset); +} +bool InsertRejetHook3() // jichi 12/28/2013: add for 剣が君 +{ + // The following pattern is the same as type2 + const BYTE bytes[] = { // Type2 Function start + 0xff,0xf0, // 01357ad2 fff0 push eax + 0x0f,0xc1,0x11, // 01357ad4 0fc111 xadd dword ptr ds:[ecx],edx + 0x4a, // 01357ad7 4a dec edx + 0x85,0xd2, // 01357ad8 85d2 test edx,edx + 0x7f, 0x0a, // 01357ada 7f 0a jg short DotKares.01357ae6 + 0x8b,0x08, // 01357adc 8b08 mov ecx,dword ptr ds:[eax] + 0x8b,0x11, // 01357ade 8b11 mov edx,dword ptr ds:[ecx] + 0x50, // 01357ae0 50 push eax + 0x8b,0x42, 0x04, // 01357ae1 8b42 04 mov eax,dword ptr ds:[edx+0x4] + 0xff,0xd0, // 01357ae4 ffd0 call eax + 0x8b,0x4c,0x24, 0x14 // 01357ae6 8b4c24 14 mov ecx,dword ptr ss:[esp+0x14] + }; + // Offset to the function call from the beginning of the function + //enum { addr_offset = 0x27 }; // Type2: hex(0x0133CEC7-0x0133CEA0) = hex(0x01357af9-0x1357ad2) + enum { hook_offset = -0xc }; // hook parameter + ULONG addr = module_base_; //- sizeof(bytes); + while (true) { + //addr += sizeof(bytes); // ++ so that each time return diff address + ULONG range = min(module_limit_ - addr, MAX_REL_ADDR); + addr = MemDbg::findBytes(bytes, sizeof(bytes), addr, addr + range); + if (!addr) { + //ITH_MSG(L"failed"); + ConsoleOutput("vnreng:Rejet: pattern not found"); + return false; + } + addr += sizeof(bytes); + // Push and call at once, i.e. push (0x68) and call (0xe8) + // 01185345 52 push edx + // 01185346 . 68 e4fe5501 push dotkares.0155fee4 ; |arg1 = 0155fee4 + // 0118534b . e8 1023f9ff call dotkares.01117660 ; \dotkares.00377660 + enum { start = 0x10, stop = 0x50 }; + // Different from FindRejetHook + DWORD i; + for (i = start; i < stop; i++) + if (*(WORD *)(addr + i - 1) == 0x6852 && *(BYTE *)(addr + i + 5) == 0xe8) // 0118534B-01185346 + break; + if (i < stop) { + addr += i; + break; + } + } //while(0xe8202474 != *(DWORD *)(addr - 3)); + + //GROWL_DWORD(addr - module_base_); // = 0xb3578 for 剣が君 + + ConsoleOutput("vnreng: INSERT Rejet"); + // The same as type2 + HookParam hp = {}; + hp.address = addr; //- 0xf; + hp.type = NO_CONTEXT|DATA_INDIRECT|FIXING_SPLIT; + hp.length_offset = 1; + hp.offset = hook_offset; + //hp.offset = -0x8; // Type1 + //hp.offset = -0xc; // Type2 + + NewHook(hp, "Rejet"); + return true; +} +} // unnamed Rejet + +bool InsertRejetHook() +{ return InsertRejetHook2() || InsertRejetHook1() || InsertRejetHook3(); } // insert hook2 first, since 2's pattern seems to be more unique + +/** + * jichi 4/1/2014: Insert AU hook + * Sample games: + * 英雼�戦姫: /HBN-8*4@4AD807 + * 英雼�戦姫GOLD: /HB-8*4@4ADB50 (alternative) + * + * /HBN-8*4@4AD807 + * - addr: 4904967 = 0x4ad807 + * - ind: 4 + * - length_offset: 1 + * - off: 4294967284 = 0xfffffff4 = -0xc + * - type: 1032 = 0x408 + * + * 004ad76a |. ff50 04 |call dword ptr ds:[eax+0x4] + * 004ad76d |. 48 |dec eax ; switch (cases 1..a) + * 004ad76e |. 83f8 09 |cmp eax,0x9 + * 004ad771 |. 0f87 37020000 |ja 英雼�戦.004ad9ae + * 004ad777 |. ff2485 2cda4a0>|jmp dword ptr ds:[eax*4+0x4ada2c] + * 004ad77e |> 83bf c4000000 >|cmp dword ptr ds:[edi+0xc4],0x1 ; case 1 of switch 004ad76d + * 004ad785 |. 75 35 |jnz short 英雼�戦.004ad7bc + * 004ad787 |. 39af c8000000 |cmp dword ptr ds:[edi+0xc8],ebp + * 004ad78d |. 72 08 |jb short 英雼�戦.004ad797 + * 004ad78f |. 8b87 b4000000 |mov eax,dword ptr ds:[edi+0xb4] + * 004ad795 |. eb 06 |jmp short 英雼�戦.004ad79d + * 004ad797 |> 8d87 b4000000 |lea eax,dword ptr ds:[edi+0xb4] + * 004ad79d |> 0fb608 |movzx ecx,byte ptr ds:[eax] + * 004ad7a0 |. 51 |push ecx + * 004ad7a1 |. e8 d15b2a00 |call 英雼�戦.00753377 + * 004ad7a6 |. 83c4 04 |add esp,0x4 + * 004ad7a9 |. 85c0 |test eax,eax + * 004ad7ab |. 74 0f |je short 英雼�戦.004ad7bc + * 004ad7ad |. 8d5424 20 |lea edx,dword ptr ss:[esp+0x20] + * 004ad7b1 |. 52 |push edx + * 004ad7b2 |. b9 88567a00 |mov ecx,英雼�戦.007a5688 + * 004ad7b7 |. e8 a40cf6ff |call 英雼�戦.0040e460 + * 004ad7bc |> 8b8424 e400000>|mov eax,dword ptr ss:[esp+0xe4] + * 004ad7c3 |. 8a48 01 |mov cl,byte ptr ds:[eax+0x1] + * 004ad7c6 |. 84c9 |test cl,cl + * 004ad7c8 |. 75 2e |jnz short 英雼�戦.004ad7f8 + * 004ad7ca |. 8d9f b0000000 |lea ebx,dword ptr ds:[edi+0xb0] + * 004ad7d0 |. be ac6e7a00 |mov esi,英雼�戦.007a6eac + * 004ad7d5 |. 8bcb |mov ecx,ebx + * 004ad7d7 |. e8 e40af6ff |call 英雼�戦.0040e2c0 + * 004ad7dc |. 84c0 |test al,al + * 004ad7de |. 0f84 ca010000 |je 英雼�戦.004ad9ae + * 004ad7e4 |. be a86e7a00 |mov esi,英雼�戦.007a6ea8 + * 004ad7e9 |. 8bcb |mov ecx,ebx + * 004ad7eb |. e8 d00af6ff |call 英雼�戦.0040e2c0 + * 004ad7f0 |. 84c0 |test al,al + * 004ad7f2 |. 0f84 b6010000 |je 英雼�戦.004ad9ae + * 004ad7f8 |> 6a 00 |push 0x0 + * 004ad7fa |. 8d8f b0000000 |lea ecx,dword ptr ds:[edi+0xb0] + * 004ad800 |. 83c8 ff |or eax,0xffffffff + * 004ad803 |. 8d5c24 24 |lea ebx,dword ptr ss:[esp+0x24] + * 004ad807 |. e8 740cf6ff |call 英雼�戦.0040e480 ; jichi: hook here + * 004ad80c |. e9 9d010000 |jmp 英雼�戦.004ad9ae + * 004ad811 |> 8b8c24 e400000>|mov ecx,dword ptr ss:[esp+0xe4] ; case 4 of switch 004ad76d + * 004ad818 |. 8039 00 |cmp byte ptr ds:[ecx],0x0 + * 004ad81b |. 0f84 8d010000 |je 英雼�戦.004ad9ae + * 004ad821 |. b8 04000000 |mov eax,0x4 + * 004ad826 |. b9 c86e7a00 |mov ecx,英雼�戦.007a6ec8 ; ascii "
" + * 004ad82b |. 8d5424 20 |lea edx,dword ptr ss:[esp+0x20] + * 004ad82f |. e8 3c0df6ff |call 英雼�戦.0040e570 + * 004ad834 |. e9 75010000 |jmp 英雼�戦.004ad9ae + * 004ad839 |> 8bbf b4000000 |mov edi,dword ptr ds:[edi+0xb4] ; case 5 of switch 004ad76d + */ +bool InsertTencoHook() +{ + const BYTE bytes[] = { + 0x6a, 0x00, // 004ad7f8 |> 6a 00 |push 0x0 + 0x8d,0x8f, 0xb0,0x00,0x00,0x00, // 004ad7fa |. 8d8f b0000000 |lea ecx,dword ptr ds:[edi+0xb0] + 0x83,0xc8, 0xff, // 004ad800 |. 83c8 ff |or eax,0xffffffff + 0x8d,0x5c,0x24, 0x24, // 004ad803 |. 8d5c24 24 |lea ebx,dword ptr ss:[esp+0x24] + 0xe8 //740cf6ff // 004ad807 |. e8 740cf6ff |call 英雼�戦.0040e480 ; jichi: hook here + }; + enum { addr_offset = sizeof(bytes) - 1 }; + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //reladdr = 0x4ad807; + if (!addr) { + ConsoleOutput("vnreng:Tenco: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.length_offset = 1; + hp.index = 4; + hp.offset = -0xc; + hp.type = NO_CONTEXT|DATA_INDIRECT; + + ConsoleOutput("vnreng: INSERT Tenco"); + NewHook(hp, "Tenco"); + return true; +} + +/** + * jichi 4/1/2014: Insert AOS hook + * About 彩斤�: http://erogetrailers.com/brand/165 + * About AOS: http://asmodean.reverse.net/pages/exaos.html + * + * Sample games: + * + * [140228] [Sugar Pot] 恋する少女と想�キセキ V1.00 H-CODE by �쿿 + * - /HB8*0@3C2F0:恋する少女と想�キセキ.exe + * - /HBC*0@3C190:恋する少女と想�キセキ.exe + * + * [120224] [Sugar Pot] ヂ�モノツキ + * + * LiLiM games + * + * /HB8*0@3C2F0:恋する少女と想�キセ + * - addr: 246512 = 0x3c2f0 + * - length_offset: 1 + * - module: 1814017450 + * - off: 8 + * - type: 72 = 0x48 + * + * 00e3c2ed cc int3 + * 00e3c2ee cc int3 + * 00e3c2ef cc int3 + * 00e3c2f0 /$ 51 push ecx ; jichi: hook here, function starts + * 00e3c2f1 |. a1 0c64eb00 mov eax,dword ptr ds:[0xeb640c] + * 00e3c2f6 |. 8b0d 7846eb00 mov ecx,dword ptr ds:[0xeb4678] + * 00e3c2fc |. 53 push ebx + * 00e3c2fd |. 55 push ebp + * 00e3c2fe |. 8b6c24 10 mov ebp,dword ptr ss:[esp+0x10] + * 00e3c302 |. 56 push esi + * 00e3c303 |. 8b35 c446eb00 mov esi,dword ptr ds:[0xeb46c4] + * 00e3c309 |. 57 push edi + * 00e3c30a |. 0fb63d c746eb00 movzx edi,byte ptr ds:[0xeb46c7] + * 00e3c311 |. 81e6 ffffff00 and esi,0xffffff + * 00e3c317 |. 894424 18 mov dword ptr ss:[esp+0x18],eax + * 00e3c31b |. 85ff test edi,edi + * 00e3c31d |. 74 6b je short 恋する�00e3c38a + * 00e3c31f |. 8bd9 mov ebx,ecx + * 00e3c321 |. 85db test ebx,ebx + * 00e3c323 |. 74 17 je short 恋する�00e3c33c + * 00e3c325 |. 8b4b 28 mov ecx,dword ptr ds:[ebx+0x28] + * 00e3c328 |. 56 push esi ; /color + * 00e3c329 |. 51 push ecx ; |hdc + * 00e3c32a |. ff15 3c40e800 call dword ptr ds:[<&gdi32.SetTextColor>>; \settextcolor + * 00e3c330 |. 89b3 c8000000 mov dword ptr ds:[ebx+0xc8],esi + * 00e3c336 |. 8b0d 7846eb00 mov ecx,dword ptr ds:[0xeb4678] + * 00e3c33c |> 0fbf55 1c movsx edx,word ptr ss:[ebp+0x1c] + * 00e3c340 |. 0fbf45 0a movsx eax,word ptr ss:[ebp+0xa] + * 00e3c344 |. 0fbf75 1a movsx esi,word ptr ss:[ebp+0x1a] + * 00e3c348 |. 03d7 add edx,edi + * 00e3c34a |. 03c2 add eax,edx + * 00e3c34c |. 0fbf55 08 movsx edx,word ptr ss:[ebp+0x8] + * 00e3c350 |. 03f7 add esi,edi + * 00e3c352 |. 03d6 add edx,esi + * 00e3c354 |. 85c9 test ecx,ecx + * 00e3c356 |. 74 32 je short 恋する�00e3c38a + */ +bool InsertAOSHook() +{ + // jichi 4/2/2014: The starting of this function is different from ヂ�モノツキ + // So, use a pattern in the middle of the function instead. + // + //const BYTE bytes[] = { + // 0x51, // 00e3c2f0 /$ 51 push ecx ; jichi: hook here, function begins + // 0xa1, 0x0c,0x64,0xeb,0x00, // 00e3c2f1 |. a1 0c64eb00 mov eax,dword ptr ds:[0xeb640c] + // 0x8b,0x0d, 0x78,0x46,0xeb,0x00, // 00e3c2f6 |. 8b0d 7846eb00 mov ecx,dword ptr ds:[0xeb4678] + // 0x53, // 00e3c2fc |. 53 push ebx + // 0x55, // 00e3c2fd |. 55 push ebp + // 0x8b,0x6c,0x24, 0x10, // 00e3c2fe |. 8b6c24 10 mov ebp,dword ptr ss:[esp+0x10] + // 0x56, // 00e3c302 |. 56 push esi + // 0x8b,0x35, 0xc4,0x46,0xeb,0x00, // 00e3c303 |. 8b35 c446eb00 mov esi,dword ptr ds:[0xeb46c4] + // 0x57, // 00e3c309 |. 57 push edi + // 0x0f,0xb6,0x3d, 0xc7,0x46,0xeb,0x00, // 00e3c30a |. 0fb63d c746eb00 movzx edi,byte ptr ds:[0xeb46c7] + // 0x81,0xe6, 0xff,0xff,0xff,0x00 // 00e3c311 |. 81e6 ffffff00 and esi,0xffffff + //}; + //enum { addr_offset = 0 }; + + const BYTE bytes[] = { + 0x0f,0xbf,0x55, 0x1c, // 00e3c33c |> 0fbf55 1c movsx edx,word ptr ss:[ebp+0x1c] + 0x0f,0xbf,0x45, 0x0a, // 00e3c340 |. 0fbf45 0a movsx eax,word ptr ss:[ebp+0xa] + 0x0f,0xbf,0x75, 0x1a, // 00e3c344 |. 0fbf75 1a movsx esi,word ptr ss:[ebp+0x1a] + 0x03,0xd7, // 00e3c348 |. 03d7 add edx,edi + 0x03,0xc2, // 00e3c34a |. 03c2 add eax,edx + 0x0f,0xbf,0x55, 0x08, // 00e3c34c |. 0fbf55 08 movsx edx,word ptr ss:[ebp+0x8] + 0x03,0xf7, // 00e3c350 |. 03f7 add esi,edi + 0x03,0xd6, // 00e3c352 |. 03d6 add edx,esi + 0x85,0xc9 // 00e3c354 |. 85c9 test ecx,ecx + }; + enum { addr_offset = 0x00e3c2f0 - 0x00e3c33c }; // distance to the beginning of the function, which is 0x51 (push ecx) + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //GROWL(reladdr); + if (!addr) { + ConsoleOutput("vnreng:AOS: pattern not found"); + return false; + } + addr += addr_offset; + //GROWL(addr); + enum { push_ecx = 0x51 }; // beginning of the function + if (*(BYTE *)addr != push_ecx) { + ConsoleOutput("vnreng:AOS: beginning of the function not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.length_offset = 1; + hp.offset = 8; + hp.type = DATA_INDIRECT; + + ConsoleOutput("vnreng: INSERT AOS"); + NewHook(hp, "AOS"); + return true; +} + +/** + * jichi 1/10/2014: Rai7 puk + * See: http://www.hongfire.com/forum/showthread.php/421909-%E3%80%90Space-Warfare-Sim%E3%80%91Rai-7-PUK/page10 + * See: www.hongfire.com/forum/showthread.php/421909-%E3%80%90Space-Warfare-Sim%E3%80%91Rai-7-PUK/page19 + * + * Version: R7P3-13v2(131220).rar, pass: sstm http://pan.baidu.com/share/home?uk=3727185265#category/type=0 + * /HS0@409524 + */ +//bool InsertRai7Hook() +//{ +//} + +/** + * jichi 10/1/2013: sol-fa-soft + * See (tryguy): http://www.hongfire.com/forum/printthread.php?t=36807&pp=10&page=639 + * + * @tryguy + * [sol-fa-soft] + * 17 スク水不要� /HA4@4AD140 + * 18 ななちも�とぁ�しょ: /HA4@5104A0 + * 19 発惁�んこぁ�� /HA4@51D720 + * 20 わたし�たまごさ� /HA4@4968E0 + * 21 修学旡�夜更かし� /HA4@49DC00 + * 22 おぼえたてキヂ�: /HA4@49DDB0 + * 23 ちっさい巫女さんSOS: /HA4@4B4AA0 + * 24 はじめてのお�ろやさん: /HA4@4B5600 + * 25 はきわすれ愛好� /HA4@57E360 + * 26 朝っぱらから発惮�� /HA4@57E360 + * 27 となり�ヴァンパイア: /HA4@5593B0 + * 28 麦わら帽子と水辺の妖精: /HA4@5593B0 + * 29 海と温泉と夏休み: /HA4@6DE8E0 + * 30 駏�子屋さん繁盛� /HA4@6DEC90 + * 31 浴衣の下�… �神社で発見�ノ�パン少女 /HA4@6DEC90 + * 32 プ�ルのじか�スク水不要�: /HA4@62AE10 + * 33 妹のお泊まり� /HA4@6087A0 + * 34 薝�少女: /HA4@6087A0 + * 35 あや�Princess Intermezzo: /HA4@609BF0 + * + * SG01 男湯�: /HA4@6087A0 + * + * c71 真�の大晦日CD: /HA4@516b50 + * c78 sol-fa-soft真夏�お気楽CD: /HA4@6DEC90 + * + * Example: 35 あや�Princess Intermezzo: /HA4@609BF0 + * - addr: 6331376 = 0x609bf0 + * - length_offset: 1 + * - off: 4 + * - type: 4 + * + * ASCII: あや� addr_offset = -50 + * Function starts + * 00609bef /> cc int3 + * 00609bf0 /> 55 push ebp + * 00609bf1 |. 8bec mov ebp,esp + * 00609bf3 |. 64:a1 00000000 mov eax,dword ptr fs:[0] + * 00609bf9 |. 6a ff push -0x1 + * 00609bfb |. 68 e1266300 push あや�006326e1 + * 00609c00 |. 50 push eax + * 00609c01 |. 64:8925 000000>mov dword ptr fs:[0],esp + * 00609c08 |. 81ec 80000000 sub esp,0x80 + * 00609c0e |. 53 push ebx + * 00609c0f |. 8b5d 08 mov ebx,dword ptr ss:[ebp+0x8] + * 00609c12 |. 57 push edi + * 00609c13 |. 8bf9 mov edi,ecx + * 00609c15 |. 8b07 mov eax,dword ptr ds:[edi] + * 00609c17 |. 83f8 02 cmp eax,0x2 + * 00609c1a |. 75 1f jnz short あや�00609c3b + * 00609c1c |. 3b5f 40 cmp ebx,dword ptr ds:[edi+0x40] + * 00609c1f |. 75 1a jnz short あや�00609c3b + * 00609c21 |. 837f 44 00 cmp dword ptr ds:[edi+0x44],0x0 + * 00609c25 |. 74 14 je short あや�00609c3b + * 00609c27 |. 5f pop edi + * 00609c28 |. b0 01 mov al,0x1 + * 00609c2a |. 5b pop ebx + * 00609c2b |. 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 00609c2e |. 64:890d 000000>mov dword ptr fs:[0],ecx + * 00609c35 |. 8be5 mov esp,ebp + * 00609c37 |. 5d pop ebp + * 00609c38 |. c2 0400 retn 0x4 + * Function stops + * + * WideChar: こいな�小田舎で初恋x中出しセクシャルライ�, addr_offset = -53 + * 0040653a cc int3 + * 0040653b cc int3 + * 0040653c cc int3 + * 0040653d cc int3 + * 0040653e cc int3 + * 0040653f cc int3 + * 00406540 > 55 push ebp + * 00406541 . 8bec mov ebp,esp + * 00406543 . 64:a1 00000000 mov eax,dword ptr fs:[0] + * 00406549 . 6a ff push -0x1 + * 0040654b . 68 f1584300 push erondo01.004358f1 + * 00406550 . 50 push eax + * 00406551 . 64:8925 000000>mov dword ptr fs:[0],esp + * 00406558 . 83ec 6c sub esp,0x6c + * 0040655b . 53 push ebx + * 0040655c . 8bd9 mov ebx,ecx + * 0040655e . 57 push edi + * 0040655f . 8b03 mov eax,dword ptr ds:[ebx] + * 00406561 . 8b7d 08 mov edi,dword ptr ss:[ebp+0x8] + * 00406564 . 83f8 02 cmp eax,0x2 + * 00406567 . 75 1f jnz short erondo01.00406588 + * 00406569 . 3b7b 3c cmp edi,dword ptr ds:[ebx+0x3c] + * 0040656c . 75 1a jnz short erondo01.00406588 + * 0040656e . 837b 40 00 cmp dword ptr ds:[ebx+0x40],0x0 + * 00406572 . 74 14 je short erondo01.00406588 + * 00406574 . 5f pop edi + * 00406575 . b0 01 mov al,0x1 + * 00406577 . 5b pop ebx + * 00406578 . 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 0040657b . 64:890d 000000>mov dword ptr fs:[0],ecx + * 00406582 . 8be5 mov esp,ebp + * 00406584 . 5d pop ebp + * 00406585 . c2 0400 retn 0x4 + * + * WideChar: 祝福�鐘�音は、桜色の風と共に, addr_offset = -50, + * FIXME: how to know if it is UTF16? This game has /H code, though: + * + * /HA-4@94D62:shukufuku_main.exe + * + * 011d619e cc int3 + * 011d619f cc int3 + * 011d61a0 55 push ebp + * 011d61a1 8bec mov ebp,esp + * 011d61a3 64:a1 00000000 mov eax,dword ptr fs:[0] + * 011d61a9 6a ff push -0x1 + * 011d61ab 68 d1811f01 push .011f81d1 + * 011d61b0 50 push eax + * 011d61b1 64:8925 00000000 mov dword ptr fs:[0],esp + * 011d61b8 81ec 80000000 sub esp,0x80 + * 011d61be 53 push ebx + * 011d61bf 8b5d 08 mov ebx,dword ptr ss:[ebp+0x8] + * 011d61c2 57 push edi + * 011d61c3 8bf9 mov edi,ecx + * 011d61c5 8b07 mov eax,dword ptr ds:[edi] + * 011d61c7 83f8 02 cmp eax,0x2 + * 011d61ca 75 1f jnz short .011d61eb + * 011d61cc 3b5f 40 cmp ebx,dword ptr ds:[edi+0x40] + * 011d61cf 75 1a jnz short .011d61eb + * 011d61d1 837f 44 00 cmp dword ptr ds:[edi+0x44],0x0 + * 011d61d5 74 14 je short .011d61eb + * 011d61d7 5f pop edi + * 011d61d8 b0 01 mov al,0x1 + * 011d61da 5b pop ebx + * 011d61db 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 011d61de 64:890d 00000000 mov dword ptr fs:[0],ecx + * 011d61e5 8be5 mov esp,ebp + * 011d61e7 5d pop ebp + * 011d61e8 c2 0400 retn 0x4 + */ +bool InsertScenarioPlayerHook() +{ + //const BYTE bytes[] = { + // 0x53, // 00609c0e |. 53 push ebx + // 0x8b,0x5d,0x08, // 00609c0f |. 8b5d 08 mov ebx,dword ptr ss:[ebp+0x8] + // 0x57, // 00609c12 |. 57 push edi + // 0x8b,0xf9, // 00609c13 |. 8bf9 mov edi,ecx + // 0x8b,0x07, // 00609c15 |. 8b07 mov eax,dword ptr ds:[edi] + // 0x83,0xf8, 0x02, // 00609c17 |. 83f8 02 cmp eax,0x2 + // 0x75, 0x1f, // 00609c1a |. 75 1f jnz short あや�00609c3b + // 0x3b,0x5f, 0x40, // 00609c1c |. 3b5f 40 cmp ebx,dword ptr ds:[edi+0x40] + // 0x75, 0x1a, // 00609c1f |. 75 1a jnz short あや�00609c3b + // 0x83,0x7f, 0x44, 0x00, // 00609c21 |. 837f 44 00 cmp dword ptr ds:[edi+0x44],0x0 + // 0x74, 0x14, // 00609c25 |. 74 14 je short あや�00609c3b + //}; + //enum { addr_offset = 0x00609bf0 - 0x00609c0e }; // distance to the beginning of the function + + const BYTE bytes[] = { + 0x74, 0x14, // 00609c25 |. 74 14 je short あや�00609c3b + 0x5f, // 00609c27 |. 5f pop edi + 0xb0, 0x01, // 00609c28 |. b0 01 mov al,0x1 + 0x5b, // 00609c2a |. 5b pop ebx + 0x8b,0x4d, 0xf4 // 00609c2b |. 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + }; + enum { // distance to the beginning of the function + addr_offset_A = 0x00609bf0 - 0x00609c25 // -53 + , addr_offset_W = 0x00406540 - 0x00406572 // -50 + }; + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG start = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + if (!start) { + ConsoleOutput("vnreng:ScenarioPlayer: pattern not found"); + return false; + } + + DWORD addr = MemDbg::findEnclosingAlignedFunction(start, 80); // range is around 50, use 80 + + enum : BYTE { push_ebp = 0x55 }; // 011d4c80 /$ 55 push ebp + if (!addr || *(BYTE *)addr != push_ebp) { + ConsoleOutput("vnreng:ScenarioPlayer: pattern found but the function offset is invalid"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.length_offset = 1; + hp.offset = 4; + if (addr - start == addr_offset_W) { + hp.type = USING_UNICODE; + ConsoleOutput("vnreng: INSERT ScenarioPlayerW"); + NewHook(hp, "ScenarioPlayerW"); + } else { + hp.type = BIG_ENDIAN; // 4 + ConsoleOutput("vnreng: INSERT ScenarioPlayerA"); + NewHook(hp, "ScenarioPlayerA"); + } + return true; +} + +/** + * jichi 4/19/2014: Marine Heart + * See: http://blgames.proboards.com/post/1984 + * http://www.yaoiotaku.com/forums/threads/11440-huge-bl-game-torrent + * + * Issue: The extracted text someitems has limited repetition + * TODO: It might be better to use FindCallAndEntryAbs for gdi32.CreateFontA? + * See how FindCallAndEntryAbs is used in Majiro. + * + * 妖恋愛奭�神サマ�堕し方/HS4*0@40D160 + * - addr: 4247904 = 0x40d160 + * - off: 4 + * - type: 9 + * + * Function starts + * 0040d160 /$ 55 push ebp ; jichi: hook here + * 0040d161 |. 8bec mov ebp,esp + * 0040d163 |. 83c4 90 add esp,-0x70 + * 0040d166 |. 33c0 xor eax,eax + * 0040d168 |. 53 push ebx + * 0040d169 |. 56 push esi + * 0040d16a |. 57 push edi + * 0040d16b |. 8b75 08 mov esi,dword ptr ss:[ebp+0x8] + * 0040d16e |. c745 cc 281e4800 mov dword ptr ss:[ebp-0x34],saisys.00481> + * 0040d175 |. 8965 d0 mov dword ptr ss:[ebp-0x30],esp + * 0040d178 |. c745 c8 d0d14700 mov dword ptr ss:[ebp-0x38], + * 0040d17f |. 66:c745 d4 0000 mov word ptr ss:[ebp-0x2c],0x0 + * 0040d185 |. 8945 e0 mov dword ptr ss:[ebp-0x20],eax + * 0040d188 |. 64:8b15 00000000 mov edx,dword ptr fs:[0] + * 0040d18f |. 8955 c4 mov dword ptr ss:[ebp-0x3c],edx + * 0040d192 |. 8d4d c4 lea ecx,dword ptr ss:[ebp-0x3c] + * 0040d195 |. 64:890d 00000000 mov dword ptr fs:[0],ecx + * 0040d19c |. 8b05 741c4800 mov eax,dword ptr ds:[0x481c74] + * 0040d1a2 |. 8945 bc mov dword ptr ss:[ebp-0x44],eax + * 0040d1a5 |. 8b05 781c4800 mov eax,dword ptr ds:[0x481c78] + * 0040d1ab |. 8945 c0 mov dword ptr ss:[ebp-0x40],eax + * 0040d1ae |. 8d46 24 lea eax,dword ptr ds:[esi+0x24] + * 0040d1b1 |. 8b56 14 mov edx,dword ptr ds:[esi+0x14] + * 0040d1b4 |. 8955 bc mov dword ptr ss:[ebp-0x44],edx + * 0040d1b7 |. 8b10 mov edx,dword ptr ds:[eax] + * 0040d1b9 |. 85d2 test edx,edx + * 0040d1bb |. 74 04 je short saisys.0040d1c1 + * 0040d1bd |. 8b08 mov ecx,dword ptr ds:[eax] + * 0040d1bf |. eb 05 jmp short saisys.0040d1c6 + * 0040d1c1 |> b9 9b1c4800 mov ecx,saisys.00481c9b + * 0040d1c6 |> 51 push ecx ; /facename + * 0040d1c7 |. 6a 01 push 0x1 ; |pitchandfamily = fixed_pitch|ff_dontcare + * 0040d1c9 |. 6a 03 push 0x3 ; |quality = 3. + * 0040d1cb |. 6a 00 push 0x0 ; |clipprecision = clip_default_precis + * 0040d1cd |. 6a 00 push 0x0 ; |outputprecision = out_default_precis + * 0040d1cf |. 68 80000000 push 0x80 ; |charset = 128. + * 0040d1d4 |. 6a 00 push 0x0 ; |strikeout = false + * 0040d1d6 |. 6a 00 push 0x0 ; |underline = false + * 0040d1d8 |. 6a 00 push 0x0 ; |italic = false + * 0040d1da |. 68 90010000 push 0x190 ; |weight = fw_normal + * 0040d1df |. 6a 00 push 0x0 ; |orientation = 0x0 + * 0040d1e1 |. 6a 00 push 0x0 ; |escapement = 0x0 + * 0040d1e3 |. 6a 00 push 0x0 ; |width = 0x0 + * 0040d1e5 |. 8b46 04 mov eax,dword ptr ds:[esi+0x4] ; | + * 0040d1e8 |. 50 push eax ; |height + * 0040d1e9 |. e8 00fa0600 call ; \createfonta + * 0040d1ee |. 8945 b8 mov dword ptr ss:[ebp-0x48],eax + * 0040d1f1 |. 8b55 b8 mov edx,dword ptr ss:[ebp-0x48] + * 0040d1f4 |. 85d2 test edx,edx + * 0040d1f6 |. 75 14 jnz short saisys.0040d20c + */ +bool InsertMarineHeartHook() +{ + // FIXME: Why this does not work?! + // jichi 6/3/2014: CreateFontA is only called once in this function + // 0040d160 /$ 55 push ebp ; jichi: hook here + // 0040d161 |. 8bec mov ebp,esp + //ULONG addr = Util::FindCallAndEntryAbs((DWORD)CreateFontA, module_limit_ - module_base_, module_base_, 0xec8b); + + const BYTE bytes[] = { + 0x51, // 0040d1c6 |> 51 push ecx ; /facename + 0x6a, 0x01, // 0040d1c7 |. 6a 01 push 0x1 ; |pitchandfamily = fixed_pitch|ff_dontcare + 0x6a, 0x03, // 0040d1c9 |. 6a 03 push 0x3 ; |quality = 3. + 0x6a, 0x00, // 0040d1cb |. 6a 00 push 0x0 ; |clipprecision = clip_default_precis + 0x6a, 0x00, // 0040d1cd |. 6a 00 push 0x0 ; |outputprecision = out_default_precis + 0x68, 0x80,0x00,0x00,0x00, // 0040d1cf |. 68 80000000 push 0x80 ; |charset = 128. + 0x6a, 0x00, // 0040d1d4 |. 6a 00 push 0x0 ; |strikeout = false + 0x6a, 0x00, // 0040d1d6 |. 6a 00 push 0x0 ; |underline = false + 0x6a, 0x00, // 0040d1d8 |. 6a 00 push 0x0 ; |italic = false + 0x68, 0x90,0x01,0x00,0x00, // 0040d1da |. 68 90010000 push 0x190 ; |weight = fw_normal + 0x6a, 0x00, // 0040d1df |. 6a 00 push 0x0 ; |orientation = 0x0 + 0x6a, 0x00, // 0040d1e1 |. 6a 00 push 0x0 ; |escapement = 0x0 + 0x6a, 0x00, // 0040d1e3 |. 6a 00 push 0x0 ; |width = 0x0 0x8b,0x46, 0x04, + 0x8b,0x46, 0x04, // 0040d1e5 |. 8b46 04 mov eax,dword ptr ds:[esi+0x4] ; | + 0x50, // 0040d1e8 |. 50 push eax ; |height + 0xe8, 0x00,0xfa,0x06,0x00 // 0040d1e9 |. e8 00fa0600 call ; \createfonta + }; + enum { addr_offset = 0x0040d160 - 0x0040d1c6 }; // distance to the beginning of the function + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //GROWL_DWORD(reladdr); + if (!addr) { + ConsoleOutput("vnreng:MarineHeart: pattern not found"); + return false; + } + + addr += addr_offset; + //addr = 0x40d160; + //GROWL_DWORD(addr); + enum : BYTE { push_ebp = 0x55 }; // 011d4c80 /$ 55 push ebp + if (*(BYTE *)addr != push_ebp) { + ConsoleOutput("vnreng:MarineHeart: pattern found but the function offset is invalid"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.offset = 4; + hp.type = USING_STRING|DATA_INDIRECT; // = 9 + + ConsoleOutput("vnreng: INSERT MarineHeart"); + NewHook(hp, "MarineHeart"); + return true; +} + +/** + * jichi 6/1/2014: + * Observations from 愛姉妹4 + * - Scenario: arg1 + 4*5 is 0, arg1+0xc is address of the text + * - Character: arg1 + 4*10 is 0, arg1+0xc is text + */ +static inline size_t _elf_strlen(LPCSTR p) // limit search address which might be bad +{ + //CC_ASSERT(p); + for (size_t i = 0; i < VNR_TEXT_CAPACITY; i++) + if (!*p++) + return i; + return 0; // when len >= VNR_TEXT_CAPACITY +} + +static void SpecialHookElf(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + //DWORD arg1 = *(DWORD *)(esp_base + 0x4); + DWORD arg1 = argof(1, esp_base); + DWORD arg2_scene = arg1 + 4*5, + arg2_chara = arg1 + 4*10; + DWORD text; //= 0; // This variable will be killed + if (*(DWORD *)arg2_scene == 0) { + text = *(DWORD *)(arg2_scene + 4*3); + if (!text || ::IsBadReadPtr((LPCVOID)text, 1)) // Text from scenario could be bad when open backlog while the character is speaking + return; + *split = 1; + } else if (*(DWORD *)arg2_chara == 0) { + text = arg2_chara + 4*3; + *split = 2; + } else + return; + //if (text && text < MemDbg::UserMemoryStopAddress) { + *len = _elf_strlen((LPCSTR)text); // in case the text is bad but still readable + //*len = ::strlen((LPCSTR)text); + *data = text; +} + +/** + * jichi 5/31/2014: elf's + * Type1: SEXヂ�ーチャー剛史 trial, reladdr = 0x2f0f0, 2 parameters + * Type2: 愛姉妹4, reladdr = 0x2f9b0, 3 parameters + * + * IDA: sub_42F9B0 proc near ; bp-based frame + * var_8 = dword ptr -8 + * var_4 = byte ptr -4 + * var_3 = word ptr -3 + * arg_0 = dword ptr 8 + * arg_4 = dword ptr 0Ch + * arg_8 = dword ptr 10h + * + * Call graph (Type2): + * 0x2f9b0 ; hook here + * > 0x666a0 ; called multiple time + * > TextOutA ; there are two TextOutA, the second is the right one + * + * Function starts (Type1), pattern offset: 0xc + * - 012ef0f0 /$ 55 push ebp ; jichi: hook + * - 012ef0f1 |. 8bec mov ebp,esp + * - 012ef0f3 |. 83ec 10 sub esp,0x10 + * - 012ef0f6 |. 837d 0c 00 cmp dword ptr ss:[ebp+0xc],0x0 + * - 012ef0fa |. 53 push ebx + * - 012ef0fb |. 56 push esi + * - 012ef0fc |. 75 0f jnz short stt_tria.012ef10d ; jicchi: pattern starts + * - 012ef0fe |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * - 012ef101 |. 8b48 04 mov ecx,dword ptr ds:[eax+0x4] + * - 012ef104 |. 8b91 90000000 mov edx,dword ptr ds:[ecx+0x90] ; jichi: pattern stops + * - 012ef10a |. 8955 0c mov dword ptr ss:[ebp+0xc],edx + * - 012ef10d |> 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + * - 012ef110 |. 8b51 04 mov edx,dword ptr ds:[ecx+0x4] + * - 012ef113 |. 33c0 xor eax,eax + * - 012ef115 |. c645 f8 00 mov byte ptr ss:[ebp-0x8],0x0 + * - 012ef119 |. 66:8945 f9 mov word ptr ss:[ebp-0x7],ax + * - 012ef11d |. 8b82 b0000000 mov eax,dword ptr ds:[edx+0xb0] + * - 012ef123 |. 8945 f4 mov dword ptr ss:[ebp-0xc],eax + * - 012ef126 |. 33db xor ebx,ebx + * - 012ef128 |> 8b4f 20 /mov ecx,dword ptr ds:[edi+0x20] + * - 012ef12b |. 83f9 10 |cmp ecx,0x10 + * + * Function starts (Type2), pattern offset: 0x10 + * - 0093f9b0 /$ 55 push ebp ; jichi: hook here + * - 0093f9b1 |. 8bec mov ebp,esp + * - 0093f9b3 |. 83ec 08 sub esp,0x8 + * - 0093f9b6 |. 837d 10 00 cmp dword ptr ss:[ebp+0x10],0x0 + * - 0093f9ba |. 53 push ebx + * - 0093f9bb |. 8b5d 0c mov ebx,dword ptr ss:[ebp+0xc] + * - 0093f9be |. 56 push esi + * - 0093f9bf |. 57 push edi + * - 0093f9c0 |. 75 0f jnz short silkys.0093f9d1 ; jichi: pattern starts + * - 0093f9c2 |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * - 0093f9c5 |. 8b48 04 mov ecx,dword ptr ds:[eax+0x4] + * - 0093f9c8 |. 8b91 90000000 mov edx,dword ptr ds:[ecx+0x90] ; jichi: pattern stops + * - 0093f9ce |. 8955 10 mov dword ptr ss:[ebp+0x10],edx + * - 0093f9d1 |> 33c0 xor eax,eax + * - 0093f9d3 |. c645 fc 00 mov byte ptr ss:[ebp-0x4],0x0 + * - 0093f9d7 |. 66:8945 fd mov word ptr ss:[ebp-0x3],ax + * - 0093f9db |. 33ff xor edi,edi + * - 0093f9dd |> 8b53 20 /mov edx,dword ptr ds:[ebx+0x20] + * - 0093f9e0 |. 8d4b 0c |lea ecx,dword ptr ds:[ebx+0xc] + * - 0093f9e3 |. 83fa 10 |cmp edx,0x10 + */ +bool InsertElfHook() +{ + const BYTE bytes[] = { + //0x55, // 0093f9b0 /$ 55 push ebp ; jichi: hook here + //0x8b,0xec, // 0093f9b1 |. 8bec mov ebp,esp + //0x83,0xec, 0x08, // 0093f9b3 |. 83ec 08 sub esp,0x8 + //0x83,0x7d, 0x10, 0x00, // 0093f9b6 |. 837d 10 00 cmp dword ptr ss:[ebp+0x10],0x0 + //0x53, // 0093f9ba |. 53 push ebx + //0x8b,0x5d, 0x0c, // 0093f9bb |. 8b5d 0c mov ebx,dword ptr ss:[ebp+0xc] + //0x56, // 0093f9be |. 56 push esi + //0x57, // 0093f9bf |. 57 push edi + 0x75, 0x0f, // 0093f9c0 |. 75 0f jnz short silkys.0093f9d1 + 0x8b,0x45, 0x08, // 0093f9c2 |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + 0x8b,0x48, 0x04, // 0093f9c5 |. 8b48 04 mov ecx,dword ptr ds:[eax+0x4] + 0x8b,0x91, 0x90,0x00,0x00,0x00 // 0093f9c8 |. 8b91 90000000 mov edx,dword ptr ds:[ecx+0x90] + }; + //enum { addr_offset = 0xc }; + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //GROWL_DWORD(addr); + //addr = 0x42f170; // 愛姉妹4 Trial + //reladdr = 0x2f9b0; // 愛姉妹4 + //reladdr = 0x2f0f0; // SEXヂ�ーチャー剛史 trial + if (!addr) { + ConsoleOutput("vnreng:Elf: pattern not found"); + return false; + } + + enum : BYTE { push_ebp = 0x55 }; + for (int i = 0; i < 0x20; i++, addr--) // value of i is supposed to be 0xc or 0x10 + if (*(BYTE *)addr == push_ebp) { // beginning of the function + + HookParam hp = {}; + hp.address = addr; + hp.text_fun = SpecialHookElf; + hp.type = USING_STRING|NO_CONTEXT; // = 9 + + ConsoleOutput("vnreng: INSERT Elf"); + NewHook(hp, "Elf"); + return true; + } + ConsoleOutput("vnreng:Elf: function not found"); + return false; +} + +/** jichi: 6/17/2015 + * Sample games + * - 堕ちてぁ�新妻 trial + * - 根雪の幻影 trial + * + * This function is found by backtracking GetGlyphOutlineA. + * There are two GetGlyphOutlineA, which are in the same function. + * That function are called by two other functions. + * The second function is hooked. + * + * 堕ちてぁ�新妻 + * baseaddr = 08e0000 + * + * 0096652E CC INT3 + * 0096652F CC INT3 + * 00966530 55 PUSH EBP + * 00966531 8BEC MOV EBP,ESP + * 00966533 83EC 18 SUB ESP,0x18 + * 00966536 A1 00109F00 MOV EAX,DWORD PTR DS:[0x9F1000] + * 0096653B 33C5 XOR EAX,EBP + * 0096653D 8945 FC MOV DWORD PTR SS:[EBP-0x4],EAX + * 00966540 53 PUSH EBX + * 00966541 8B5D 0C MOV EBX,DWORD PTR SS:[EBP+0xC] + * 00966544 56 PUSH ESI + * 00966545 8B75 08 MOV ESI,DWORD PTR SS:[EBP+0x8] + * 00966548 57 PUSH EDI + * 00966549 6A 00 PUSH 0x0 + * 0096654B 894D EC MOV DWORD PTR SS:[EBP-0x14],ECX + * 0096654E 8B0D FCB7A200 MOV ECX,DWORD PTR DS:[0xA2B7FC] + * 00966554 68 90D29D00 PUSH .009DD290 ; ASCII "/Config/SceneSkip" + * 00966559 895D F0 MOV DWORD PTR SS:[EBP-0x10],EBX + * 0096655C E8 2F4A0100 CALL .0097AF90 + * 00966561 83F8 01 CMP EAX,0x1 + * 00966564 0F84 E0010000 JE .0096674A + * 0096656A 8B55 EC MOV EDX,DWORD PTR SS:[EBP-0x14] + * 0096656D 85DB TEST EBX,EBX + * 0096656F 75 09 JNZ SHORT .0096657A + * 00966571 8B42 04 MOV EAX,DWORD PTR DS:[EDX+0x4] + * 00966574 8B40 38 MOV EAX,DWORD PTR DS:[EAX+0x38] + * 00966577 8945 F0 MOV DWORD PTR SS:[EBP-0x10],EAX + * 0096657A 33C0 XOR EAX,EAX + * 0096657C C645 F8 00 MOV BYTE PTR SS:[EBP-0x8],0x0 + * 00966580 33C9 XOR ECX,ECX + * 00966582 66:8945 F9 MOV WORD PTR SS:[EBP-0x7],AX + * 00966586 3946 14 CMP DWORD PTR DS:[ESI+0x14],EAX + * 00966589 0F86 BB010000 JBE .0096674A + * + * Scenario stack: + * + * 002FF9DC 00955659 RETURN to .00955659 from .00966530 + * 002FF9E0 002FFA10 ; jichi: text in [arg1+4] + * 002FF9E4 00000000 ; arg2 is zero + * 002FF9E8 00000001 + * 002FF9EC 784B8FC7 + * + * Name stack: + * + * 002FF59C 00930A76 RETURN to .00930A76 from .00966530 + * 002FF5A0 002FF5D0 ; jichi: text in [arg1+4] + * 002FF5A4 004DDEC0 ; arg2 is a pointer + * 002FF5A8 00000001 + * 002FF5AC 784B8387 + * 002FF5B0 00000182 + * 002FF5B4 00000000 + * + * Scenario and Name are called by different callers. + * + * 根雪の幻影 + * + * 00A1A00E CC INT3 + * 00A1A00F CC INT3 + * 00A1A010 55 PUSH EBP + * 00A1A011 8BEC MOV EBP,ESP + * 00A1A013 83EC 18 SUB ESP,0x18 + * 00A1A016 A1 0050AA00 MOV EAX,DWORD PTR DS:[0xAA5000] + * 00A1A01B 33C5 XOR EAX,EBP + * 00A1A01D 8945 FC MOV DWORD PTR SS:[EBP-0x4],EAX + * 00A1A020 53 PUSH EBX + * 00A1A021 56 PUSH ESI + * 00A1A022 8B75 0C MOV ESI,DWORD PTR SS:[EBP+0xC] + * 00A1A025 57 PUSH EDI + * 00A1A026 8B7D 08 MOV EDI,DWORD PTR SS:[EBP+0x8] + * 00A1A029 6A 00 PUSH 0x0 + * 00A1A02B 894D F0 MOV DWORD PTR SS:[EBP-0x10],ECX + * 00A1A02E 8B0D C434AE00 MOV ECX,DWORD PTR DS:[0xAE34C4] + * 00A1A034 68 F816A900 PUSH .00A916F8 ; ASCII "/Config/SceneSkip" + * 00A1A039 8975 EC MOV DWORD PTR SS:[EBP-0x14],ESI + * 00A1A03C E8 7F510100 CALL .00A2F1C0 + * 00A1A041 83F8 01 CMP EAX,0x1 + * 00A1A044 0F84 3A010000 JE .00A1A184 + * 00A1A04A 8B4D F0 MOV ECX,DWORD PTR SS:[EBP-0x10] + * 00A1A04D 85F6 TEST ESI,ESI + * 00A1A04F 75 09 JNZ SHORT .00A1A05A + * 00A1A051 8B41 04 MOV EAX,DWORD PTR DS:[ECX+0x4] + * 00A1A054 8B40 38 MOV EAX,DWORD PTR DS:[EAX+0x38] + * 00A1A057 8945 EC MOV DWORD PTR SS:[EBP-0x14],EAX + * 00A1A05A 33C0 XOR EAX,EAX + * 00A1A05C C645 F8 00 MOV BYTE PTR SS:[EBP-0x8],0x0 + * 00A1A060 33DB XOR EBX,EBX + * 00A1A062 66:8945 F9 MOV WORD PTR SS:[EBP-0x7],AX + * 00A1A066 3947 14 CMP DWORD PTR DS:[EDI+0x14],EAX + * 00A1A069 0F86 15010000 JBE .00A1A184 + * 00A1A06F 90 NOP + * 00A1A070 837F 18 10 CMP DWORD PTR DS:[EDI+0x18],0x10 + * 00A1A074 72 05 JB SHORT .00A1A07B + * 00A1A076 8B47 04 MOV EAX,DWORD PTR DS:[EDI+0x4] + * 00A1A079 EB 03 JMP SHORT .00A1A07E + * 00A1A07B 8D47 04 LEA EAX,DWORD PTR DS:[EDI+0x4] + * 00A1A07E 803C18 00 CMP BYTE PTR DS:[EAX+EBX],0x0 + * 00A1A082 0F84 FC000000 JE .00A1A184 + * 00A1A088 837F 18 10 CMP DWORD PTR DS:[EDI+0x18],0x10 + * 00A1A08C 72 05 JB SHORT .00A1A093 + * 00A1A08E 8B47 04 MOV EAX,DWORD PTR DS:[EDI+0x4] + * 00A1A091 EB 03 JMP SHORT .00A1A096 + * 00A1A093 8D47 04 LEA EAX,DWORD PTR DS:[EDI+0x4] + * 00A1A096 8A0418 MOV AL,BYTE PTR DS:[EAX+EBX] + * 00A1A099 3C 81 CMP AL,0x81 + * 00A1A09B 72 04 JB SHORT .00A1A0A1 + * 00A1A09D 3C 9F CMP AL,0x9F + * 00A1A09F 76 06 JBE SHORT .00A1A0A7 + * 00A1A0A1 04 20 ADD AL,0x20 + * 00A1A0A3 3C 0F CMP AL,0xF + * 00A1A0A5 77 40 JA SHORT .00A1A0E7 + * 00A1A0A7 837F 18 10 CMP DWORD PTR DS:[EDI+0x18],0x10 + * 00A1A0AB 72 05 JB SHORT .00A1A0B2 + * 00A1A0AD 8B47 04 MOV EAX,DWORD PTR DS:[EDI+0x4] + * 00A1A0B0 EB 03 JMP SHORT .00A1A0B5 + * 00A1A0B2 8D47 04 LEA EAX,DWORD PTR DS:[EDI+0x4] + * 00A1A0B5 837F 18 10 CMP DWORD PTR DS:[EDI+0x18],0x10 + * 00A1A0B9 8A0418 MOV AL,BYTE PTR DS:[EAX+EBX] + * 00A1A0BC 8845 F8 MOV BYTE PTR SS:[EBP-0x8],AL + * 00A1A0BF 72 13 JB SHORT .00A1A0D4 + * 00A1A0C1 8B47 04 MOV EAX,DWORD PTR DS:[EDI+0x4] + * 00A1A0C4 C645 F7 02 MOV BYTE PTR SS:[EBP-0x9],0x2 + * 00A1A0C8 8A4418 01 MOV AL,BYTE PTR DS:[EAX+EBX+0x1] + * 00A1A0CC 83C3 02 ADD EBX,0x2 + * 00A1A0CF 8845 F9 MOV BYTE PTR SS:[EBP-0x7],AL + * 00A1A0D2 EB 30 JMP SHORT .00A1A104 + * 00A1A0D4 8D47 04 LEA EAX,DWORD PTR DS:[EDI+0x4] + * 00A1A0D7 C645 F7 02 MOV BYTE PTR SS:[EBP-0x9],0x2 + * 00A1A0DB 8A4418 01 MOV AL,BYTE PTR DS:[EAX+EBX+0x1] + * 00A1A0DF 83C3 02 ADD EBX,0x2 + * 00A1A0E2 8845 F9 MOV BYTE PTR SS:[EBP-0x7],AL + * 00A1A0E5 EB 1D JMP SHORT .00A1A104 + * 00A1A0E7 837F 18 10 CMP DWORD PTR DS:[EDI+0x18],0x10 + * 00A1A0EB 72 05 JB SHORT .00A1A0F2 + * 00A1A0ED 8B47 04 MOV EAX,DWORD PTR DS:[EDI+0x4] + * 00A1A0F0 EB 03 JMP SHORT .00A1A0F5 + * 00A1A0F2 8D47 04 LEA EAX,DWORD PTR DS:[EDI+0x4] + * 00A1A0F5 8A0418 MOV AL,BYTE PTR DS:[EAX+EBX] + * 00A1A0F8 43 INC EBX + * 00A1A0F9 8845 F8 MOV BYTE PTR SS:[EBP-0x8],AL + * 00A1A0FC C645 F9 00 MOV BYTE PTR SS:[EBP-0x7],0x0 + * 00A1A100 C645 F7 01 MOV BYTE PTR SS:[EBP-0x9],0x1 + * 00A1A104 807F 48 01 CMP BYTE PTR DS:[EDI+0x48],0x1 + * 00A1A108 75 21 JNZ SHORT .00A1A12B + * 00A1A10A 8B49 08 MOV ECX,DWORD PTR DS:[ECX+0x8] + * 00A1A10D 8D47 38 LEA EAX,DWORD PTR DS:[EDI+0x38] + * 00A1A110 50 PUSH EAX + * 00A1A111 FF77 28 PUSH DWORD PTR DS:[EDI+0x28] + * 00A1A114 8B47 24 MOV EAX,DWORD PTR DS:[EDI+0x24] + * 00A1A117 03C0 ADD EAX,EAX + * 00A1A119 50 PUSH EAX + * 00A1A11A 8D47 20 LEA EAX,DWORD PTR DS:[EDI+0x20] + * 00A1A11D 50 PUSH EAX + * 00A1A11E 8D47 1C LEA EAX,DWORD PTR DS:[EDI+0x1C] + * 00A1A121 50 PUSH EAX + * 00A1A122 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-0x8] + * 00A1A125 50 PUSH EAX + * 00A1A126 E8 85220000 CALL .00A1C3B0 + * 00A1A12B FF77 34 PUSH DWORD PTR DS:[EDI+0x34] + * 00A1A12E 8B4D EC MOV ECX,DWORD PTR SS:[EBP-0x14] + * 00A1A131 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-0x8] + * 00A1A134 FF77 4C PUSH DWORD PTR DS:[EDI+0x4C] + * 00A1A137 FF77 30 PUSH DWORD PTR DS:[EDI+0x30] + * 00A1A13A FF77 2C PUSH DWORD PTR DS:[EDI+0x2C] + * 00A1A13D FF77 20 PUSH DWORD PTR DS:[EDI+0x20] + * 00A1A140 FF77 1C PUSH DWORD PTR DS:[EDI+0x1C] + * 00A1A143 50 PUSH EAX + * 00A1A144 E8 1733FFFF CALL .00A0D460 + * 00A1A149 0FBE45 F7 MOVSX EAX,BYTE PTR SS:[EBP-0x9] + * 00A1A14D 0FAF47 24 IMUL EAX,DWORD PTR DS:[EDI+0x24] + * 00A1A151 0147 1C ADD DWORD PTR DS:[EDI+0x1C],EAX + * 00A1A154 807F 48 00 CMP BYTE PTR DS:[EDI+0x48],0x0 + * 00A1A158 8B47 1C MOV EAX,DWORD PTR DS:[EDI+0x1C] + * 00A1A15B 75 1B JNZ SHORT .00A1A178 + * 00A1A15D 3947 40 CMP DWORD PTR DS:[EDI+0x40],EAX + * 00A1A160 7F 16 JG SHORT .00A1A178 + * 00A1A162 8B47 38 MOV EAX,DWORD PTR DS:[EDI+0x38] + * 00A1A165 8B4F 28 MOV ECX,DWORD PTR DS:[EDI+0x28] + * 00A1A168 014F 20 ADD DWORD PTR DS:[EDI+0x20],ECX + * 00A1A16B 8947 1C MOV DWORD PTR DS:[EDI+0x1C],EAX + * 00A1A16E 8B47 20 MOV EAX,DWORD PTR DS:[EDI+0x20] + * 00A1A171 03C1 ADD EAX,ECX + * 00A1A173 3B47 44 CMP EAX,DWORD PTR DS:[EDI+0x44] + * 00A1A176 7D 0C JGE SHORT .00A1A184 + * 00A1A178 8B4D F0 MOV ECX,DWORD PTR SS:[EBP-0x10] + * 00A1A17B 3B5F 14 CMP EBX,DWORD PTR DS:[EDI+0x14] + * 00A1A17E ^0F82 ECFEFFFF JB .00A1A070 + * 00A1A184 8B4D FC MOV ECX,DWORD PTR SS:[EBP-0x4] + * 00A1A187 5F POP EDI + * 00A1A188 5E POP ESI + * 00A1A189 33CD XOR ECX,EBP + * 00A1A18B 5B POP EBX + * 00A1A18C E8 87600200 CALL .00A40218 + * 00A1A191 8BE5 MOV ESP,EBP + * 00A1A193 5D POP EBP + * 00A1A194 C2 0C00 RETN 0xC + * 00A1A197 CC INT3 + * 00A1A198 CC INT3 + */ +static void SpecialHookSilkys(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + //DWORD arg1 = *(DWORD *)(esp_base + 0x4); + DWORD arg1 = argof(1, esp_base), + arg2 = argof(2, esp_base); + + int size = *(DWORD *)(arg1 + 0x14); + if (size <= 0) + return; + + enum { ShortTextCapacity = 0x10 }; + + DWORD text = 0; + //if (arg2 == 0) { + if (size >= ShortTextCapacity) { + text = *(DWORD *)(arg1 + 4); + if (text && ::IsBadReadPtr((LPCVOID)text, size)) // this might not be needed though + text = 0; + } + if (!text) { // short text + text = arg1 + 4; + size = min(size, ShortTextCapacity); + } + *len = size; + *data = text; + + *split = arg2 == 0 ? 1 : 2; // arg2 == 0 ? scenario : name +} +bool InsertSilkysHook() +{ + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng:Silkys: failed to get memory range"); + return false; + } + + const BYTE bytes[] = { + 0x66,0x89,0x45, 0xf9, // 00a1a062 66:8945 f9 mov word ptr ss:[ebp-0x7],ax + 0x39,0x47, 0x14 // 00a1a066 3947 14 cmp dword ptr ds:[edi+0x14],eax + }; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:Silkys: pattern not found"); + return false; + } + + addr = MemDbg::findEnclosingAlignedFunction(addr); + if (!addr) { + ConsoleOutput("vnreng:Silkys: function not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.text_fun = SpecialHookSilkys; + hp.type = USING_STRING|NO_CONTEXT; // = 9 + + ConsoleOutput("vnreng: INSERT Silkys"); + NewHook(hp, "SilkysPlus"); + return true; +} + +/** jichi 6/1/2014 Eushully + * Insert to the last GetTextExtentPoint32A + * + * ATCode: + * http://capita.tistory.com/m/post/255 + * + * Binary: + * {AGE.EXE!0x000113C3(89 C2 C1 E2 04 29 C2 E8 BD 25 20 00 52 89 D1 59), AGE.EXE!0x00012A47(E8 40 0F 20 00 90 90 90 90), AGE.EXE!0x0001DF07(55 8B EC 83 EC 08 56 EB 07 E8 32 5A 1F 00 EB F0), AGE.EXE!0x002137CE(90 90 90 90 90 C2 04 00 53 8B 1A 83 FB 6E 74 14 81 FB 96 01 00 00 74 1B 83 FB 6F 74 25 83 FB 72 74 27 EB 2C 8B 5A 10 89 1F 83 C7 04 B8 05 00 00 00 EB 1F 8B 5A 10 89 1F 83 C7 04 B8 07 00 00 00 EB 10 B8 03 00 00 00 EB 09 B8 01 00 00 00 EB 02 31 C0 5B C3 60 89 E5 83 EC 18 E8 7E 01 00 00 8B 55 F8 83 3A 00 75 31 8B 45 FC 8B 4C 30 E8 89 CA C1 E2 04 29 CA 8D 0C D6 8B 1C 08 51 8B 4C 08 FC 8B 7D F4 89 DA E8 7E FF FF FF 85 C0 74 0A 83 F8 01 74 09 8D 14 82 EB ED 89 EC 61 C3 C7 07 00 00 00 00 8B 75 F4 8B 7D F0 52 8B 06 85 C0 74 17 8D 04 81 8A 10 80 FA FF 74 08 F6 D2 88 17 40 47 EB F1 83 C6 04 EB E3 8B 55 F0 52 8B 02 E8 2F FF FF FF 8B 12 39 D0 74 C1 8B 55 F8 C7 02 01 00 00 00 8B 4D E4 8B 45 FC 8D 04 08 8B 55 F8 89 42 04 58 89 42 08 89 5A 0C 8B 45 FC 8B 4C 08 FC 8B 45 F4 8B 00 89 42 10 8D 04 81 89 42 14 8B 72 0C 8B 7D EC B9 08 00 00 00 F3 A5 8B 5D E8 8B 7A 14 8B 75 F0 31 C9 52 8A 06 84 C0 74 0F F6 D0 8A 14 39 88 14 19 88 04 39 41 46 EB EB 5A 8B 04 39 89 04 19 31 C0 F7 D0 89 04 39 83 C1 04 89 4A 18 8B 7A 0C 8B 42 10 31 C9 BB 6E 00 00 00 89 1F 89 4F 04 89 4F 08 C7 47 0C 02 00 00 00 83 C3 04 89 5F 14 89 4F 18 89 4F 1C 89 EC 61 C3 60 89 E5 83 EC 18 E8 59 00 00 00 8B 5D F8 83 3B 01 75 2E 31 C9 89 0B 8B 7B 0C 8B 75 EC 8D 49 08 F3 A5 8B 7B 14 8B 75 E8 8B 4B 18 F3 A4 8B 43 04 8B 53 08 89 10 8D 7B 04 31 C0 B9 40 01 00 00 F3 AB 89 EC 61 C3 8B 8C D6 A8 D7 05 00 8B 01 3D 96 01 00 00 74 07 83 F8 6E 74 02 EB 07 E8 7A FE FF FF 8B 01 C3 60 C7 45 FC A8 D7 05 00 EB 03 58 EB 05 E8 F8 FF FF FF 2D BD 39 21 00 03 80 D4 02 00 00 B9 00 01 00 00 8D 80 00 40 01 00 89 45 F8 8D 04 01 89 45 F4 8D 04 01 89 45 F0 8D 04 01 89 45 EC 8D 04 01 89 45 E8 61 C3)} + * + * #1 other text AGE.EXE!0x000113C3(89 C2 C1 E2 04 29 C2 E8 BD 25 20 00 52 89 D1 59) + * #2 scenario AGE.EXE!0x00012A47(E8 40 0F 20 00 90 90 90 90) + * + * 0041130B 8B96 9CA30A00 MOV EDX,DWORD PTR DS:[ESI+0xAA39C] + * 00411311 81A6 CCA90A00 FF>AND DWORD PTR DS:[ESI+0xAA9CC],0xF7FFFFF> + * 0041131B 33C0 XOR EAX,EAX + * 0041131D 50 PUSH EAX + * 0041131E 8986 1C160000 MOV DWORD PTR DS:[ESI+0x161C],EAX + * 00411324 8986 78EB0500 MOV DWORD PTR DS:[ESI+0x5EB78],EAX + * 0041132A 8B42 0C MOV EAX,DWORD PTR DS:[EDX+0xC] + * 0041132D 68 F4536100 PUSH .006153F4 ; ASCII "message:ReadTextSkip" + * 00411332 8D8E 9CA30A00 LEA ECX,DWORD PTR DS:[ESI+0xAA39C] + * 00411338 FFD0 CALL EAX + * 0041133A 8B96 9CA30A00 MOV EDX,DWORD PTR DS:[ESI+0xAA39C] + * 00411340 8B42 04 MOV EAX,DWORD PTR DS:[EDX+0x4] + * 00411343 68 4C606100 PUSH .0061604C ; ASCII "set:CancelMesSkipOnClick" + * 00411348 8D8E 9CA30A00 LEA ECX,DWORD PTR DS:[ESI+0xAA39C] + * 0041134E FFD0 CALL EAX + * 00411350 83F8 02 CMP EAX,0x2 + * 00411353 75 1A JNZ SHORT .0041136F + * 00411355 68 34606100 PUSH .00616034 ; ASCII "CALLBACK_SETTING.BIN" + * 0041135A 8BCE MOV ECX,ESI + * 0041135C E8 7FFBFFFF CALL .00410EE0 + * 00411361 5F POP EDI + * 00411362 5E POP ESI + * 00411363 5B POP EBX + * 00411364 C3 RETN + * 00411365 C786 18770700 01>MOV DWORD PTR DS:[ESI+0x77718],0x1 + * 0041136F 83BE 6C780700 00 CMP DWORD PTR DS:[ESI+0x7786C],0x0 + * 00411376 75 45 JNZ SHORT .004113BD + * 00411378 F603 40 TEST BYTE PTR DS:[EBX],0x40 + * 0041137B 75 40 JNZ SHORT .004113BD + * 0041137D 81A6 CCA90A00 FF>AND DWORD PTR DS:[ESI+0xAA9CC],0xF7FFFFF> + * 00411387 33DB XOR EBX,EBX + * 00411389 8DBE B0780700 LEA EDI,DWORD PTR DS:[ESI+0x778B0] + * 0041138F 90 NOP + * 00411390 8B07 MOV EAX,DWORD PTR DS:[EDI] + * 00411392 85C0 TEST EAX,EAX + * 00411394 74 1E JE SHORT .004113B4 + * 00411396 8B8F E4D5F8FF MOV ECX,DWORD PTR DS:[EDI+0xFFF8D5E4] + * 0041139C 8B57 0C MOV EDX,DWORD PTR DS:[EDI+0xC] + * 0041139F 51 PUSH ECX + * 004113A0 52 PUSH EDX + * 004113A1 50 PUSH EAX + * 004113A2 53 PUSH EBX + * 004113A3 8D8E 04480100 LEA ECX,DWORD PTR DS:[ESI+0x14804] + * 004113A9 E8 42840900 CALL .004A97F0 + * 004113AE C707 00000000 MOV DWORD PTR DS:[EDI],0x0 + * 004113B4 43 INC EBX + * 004113B5 83C7 04 ADD EDI,0x4 + * 004113B8 83FB 03 CMP EBX,0x3 + * 004113BB ^7C D3 JL SHORT .00411390 + * 004113BD 8B86 90D70500 MOV EAX,DWORD PTR DS:[ESI+0x5D790] + * 004113C3 8BC8 MOV ECX,EAX ; jichi: #1 hook here + * 004113C5 C1E1 04 SHL ECX,0x4 + * 004113C8 2BC8 SUB ECX,EAX + * 004113CA 8B94CE A8D70500 MOV EDX,DWORD PTR DS:[ESI+ECX*8+0x5D7A8] + * 004113D1 8B02 MOV EAX,DWORD PTR DS:[EDX] + * 004113D3 85C0 TEST EAX,EAX + * //004113C3 89C2 MOV EDX,EAX + * //004113C5 C1E2 04 SHL EDX,0x4 + * //004113C8 29C2 SUB EDX,EAX + * //004113CA E8 BD252000 CALL .0061398C + * //004113CF 52 PUSH EDX + * //004113D0 89D1 MOV ECX,EDX + * //004113D2 59 POP ECX + * 004113D5 78 35 JS SHORT .0041140C + * 004113D7 3D 00040000 CMP EAX,0x400 + * 004113DC 7D 2E JGE SHORT .0041140C + * 004113DE 8B8486 244F0A00 MOV EAX,DWORD PTR DS:[ESI+EAX*4+0xA4F24] + * 004113E5 8BCE MOV ECX,ESI + * 004113E7 FFD0 CALL EAX + * 004113E9 8B86 90D70500 MOV EAX,DWORD PTR DS:[ESI+0x5D790] + * 004113EF 8BC8 MOV ECX,EAX + * 004113F1 C1E1 04 SHL ECX,0x4 + * 004113F4 2BC8 SUB ECX,EAX + * 004113F6 8B94CE 04D80500 MOV EDX,DWORD PTR DS:[ESI+ECX*8+0x5D804] + * 004113FD 8D04CE LEA EAX,DWORD PTR DS:[ESI+ECX*8] + * 00411400 03D2 ADD EDX,EDX + * 00411402 03D2 ADD EDX,EDX + * 00411404 0190 A8D70500 ADD DWORD PTR DS:[EAX+0x5D7A8],EDX + * 0041140A EB 07 JMP SHORT .00411413 + * 0041140C 8BCE MOV ECX,ESI + * 0041140E E8 7D6C0000 CALL .00418090 + * 00411413 8B86 9CA30A00 MOV EAX,DWORD PTR DS:[ESI+0xAA39C] + * 00411419 8B50 04 MOV EDX,DWORD PTR DS:[EAX+0x4] + * 0041141C 8D8E 9CA30A00 LEA ECX,DWORD PTR DS:[ESI+0xAA39C] + * 00411422 68 4C606100 PUSH .0061604C ; ASCII "set:CancelMesSkipOnClick" + * 00411427 FFD2 CALL EDX + * 00411429 85C0 TEST EAX,EAX + * 0041142B ^0F85 30FFFFFF JNZ .00411361 + * 00411431 3986 D8C90000 CMP DWORD PTR DS:[ESI+0xC9D8],EAX + * 00411437 ^0F84 24FFFFFF JE .00411361 + * 0041143D 8B86 D0A90A00 MOV EAX,DWORD PTR DS:[ESI+0xAA9D0] + * 00411443 A8 10 TEST AL,0x10 + * 00411445 0F84 84000000 JE .004114CF + * 0041144B 83E0 EF AND EAX,0xFFFFFFEF + * 0041144E 83BE 10770700 00 CMP DWORD PTR DS:[ESI+0x77710],0x0 + * 00411455 8986 D0A90A00 MOV DWORD PTR DS:[ESI+0xAA9D0],EAX + * 0041145B ^0F85 00FFFFFF JNZ .00411361 + * 00411461 8B86 ECC90000 MOV EAX,DWORD PTR DS:[ESI+0xC9EC] + * 00411467 8DBE 3C550000 LEA EDI,DWORD PTR DS:[ESI+0x553C] + * 0041146D 85C0 TEST EAX,EAX + * 0041146F ^0F88 ECFEFFFF JS .00411361 + * 00411475 3987 08040000 CMP DWORD PTR DS:[EDI+0x408],EAX + * 0041147B ^0F8E E0FEFFFF JLE .00411361 + * 00411481 8BCE MOV ECX,ESI + * 00411483 E8 A86AFFFF CALL .00407F30 + * 00411488 6A 00 PUSH 0x0 + * 0041148A 8BCE MOV ECX,ESI + * 0041148C E8 EF3CFFFF CALL .00405180 + * 00411491 8B86 90D70500 MOV EAX,DWORD PTR DS:[ESI+0x5D790] + * 00411497 8BC8 MOV ECX,EAX + * 00411499 C1E1 04 SHL ECX,0x4 + * 0041149C 2BC8 SUB ECX,EAX + * 0041149E 8D34CE LEA ESI,DWORD PTR DS:[ESI+ECX*8] + * 004114A1 8BCF MOV ECX,EDI + * 004114A3 E8 0839FFFF CALL .00404DB0 + * 004114A8 8B96 A4D70500 MOV EDX,DWORD PTR DS:[ESI+0x5D7A4] + * 004114AE 8D0482 LEA EAX,DWORD PTR DS:[EDX+EAX*4] + * 004114B1 8986 A8D70500 MOV DWORD PTR DS:[ESI+0x5D7A8],EAX + * 004114B7 C787 B0740000 FF>MOV DWORD PTR DS:[EDI+0x74B0],-0x1 + * + * 00412953 53 PUSH EBX + * 00412954 FF15 B8406100 CALL DWORD PTR DS:[0x6140B8] ; kernel32.Sleep + * 0041295A 53 PUSH EBX + * 0041295B 53 PUSH EBX + * 0041295C 53 PUSH EBX + * 0041295D 53 PUSH EBX + * 0041295E 8D8D 34F8FFFF LEA ECX,DWORD PTR SS:[EBP-0x7CC] + * 00412964 51 PUSH ECX + * 00412965 FF15 AC436100 CALL DWORD PTR DS:[0x6143AC] ; user32.PeekMessageA + * 0041296B 85C0 TEST EAX,EAX + * 0041296D ^0F85 5DF3FFFF JNZ .00411CD0 + * 00412973 ^E9 D8F3FFFF JMP .00411D50 + * 00412978 A9 00000020 TEST EAX,0x20000000 + * 0041297D 74 0C JE SHORT .0041298B + * 0041297F 8BCE MOV ECX,ESI + * 00412981 E8 3A63FFFF CALL .00408CC0 + * 00412986 ^E9 C5F3FFFF JMP .00411D50 + * 0041298B 85C0 TEST EAX,EAX + * 0041298D 79 14 JNS SHORT .004129A3 + * 0041298F 8BCE MOV ECX,ESI + * 00412991 E8 AAEBFFFF CALL .00411540 + * 00412996 6A 02 PUSH 0x2 + * 00412998 FF15 B8406100 CALL DWORD PTR DS:[0x6140B8] ; kernel32.Sleep + * 0041299E ^E9 ADF3FFFF JMP .00411D50 + * 004129A3 A8 01 TEST AL,0x1 + * 004129A5 74 25 JE SHORT .004129CC + * 004129A7 8D8E D08D0600 LEA ECX,DWORD PTR DS:[ESI+0x68DD0] + * 004129AD E8 CEF30300 CALL .00451D80 + * 004129B2 8985 ACF8FFFF MOV DWORD PTR SS:[EBP-0x754],EAX + * 004129B8 3BC3 CMP EAX,EBX + * 004129BA ^0F8C 90F3FFFF JL .00411D50 + * 004129C0 83A6 CCA90A00 FE AND DWORD PTR DS:[ESI+0xAA9CC],0xFFFFFFF> + * 004129C7 ^E9 84F3FFFF JMP .00411D50 + * 004129CC A8 20 TEST AL,0x20 + * 004129CE 74 3C JE SHORT .00412A0C + * 004129D0 8D8E 5C8E0600 LEA ECX,DWORD PTR DS:[ESI+0x68E5C] + * 004129D6 E8 A5F30300 CALL .00451D80 + * 004129DB 8985 ACF8FFFF MOV DWORD PTR SS:[EBP-0x754],EAX + * 004129E1 3BC3 CMP EAX,EBX + * 004129E3 ^0F8C 67F3FFFF JL .00411D50 + * 004129E9 83A6 CCA90A00 DF AND DWORD PTR DS:[ESI+0xAA9CC],0xFFFFFFD> + * 004129F0 8D8E 5C8E0600 LEA ECX,DWORD PTR DS:[ESI+0x68E5C] + * 004129F6 E8 45EE0300 CALL .00451840 + * 004129FB 50 PUSH EAX + * 004129FC 8D8E 5C8E0600 LEA ECX,DWORD PTR DS:[ESI+0x68E5C] + * 00412A02 E8 39F30300 CALL .00451D40 + * 00412A07 ^E9 44F3FFFF JMP .00411D50 + * 00412A0C A9 00000010 TEST EAX,0x10000000 + * 00412A11 74 14 JE SHORT .00412A27 + * 00412A13 8BCE MOV ECX,ESI + * 00412A15 E8 A664FFFF CALL .00408EC0 + * 00412A1A 6A 02 PUSH 0x2 + * 00412A1C FF15 B8406100 CALL DWORD PTR DS:[0x6140B8] ; kernel32.Sleep + * 00412A22 ^E9 29F3FFFF JMP .00411D50 + * 00412A27 A9 00008000 TEST EAX,0x800000 + * 00412A2C 74 0C JE SHORT .00412A3A + * 00412A2E 8BCE MOV ECX,ESI + * 00412A30 E8 6B66FFFF CALL .004090A0 + * 00412A35 ^E9 16F3FFFF JMP .00411D50 + * 00412A3A 8B86 90D70500 MOV EAX,DWORD PTR DS:[ESI+0x5D790] + * 00412A40 8BD0 MOV EDX,EAX + * 00412A42 C1E2 04 SHL EDX,0x4 + * 00412A45 2BD0 SUB EDX,EAX + * 00412A47 8B84D6 A8D70500 MOV EAX,DWORD PTR DS:[ESI+EDX*8+0x5D7A8] ; jichi: #2 hook here + * //00412A47 E8 400F2000 CALL .0061398C + * 00412A4E 8B00 MOV EAX,DWORD PTR DS:[EAX] + * 00412A50 3BC3 CMP EAX,EBX + * 00412A52 7C 37 JL SHORT .00412A8B + * 00412A54 3D 00040000 CMP EAX,0x400 + * 00412A59 7D 30 JGE SHORT .00412A8B + * 00412A5B 8BCE MOV ECX,ESI + * 00412A5D 8B9486 244F0A00 MOV EDX,DWORD PTR DS:[ESI+EAX*4+0xA4F24] + * 00412A64 FFD2 CALL EDX + * 00412A66 8B86 90D70500 MOV EAX,DWORD PTR DS:[ESI+0x5D790] + * 00412A6C 8BC8 MOV ECX,EAX + * 00412A6E C1E1 04 SHL ECX,0x4 + * 00412A71 2BC8 SUB ECX,EAX + * 00412A73 8D04CE LEA EAX,DWORD PTR DS:[ESI+ECX*8] + * 00412A76 8B90 04D80500 MOV EDX,DWORD PTR DS:[EAX+0x5D804] + * 00412A7C 03D2 ADD EDX,EDX + * 00412A7E 03D2 ADD EDX,EDX + * 00412A80 0190 A8D70500 ADD DWORD PTR DS:[EAX+0x5D7A8],EDX + * 00412A86 ^E9 C5F2FFFF JMP .00411D50 + * 00412A8B 8BCE MOV ECX,ESI + * 00412A8D E8 FE550000 CALL .00418090 + * 00412A92 ^E9 B9F2FFFF JMP .00411D50 + * 00412A97 C785 A4F8FFFF 01>MOV DWORD PTR SS:[EBP-0x75C],0x1 + * 00412AA1 C745 FC FFFFFFFF MOV DWORD PTR SS:[EBP-0x4],-0x1 + * 00412AA8 B8 E02D4100 MOV EAX,.00412DE0 + * 00412AAD C3 RETN + * 00412AAE 8B85 14F8FFFF MOV EAX,DWORD PTR SS:[EBP-0x7EC] + * 00412AB4 50 PUSH EAX + * 00412AB5 8B8D 10F8FFFF MOV ECX,DWORD PTR SS:[EBP-0x7F0] + * + * Patched code: + * + * 0041DF07 55 PUSH EBP + * 0041DF08 8BEC MOV EBP,ESP + * 0041DF0A 83EC 08 SUB ESP,0x8 + * 0041DF0D 56 PUSH ESI + * 0041DF0E EB 07 JMP SHORT .0041DF17 + * 0041DF10 E8 325A1F00 CALL .00613947 + * 0041DF15 ^EB F0 JMP SHORT .0041DF07 + * + * 006137CE 90 NOP + * 006137CF 90 NOP + * 006137D0 90 NOP + * 006137D1 90 NOP + * 006137D2 90 NOP + * 006137D3 C2 0400 RETN 0x4 + * 006137D6 53 PUSH EBX + * 006137D7 8B1A MOV EBX,DWORD PTR DS:[EDX] + * 006137D9 83FB 6E CMP EBX,0x6E + * 006137DC 74 14 JE SHORT .006137F2 + * 006137DE 81FB 96010000 CMP EBX,0x196 + * 006137E4 74 1B JE SHORT .00613801 + * 006137E6 83FB 6F CMP EBX,0x6F + * 006137E9 74 25 JE SHORT .00613810 + * 006137EB 83FB 72 CMP EBX,0x72 + * 006137EE 74 27 JE SHORT .00613817 + * 006137F0 EB 2C JMP SHORT .0061381E + * 006137F2 8B5A 10 MOV EBX,DWORD PTR DS:[EDX+0x10] + * 006137F5 891F MOV DWORD PTR DS:[EDI],EBX + * 006137F7 83C7 04 ADD EDI,0x4 + * 006137FA B8 05000000 MOV EAX,0x5 + * 006137FF EB 1F JMP SHORT .00613820 + * 00613801 8B5A 10 MOV EBX,DWORD PTR DS:[EDX+0x10] + * 00613804 891F MOV DWORD PTR DS:[EDI],EBX + * 00613806 83C7 04 ADD EDI,0x4 + * 00613809 B8 07000000 MOV EAX,0x7 + * 0061380E EB 10 JMP SHORT .00613820 + * 00613810 B8 03000000 MOV EAX,0x3 + * 00613815 EB 09 JMP SHORT .00613820 + * 00613817 B8 01000000 MOV EAX,0x1 + * 0061381C EB 02 JMP SHORT .00613820 + * 0061381E 31C0 XOR EAX,EAX + * 00613820 5B POP EBX + * 00613821 C3 RETN + * 00613822 60 PUSHAD ; jichi: the translate function for hookpoint #2 + * 00613823 89E5 MOV EBP,ESP + * 00613825 83EC 18 SUB ESP,0x18 ; reserve 18 local variables + * 00613828 E8 7E010000 CALL .006139AB + * 0061382D 8B55 F8 MOV EDX,DWORD PTR SS:[EBP-0x8] + * 00613830 833A 00 CMP DWORD PTR DS:[EDX],0x0 + * 00613833 75 31 JNZ SHORT .00613866 + * 00613835 8B45 FC MOV EAX,DWORD PTR SS:[EBP-0x4] + * 00613838 8B4C30 E8 MOV ECX,DWORD PTR DS:[EAX+ESI-0x18] + * 0061383C 89CA MOV EDX,ECX + * 0061383E C1E2 04 SHL EDX,0x4 + * 00613841 29CA SUB EDX,ECX + * 00613843 8D0CD6 LEA ECX,DWORD PTR DS:[ESI+EDX*8] + * 00613846 8B1C08 MOV EBX,DWORD PTR DS:[EAX+ECX] + * 00613849 51 PUSH ECX + * 0061384A 8B4C08 FC MOV ECX,DWORD PTR DS:[EAX+ECX-0x4] + * 0061384E 8B7D F4 MOV EDI,DWORD PTR SS:[EBP-0xC] + * 00613851 89DA MOV EDX,EBX + * 00613853 E8 7EFFFFFF CALL .006137D6 + * 00613858 85C0 TEST EAX,EAX + * 0061385A 74 0A JE SHORT .00613866 + * 0061385C 83F8 01 CMP EAX,0x1 + * 0061385F 74 09 JE SHORT .0061386A + * 00613861 8D1482 LEA EDX,DWORD PTR DS:[EDX+EAX*4] + * 00613864 ^EB ED JMP SHORT .00613853 + * 00613866 89EC MOV ESP,EBP + * 00613868 61 POPAD + * 00613869 C3 RETN + * 0061386A C707 00000000 MOV DWORD PTR DS:[EDI],0x0 + * 00613870 8B75 F4 MOV ESI,DWORD PTR SS:[EBP-0xC] + * 00613873 8B7D F0 MOV EDI,DWORD PTR SS:[EBP-0x10] + * 00613876 52 PUSH EDX + * 00613877 8B06 MOV EAX,DWORD PTR DS:[ESI] + * 00613879 85C0 TEST EAX,EAX + * 0061387B 74 17 JE SHORT .00613894 + * 0061387D 8D0481 LEA EAX,DWORD PTR DS:[ECX+EAX*4] + * 00613880 8A10 MOV DL,BYTE PTR DS:[EAX] + * 00613882 80FA FF CMP DL,0xFF + * 00613885 74 08 JE SHORT .0061388F + * 00613887 F6D2 NOT DL + * 00613889 8817 MOV BYTE PTR DS:[EDI],DL + * 0061388B 40 INC EAX + * 0061388C 47 INC EDI + * 0061388D ^EB F1 JMP SHORT .00613880 + * 0061388F 83C6 04 ADD ESI,0x4 + * 00613892 ^EB E3 JMP SHORT .00613877 + * 00613894 8B55 F0 MOV EDX,DWORD PTR SS:[EBP-0x10] + * 00613897 52 PUSH EDX + * 00613898 8B02 MOV EAX,DWORD PTR DS:[EDX] + * 0061389A E8 2FFFFFFF CALL .006137CE + * 0061389F 8B12 MOV EDX,DWORD PTR DS:[EDX] + * 006138A1 39D0 CMP EAX,EDX + * 006138A3 ^74 C1 JE SHORT .00613866 + * 006138A5 8B55 F8 MOV EDX,DWORD PTR SS:[EBP-0x8] + * 006138A8 C702 01000000 MOV DWORD PTR DS:[EDX],0x1 + * 006138AE 8B4D E4 MOV ECX,DWORD PTR SS:[EBP-0x1C] + * 006138B1 8B45 FC MOV EAX,DWORD PTR SS:[EBP-0x4] + * 006138B4 8D0408 LEA EAX,DWORD PTR DS:[EAX+ECX] + * 006138B7 8B55 F8 MOV EDX,DWORD PTR SS:[EBP-0x8] + * 006138BA 8942 04 MOV DWORD PTR DS:[EDX+0x4],EAX + * 006138BD 58 POP EAX + * 006138BE 8942 08 MOV DWORD PTR DS:[EDX+0x8],EAX + * 006138C1 895A 0C MOV DWORD PTR DS:[EDX+0xC],EBX + * 006138C4 8B45 FC MOV EAX,DWORD PTR SS:[EBP-0x4] + * 006138C7 8B4C08 FC MOV ECX,DWORD PTR DS:[EAX+ECX-0x4] + * 006138CB 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-0xC] + * 006138CE 8B00 MOV EAX,DWORD PTR DS:[EAX] + * 006138D0 8942 10 MOV DWORD PTR DS:[EDX+0x10],EAX + * 006138D3 8D0481 LEA EAX,DWORD PTR DS:[ECX+EAX*4] + * 006138D6 8942 14 MOV DWORD PTR DS:[EDX+0x14],EAX + * 006138D9 8B72 0C MOV ESI,DWORD PTR DS:[EDX+0xC] + * 006138DC 8B7D EC MOV EDI,DWORD PTR SS:[EBP-0x14] + * 006138DF B9 08000000 MOV ECX,0x8 + * 006138E4 F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS> + * 006138E6 8B5D E8 MOV EBX,DWORD PTR SS:[EBP-0x18] + * 006138E9 8B7A 14 MOV EDI,DWORD PTR DS:[EDX+0x14] + * 006138EC 8B75 F0 MOV ESI,DWORD PTR SS:[EBP-0x10] + * 006138EF 31C9 XOR ECX,ECX + * 006138F1 52 PUSH EDX + * 006138F2 8A06 MOV AL,BYTE PTR DS:[ESI] + * 006138F4 84C0 TEST AL,AL + * 006138F6 74 0F JE SHORT .00613907 + * 006138F8 F6D0 NOT AL + * 006138FA 8A1439 MOV DL,BYTE PTR DS:[ECX+EDI] + * 006138FD 881419 MOV BYTE PTR DS:[ECX+EBX],DL + * 00613900 880439 MOV BYTE PTR DS:[ECX+EDI],AL + * 00613903 41 INC ECX + * 00613904 46 INC ESI + * 00613905 ^EB EB JMP SHORT .006138F2 + * 00613907 5A POP EDX + * 00613908 8B0439 MOV EAX,DWORD PTR DS:[ECX+EDI] + * 0061390B 890419 MOV DWORD PTR DS:[ECX+EBX],EAX + * 0061390E 31C0 XOR EAX,EAX + * 00613910 F7D0 NOT EAX + * 00613912 890439 MOV DWORD PTR DS:[ECX+EDI],EAX + * 00613915 83C1 04 ADD ECX,0x4 + * 00613918 894A 18 MOV DWORD PTR DS:[EDX+0x18],ECX + * 0061391B 8B7A 0C MOV EDI,DWORD PTR DS:[EDX+0xC] + * 0061391E 8B42 10 MOV EAX,DWORD PTR DS:[EDX+0x10] + * 00613921 31C9 XOR ECX,ECX + * 00613923 BB 6E000000 MOV EBX,0x6E + * 00613928 891F MOV DWORD PTR DS:[EDI],EBX + * 0061392A 894F 04 MOV DWORD PTR DS:[EDI+0x4],ECX + * 0061392D 894F 08 MOV DWORD PTR DS:[EDI+0x8],ECX + * 00613930 C747 0C 02000000 MOV DWORD PTR DS:[EDI+0xC],0x2 + * 00613937 83C3 04 ADD EBX,0x4 + * 0061393A 895F 14 MOV DWORD PTR DS:[EDI+0x14],EBX + * 0061393D 894F 18 MOV DWORD PTR DS:[EDI+0x18],ECX + * 00613940 894F 1C MOV DWORD PTR DS:[EDI+0x1C],ECX + * 00613943 89EC MOV ESP,EBP + * 00613945 61 POPAD + * 00613946 C3 RETN + * 00613947 60 PUSHAD + * 00613948 89E5 MOV EBP,ESP + * 0061394A 83EC 18 SUB ESP,0x18 + * 0061394D E8 59000000 CALL .006139AB + * 00613952 8B5D F8 MOV EBX,DWORD PTR SS:[EBP-0x8] + * 00613955 833B 01 CMP DWORD PTR DS:[EBX],0x1 + * 00613958 75 2E JNZ SHORT .00613988 + * 0061395A 31C9 XOR ECX,ECX + * 0061395C 890B MOV DWORD PTR DS:[EBX],ECX + * 0061395E 8B7B 0C MOV EDI,DWORD PTR DS:[EBX+0xC] + * 00613961 8B75 EC MOV ESI,DWORD PTR SS:[EBP-0x14] + * 00613964 8D49 08 LEA ECX,DWORD PTR DS:[ECX+0x8] + * 00613967 F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS> + * 00613969 8B7B 14 MOV EDI,DWORD PTR DS:[EBX+0x14] + * 0061396C 8B75 E8 MOV ESI,DWORD PTR SS:[EBP-0x18] + * 0061396F 8B4B 18 MOV ECX,DWORD PTR DS:[EBX+0x18] + * 00613972 F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[> + * 00613974 8B43 04 MOV EAX,DWORD PTR DS:[EBX+0x4] + * 00613977 8B53 08 MOV EDX,DWORD PTR DS:[EBX+0x8] + * 0061397A 8910 MOV DWORD PTR DS:[EAX],EDX + * 0061397C 8D7B 04 LEA EDI,DWORD PTR DS:[EBX+0x4] + * 0061397F 31C0 XOR EAX,EAX + * 00613981 B9 40010000 MOV ECX,0x140 + * 00613986 F3:AB REP STOS DWORD PTR ES:[EDI] + * 00613988 89EC MOV ESP,EBP + * 0061398A 61 POPAD + * 0061398B C3 RETN + * 0061398C 8B8CD6 A8D70500 MOV ECX,DWORD PTR DS:[ESI+EDX*8+0x5D7A8] ; jichi: #2 hook jumped here, execute the original instruction first + * 00613993 8B01 MOV EAX,DWORD PTR DS:[ECX] ; get dword split in ecx + * 00613995 3D 96010000 CMP EAX,0x196 + * 0061399A 74 07 JE SHORT .006139A3 ; translate if split is 0x196 or 0x6e + * 0061399C 83F8 6E CMP EAX,0x6E + * 0061399F 74 02 JE SHORT .006139A3 + * 006139A1 EB 07 JMP SHORT .006139AA + * 006139A3 E8 7AFEFFFF CALL .00613822 + * 006139A8 8B01 MOV EAX,DWORD PTR DS:[ECX] + * 006139AA C3 RETN + * 006139AB 60 PUSHAD + * 006139AC C745 FC A8D70500 MOV DWORD PTR SS:[EBP-0x4],0x5D7A8 + * 006139B3 EB 03 JMP SHORT .006139B8 + * 006139B5 58 POP EAX + * 006139B6 EB 05 JMP SHORT .006139BD + * 006139B8 E8 F8FFFFFF CALL .006139B5 + * 006139BD 2D BD392100 SUB EAX,0x2139BD + * 006139C2 0380 D4020000 ADD EAX,DWORD PTR DS:[EAX+0x2D4] + * 006139C8 B9 00010000 MOV ECX,0x100 + * 006139CD 8D80 00400100 LEA EAX,DWORD PTR DS:[EAX+0x14000] + * 006139D3 8945 F8 MOV DWORD PTR SS:[EBP-0x8],EAX + * 006139D6 8D0401 LEA EAX,DWORD PTR DS:[ECX+EAX] + * 006139D9 8945 F4 MOV DWORD PTR SS:[EBP-0xC],EAX + * 006139DC 8D0401 LEA EAX,DWORD PTR DS:[ECX+EAX] + * 006139DF 8945 F0 MOV DWORD PTR SS:[EBP-0x10],EAX + * 006139E2 8D0401 LEA EAX,DWORD PTR DS:[ECX+EAX] + * 006139E5 8945 EC MOV DWORD PTR SS:[EBP-0x14],EAX + * 006139E8 8D0401 LEA EAX,DWORD PTR DS:[ECX+EAX] + * 006139EB 8945 E8 MOV DWORD PTR SS:[EBP-0x18],EAX + * 006139EE 61 POPAD + * 006139EF C3 RETN + * 006139F0 0000 ADD BYTE PTR DS:[EAX],AL + * 006139F2 0000 ADD BYTE PTR DS:[EAX],AL + * 006139F4 0000 ADD BYTE PTR DS:[EAX],AL + */ +bool InsertEushullyHook() +{ + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng:Eushully: failed to get memory range"); + return false; + } + ULONG addr = MemDbg::findLastCallerAddressAfterInt3((DWORD)::GetTextExtentPoint32A, startAddress, stopAddress); + //GROWL_DWORD(addr); + if (!addr) { + ConsoleOutput("vnreng:Eushully: failed"); + return false; + } + + // BOOL GetTextExtentPoint32( + // _In_ HDC hdc, + // _In_ LPCTSTR lpString, + // _In_ int c, + // _Out_ LPSIZE lpSize + // ); + enum stack { // current stack + //retaddr = 0 // esp[0] is the return address since this is the beginning of the function + arg1_hdc = 4 * 1 // 0x4 + , arg2_lpString = 4 * 2 // 0x8 + , arg3_lc = 4 * 3 // 0xc + , arg4_lpSize = 4 * 4 // 0x10 + }; + + HookParam hp = {}; + hp.address = addr; + hp.type = USING_STRING|FIXING_SPLIT; // merging all threads + hp.offset = arg2_lpString; // arg2 = 0x4 * 2 + ConsoleOutput("vnreng: INSERT Eushully"); + NewHook(hp, "ARCGameEngine"); + return true; +} + +/** jichi 6/1/2014 AMUSE CRAFT + * Related brands: http://erogetrailers.com/brand/2047 + * Sample game: 魔女こいにっ� * See: http://sakuradite.com/topic/223 + * Sample H-code: /HBN-4*0:18@26159:MAJOKOI_try.exe (need remove context, though) + * + * Sample games: + * - 時計仕掛け�レイライン + * - きみと僕との騎士の日� * + * /HBN-4*0:18@26159:MAJOKOI_TRY.EXE + * - addr: 155993 + * - length_offset: 1 + * - module: 104464j455 + * - off: 4294967288 = 0xfffffff8 + * - split: 24 = 0x18 + * - type: 1112 = 0x458 + * + * Call graph: + * - hook reladdr: 0x26159, fun reladdr: 26150 + * - scene fun reladdr: 0x26fd0 + * - arg1 and arg3 are pointers + * - arg2 is the text + * - scenairo only reladdr: 0x26670 + * Issue for implementing embeded engine: two functions are needed to be hijacked + * + * 013c614e cc int3 + * 013c614f cc int3 + * 013c6150 /$ 55 push ebp ; jichi: function starts, this function seems to process text encoding + * 013c6151 |. 8bec mov ebp,esp + * 013c6153 |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 013c6156 |. 0fb608 movzx ecx,byte ptr ds:[eax] + * 013c6159 |. 81f9 81000000 cmp ecx,0x81 ; jichi: hook here + * 013c615f |. 7c 0d jl short majokoi_.013c616e + * 013c6161 |. 8b55 08 mov edx,dword ptr ss:[ebp+0x8] + * 013c6164 |. 0fb602 movzx eax,byte ptr ds:[edx] + * 013c6167 |. 3d 9f000000 cmp eax,0x9f + * 013c616c |. 7e 1c jle short majokoi_.013c618a + * 013c616e |> 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + * 013c6171 |. 0fb611 movzx edx,byte ptr ds:[ecx] + * 013c6174 |. 81fa e0000000 cmp edx,0xe0 + * 013c617a |. 7c 30 jl short majokoi_.013c61ac + * 013c617c |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 013c617f |. 0fb608 movzx ecx,byte ptr ds:[eax] + * 013c6182 |. 81f9 fc000000 cmp ecx,0xfc + * 013c6188 |. 7f 22 jg short majokoi_.013c61ac + * 013c618a |> 8b55 08 mov edx,dword ptr ss:[ebp+0x8] + * 013c618d |. 0fb642 01 movzx eax,byte ptr ds:[edx+0x1] + * 013c6191 |. 83f8 40 cmp eax,0x40 + * 013c6194 |. 7c 16 jl short majokoi_.013c61ac + * 013c6196 |. 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + * 013c6199 |. 0fb651 01 movzx edx,byte ptr ds:[ecx+0x1] + * 013c619d |. 81fa fc000000 cmp edx,0xfc + * 013c61a3 |. 7f 07 jg short majokoi_.013c61ac + * 013c61a5 |. b8 01000000 mov eax,0x1 + * 013c61aa |. eb 02 jmp short majokoi_.013c61ae + * 013c61ac |> 33c0 xor eax,eax + * 013c61ae |> 5d pop ebp + * 013c61af \. c3 retn + */ +static bool InsertOldPalHook() // this is used in case the new pattern does not work +{ + const BYTE bytes[] = { + 0x55, // 013c6150 /$ 55 push ebp ; jichi: function starts + 0x8b,0xec, // 013c6151 |. 8bec mov ebp,esp + 0x8b,0x45, 0x08, // 013c6153 |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + 0x0f,0xb6,0x08, // 013c6156 |. 0fb608 movzx ecx,byte ptr ds:[eax] + 0x81,0xf9 //81000000 // 013c6159 |. 81f9 81000000 cmp ecx,0x81 ; jichi: hook here + }; + enum { addr_offset = sizeof(bytes) - 2 }; + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //GROWL_DWORD(reladdr); // supposed to be 0x21650 + //GROWL_DWORD(reladdr + addr_offset); + //reladdr = 0x26159; // 魔女こいにっ�trial + if (!addr) { + ConsoleOutput("vnreng:AMUSE CRAFT: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + //hp.type = NO_CONTEXT|USING_SPLIT|DATA_INDIRECT; // 0x418 + //hp.type = NO_CONTEXT|USING_SPLIT|DATA_INDIRECT|RELATIVE_SPLIT; // Use relative address to prevent floating issue + hp.type = NO_CONTEXT|USING_SPLIT|DATA_INDIRECT; + hp.offset = -0x8; // eax + //hp.split = 0x18; + //hp.split = 0x4; // This is supposed to be the return address + hp.split = 0x20; // arg6 + hp.length_offset = 1; + ConsoleOutput("vnreng: INSERT AMUSE CRAFT"); + NewHook(hp, "Pal"); + return true; +} +static bool InsertNewPalHook() +{ + const BYTE bytes[] = { + 0x55, // 002c6ab0 55 push ebp + 0x8b,0xec, // 002c6ab1 8bec mov ebp,esp + 0x83,0xec, 0x78, // 002c6ab3 83ec 78 sub esp,0x78 + 0xa1, XX4, // 002c6ab6 a1 8c002f00 mov eax,dword ptr ds:[0x2f008c] + 0x33,0xc5, // 002c6abb 33c5 xor eax,ebp + 0x89,0x45, 0xf8 // 002c6abd 8945 f8 mov dword ptr ss:[ebp-0x8],eax + }; + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::matchBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + if (!addr) { + ConsoleOutput("vnreng:Pal: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + //hp.type = NO_CONTEXT|USING_SPLIT|DATA_INDIRECT; // 0x418 + hp.type = RELATIVE_SPLIT; // Use relative address to prevent floating issue + hp.offset = 4 * 2; // arg2 + ConsoleOutput("vnreng: INSERT Pal"); + NewHook(hp, "Pal"); + return true; +} +bool InsertPalHook() // use Old Pal first, which does not have ruby +{ return InsertOldPalHook() || InsertNewPalHook(); } + +/** jichi 7/6/2014 NeXAS + * Sample game: BALDRSKYZERO EXTREME + * + * Call graph: + * - GetGlyphOutlineA x 2 functions + * - Caller 503620: char = [arg1 + 0x1a8] + * - Caller: 500039, 4ffff0 + * edi = [esi+0x1a0] # stack size 4x3 + * arg1 = eax = [edi] + * + * 0050361f cc int3 + * 00503620 /$ 55 push ebp + * 00503621 |. 8bec mov ebp,esp + * 00503623 |. 83e4 f8 and esp,0xfffffff8 + * 00503626 |. 64:a1 00000000 mov eax,dword ptr fs:[0] + * 0050362c |. 6a ff push -0x1 + * 0050362e |. 68 15815900 push bszex.00598115 + * 00503633 |. 50 push eax + * 00503634 |. 64:8925 000000>mov dword ptr fs:[0],esp + * 0050363b |. 81ec 78010000 sub esp,0x178 + * 00503641 |. 53 push ebx + * 00503642 |. 8b5d 08 mov ebx,dword ptr ss:[ebp+0x8] + * 00503645 |. 80bb ed010000 >cmp byte ptr ds:[ebx+0x1ed],0x0 + * 0050364c |. 56 push esi + * 0050364d |. 57 push edi + * 0050364e |. 0f85 6e0b0000 jnz bszex.005041c2 + * 00503654 |. 8db3 a8010000 lea esi,dword ptr ds:[ebx+0x1a8] + * 0050365a |. c683 ed010000 >mov byte ptr ds:[ebx+0x1ed],0x1 + * 00503661 |. 837e 14 10 cmp dword ptr ds:[esi+0x14],0x10 + * 00503665 |. 72 04 jb short bszex.0050366b + * 00503667 |. 8b06 mov eax,dword ptr ds:[esi] + * 00503669 |. eb 02 jmp short bszex.0050366d + * 0050366b |> 8bc6 mov eax,esi + * 0050366d |> 8038 20 cmp byte ptr ds:[eax],0x20 + * 00503670 |. 0f84 ef0a0000 je bszex.00504165 + * 00503676 |. b9 fcc97400 mov ecx,bszex.0074c9fc + * 0050367b |. 8bfe mov edi,esi + * 0050367d |. e8 2e20f1ff call bszex.004156b0 + * 00503682 |. 84c0 test al,al + * 00503684 |. 0f85 db0a0000 jnz bszex.00504165 + * 0050368a |. 8b93 38010000 mov edx,dword ptr ds:[ebx+0x138] + * 00503690 |. 33c0 xor eax,eax + * 00503692 |. 3bd0 cmp edx,eax + * 00503694 |. 0f84 8d0a0000 je bszex.00504127 + * 0050369a |. 8b8b 3c010000 mov ecx,dword ptr ds:[ebx+0x13c] + * 005036a0 |. 3bc8 cmp ecx,eax + * 005036a2 |. 0f84 7f0a0000 je bszex.00504127 + * 005036a8 |. 894424 40 mov dword ptr ss:[esp+0x40],eax + * 005036ac |. 894424 44 mov dword ptr ss:[esp+0x44],eax + * 005036b0 |. 894424 48 mov dword ptr ss:[esp+0x48],eax + * 005036b4 |. 898424 8c01000>mov dword ptr ss:[esp+0x18c],eax + * 005036bb |. 33ff xor edi,edi + * 005036bd |. 66:897c24 60 mov word ptr ss:[esp+0x60],di + * 005036c2 |. bf 01000000 mov edi,0x1 + * 005036c7 |. 66:897c24 62 mov word ptr ss:[esp+0x62],di + * 005036cc |. 33ff xor edi,edi + * 005036ce |. 66:897c24 64 mov word ptr ss:[esp+0x64],di + * 005036d3 |. 66:897c24 66 mov word ptr ss:[esp+0x66],di + * 005036d8 |. 66:897c24 68 mov word ptr ss:[esp+0x68],di + * 005036dd |. 66:897c24 6a mov word ptr ss:[esp+0x6a],di + * 005036e2 |. 66:897c24 6c mov word ptr ss:[esp+0x6c],di + * 005036e7 |. bf 01000000 mov edi,0x1 + * 005036ec |. 66:897c24 6e mov word ptr ss:[esp+0x6e],di + * 005036f1 |. 894424 0c mov dword ptr ss:[esp+0xc],eax + * 005036f5 |. 894424 10 mov dword ptr ss:[esp+0x10],eax + * 005036f9 |. 3883 ec010000 cmp byte ptr ds:[ebx+0x1ec],al + * 005036ff |. 0f84 39010000 je bszex.0050383e + * 00503705 |. c78424 f000000>mov dword ptr ss:[esp+0xf0],bszex.00780e> + * 00503710 |. 898424 3001000>mov dword ptr ss:[esp+0x130],eax + * 00503717 |. 898424 1001000>mov dword ptr ss:[esp+0x110],eax + * 0050371e |. 898424 1401000>mov dword ptr ss:[esp+0x114],eax + * 00503725 |. c68424 8c01000>mov byte ptr ss:[esp+0x18c],0x1 + * 0050372d |. 837e 14 10 cmp dword ptr ds:[esi+0x14],0x10 + * 00503731 |. 72 02 jb short bszex.00503735 + * 00503733 |. 8b36 mov esi,dword ptr ds:[esi] + * 00503735 |> 51 push ecx + * 00503736 |. 52 push edx + * 00503737 |. 56 push esi + * 00503738 |. 8d8424 ec00000>lea eax,dword ptr ss:[esp+0xec] + * 0050373f |. 68 00ca7400 push bszex.0074ca00 ; ascii "gaiji%s%02d%02d.fil" + * 00503744 |. 50 push eax + * 00503745 |. e8 cec6f7ff call bszex.0047fe18 + * 0050374a |. 83c4 14 add esp,0x14 + * 0050374d |. 8d8c24 e000000>lea ecx,dword ptr ss:[esp+0xe0] + * 00503754 |. 51 push ecx ; /arg1 + * 00503755 |. 8d8c24 9400000>lea ecx,dword ptr ss:[esp+0x94] ; | + * 0050375c |. e8 dfeaefff call bszex.00402240 ; \bszex.00402240 + * 00503761 |. 6a 00 push 0x0 ; /arg4 = 00000000 + * 00503763 |. 8d9424 9400000>lea edx,dword ptr ss:[esp+0x94] ; | + * 0050376a |. c68424 9001000>mov byte ptr ss:[esp+0x190],0x2 ; | + * 00503772 |. a1 a8a78200 mov eax,dword ptr ds:[0x82a7a8] ; | + * 00503777 |. 52 push edx ; |arg3 + * 00503778 |. 50 push eax ; |arg2 => 00000000 + * 00503779 |. 8d8c24 fc00000>lea ecx,dword ptr ss:[esp+0xfc] ; | + * 00503780 |. 51 push ecx ; |arg1 + * 00503781 |. e8 2a0dfeff call bszex.004e44b0 ; \bszex.004e44b0 + * 00503786 |. 84c0 test al,al + * 00503788 |. 8d8c24 9000000>lea ecx,dword ptr ss:[esp+0x90] + * 0050378f |. 0f95c3 setne bl + * 00503792 |. c68424 8c01000>mov byte ptr ss:[esp+0x18c],0x1 + * 0050379a |. e8 a1baf1ff call bszex.0041f240 + * 0050379f |. 84db test bl,bl + * 005037a1 |. 74 40 je short bszex.005037e3 + * 005037a3 |. 8db424 f000000>lea esi,dword ptr ss:[esp+0xf0] + * 005037aa |. e8 6106feff call bszex.004e3e10 + * 005037af |. 8bd8 mov ebx,eax + * 005037b1 |. 895c24 0c mov dword ptr ss:[esp+0xc],ebx + * 005037b5 |. e8 5606feff call bszex.004e3e10 + * 005037ba |. 8bf8 mov edi,eax + * 005037bc |. 0faffb imul edi,ebx + * 005037bf |. 894424 10 mov dword ptr ss:[esp+0x10],eax + * 005037c3 |. 8bc7 mov eax,edi + * 005037c5 |. 8d7424 40 lea esi,dword ptr ss:[esp+0x40] + * 005037c9 |. e8 e219f1ff call bszex.004151b0 + * 005037ce |. 8b5424 40 mov edx,dword ptr ss:[esp+0x40] + * 005037d2 |. 52 push edx ; /arg1 + * 005037d3 |. 8bc7 mov eax,edi ; | + * 005037d5 |. 8db424 f400000>lea esi,dword ptr ss:[esp+0xf4] ; | + * 005037dc |. e8 8f03feff call bszex.004e3b70 ; \bszex.004e3b70 + * 005037e1 |. eb 10 jmp short bszex.005037f3 + * 005037e3 |> 8d8424 e000000>lea eax,dword ptr ss:[esp+0xe0] + * 005037ea |. 50 push eax + * 005037eb |. e8 60c5f2ff call bszex.0042fd50 + * 005037f0 |. 83c4 04 add esp,0x4 + * 005037f3 |> 8b5c24 10 mov ebx,dword ptr ss:[esp+0x10] + * 005037f7 |. 8b7c24 40 mov edi,dword ptr ss:[esp+0x40] + * 005037fb |. 8bcb mov ecx,ebx + * 005037fd |. 0faf4c24 0c imul ecx,dword ptr ss:[esp+0xc] + * 00503802 |. 33c0 xor eax,eax + * 00503804 |. 85c9 test ecx,ecx + * 00503806 |. 7e 09 jle short bszex.00503811 + * 00503808 |> c02c07 02 /shr byte ptr ds:[edi+eax],0x2 + * 0050380c |. 40 |inc eax + * 0050380d |. 3bc1 |cmp eax,ecx + * 0050380f |.^7c f7 \jl short bszex.00503808 + * 00503811 |> 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + * 00503814 |. 33c0 xor eax,eax + * 00503816 |. 8db424 f000000>lea esi,dword ptr ss:[esp+0xf0] + * 0050381d |. 8981 dc010000 mov dword ptr ds:[ecx+0x1dc],eax + * 00503823 |. 8981 e0010000 mov dword ptr ds:[ecx+0x1e0],eax + * 00503829 |. c78424 f000000>mov dword ptr ss:[esp+0xf0],bszex.00780e> + * 00503834 |. e8 4702feff call bszex.004e3a80 + * 00503839 |. e9 68010000 jmp bszex.005039a6 + * 0050383e |> 8b0d 08a58200 mov ecx,dword ptr ds:[0x82a508] + * 00503844 |. 51 push ecx ; /hwnd => null + * 00503845 |. ff15 d4e26f00 call dword ptr ds:[<&user32.getdc>] ; \getdc + * 0050384b |. 68 50b08200 push bszex.0082b050 ; /facename = "" + * 00503850 |. 6a 00 push 0x0 ; |pitchandfamily = default_pitch|ff_dontcare + * 00503852 |. 6a 02 push 0x2 ; |quality = proof_quality + * 00503854 |. 6a 00 push 0x0 ; |clipprecision = clip_default_precis + * 00503856 |. 6a 07 push 0x7 ; |outputprecision = out_tt_only_precis + * 00503858 |. 68 80000000 push 0x80 ; |charset = 128. + * 0050385d |. 6a 00 push 0x0 ; |strikeout = false + * 0050385f |. 6a 00 push 0x0 ; |underline = false + * 00503861 |. 8bf8 mov edi,eax ; | + * 00503863 |. 8b83 38010000 mov eax,dword ptr ds:[ebx+0x138] ; | + * 00503869 |. 6a 00 push 0x0 ; |italic = false + * 0050386b |. 68 84030000 push 0x384 ; |weight = fw_heavy + * 00503870 |. 99 cdq ; | + * 00503871 |. 6a 00 push 0x0 ; |orientation = 0x0 + * 00503873 |. 2bc2 sub eax,edx ; | + * 00503875 |. 8b93 3c010000 mov edx,dword ptr ds:[ebx+0x13c] ; | + * 0050387b |. 6a 00 push 0x0 ; |escapement = 0x0 + * 0050387d |. d1f8 sar eax,1 ; | + * 0050387f |. 50 push eax ; |width + * 00503880 |. 52 push edx ; |height + * 00503881 |. ff15 48e06f00 call dword ptr ds:[<&gdi32.createfonta>] ; \createfonta + * 00503887 |. 50 push eax ; /hobject + * 00503888 |. 57 push edi ; |hdc + * 00503889 |. 894424 30 mov dword ptr ss:[esp+0x30],eax ; | + * 0050388d |. ff15 4ce06f00 call dword ptr ds:[<&gdi32.selectobject>>; \selectobject + * 00503893 |. 894424 1c mov dword ptr ss:[esp+0x1c],eax + * 00503897 |. 8d8424 4801000>lea eax,dword ptr ss:[esp+0x148] + * 0050389e |. 50 push eax ; /ptextmetric + * 0050389f |. 57 push edi ; |hdc + * 005038a0 |. ff15 50e06f00 call dword ptr ds:[<&gdi32.gettextmetric>; \gettextmetricsa + * 005038a6 |. 837e 14 10 cmp dword ptr ds:[esi+0x14],0x10 + * 005038aa |. 72 02 jb short bszex.005038ae + * 005038ac |. 8b36 mov esi,dword ptr ds:[esi] + * 005038ae |> 56 push esi ; /arg1 + * 005038af |. e8 deccf7ff call bszex.00480592 ; \bszex.00480592 + * 005038b4 |. 83c4 04 add esp,0x4 + * 005038b7 |. 8d4c24 60 lea ecx,dword ptr ss:[esp+0x60] + * 005038bb |. 51 push ecx ; /pmat2 + * 005038bc |. 6a 00 push 0x0 ; |buffer = null + * 005038be |. 6a 00 push 0x0 ; |bufsize = 0x0 + * 005038c0 |. 8d9424 d800000>lea edx,dword ptr ss:[esp+0xd8] ; | + * 005038c7 |. 52 push edx ; |pmetrics + * 005038c8 |. 6a 06 push 0x6 ; |format = ggo_gray8_bitmap + * 005038ca |. 50 push eax ; |char + * 005038cb |. 57 push edi ; |hdc + * 005038cc |. 894424 30 mov dword ptr ss:[esp+0x30],eax ; | + * 005038d0 |. ff15 54e06f00 call dword ptr ds:[<&gdi32.getglyphoutli>; \getglyphoutlinea + * 005038d6 |. 8bd8 mov ebx,eax + * 005038d8 |. 85db test ebx,ebx + * 005038da |. 0f84 d5070000 je bszex.005040b5 + * 005038e0 |. 83fb ff cmp ebx,-0x1 + * 005038e3 |. 0f84 cc070000 je bszex.005040b5 + * 005038e9 |. 8d7424 40 lea esi,dword ptr ss:[esp+0x40] + * 005038ed |. e8 be18f1ff call bszex.004151b0 + * 005038f2 |. 8b4c24 40 mov ecx,dword ptr ss:[esp+0x40] + * 005038f6 |. 8d4424 60 lea eax,dword ptr ss:[esp+0x60] + * 005038fa |. 50 push eax ; /pmat2 + * 005038fb |. 8b4424 18 mov eax,dword ptr ss:[esp+0x18] ; | + * 005038ff |. 51 push ecx ; |buffer + * 00503900 |. 53 push ebx ; |bufsize + * 00503901 |. 8d9424 d800000>lea edx,dword ptr ss:[esp+0xd8] ; | + * 00503908 |. 52 push edx ; |pmetrics + * 00503909 |. 6a 06 push 0x6 ; |format = ggo_gray8_bitmap + * 0050390b |. 50 push eax ; |char + * 0050390c |. 57 push edi ; |hdc + * 0050390d |. ff15 54e06f00 call dword ptr ds:[<&gdi32.getglyphoutli>; \getglyphoutlinea + * 00503913 |. 8b4c24 1c mov ecx,dword ptr ss:[esp+0x1c] + * 00503917 |. 51 push ecx ; /hobject + * 00503918 |. 57 push edi ; |hdc + * 00503919 |. ff15 4ce06f00 call dword ptr ds:[<&gdi32.selectobject>>; \selectobject + * 0050391f |. 8b15 08a58200 mov edx,dword ptr ds:[0x82a508] + * 00503925 |. 57 push edi ; /hdc + * 00503926 |. 52 push edx ; |hwnd => null + * 00503927 |. ff15 a4e26f00 call dword ptr ds:[<&user32.releasedc>] ; \releasedc + * 0050392d |. 8b4424 28 mov eax,dword ptr ss:[esp+0x28] + * 00503931 |. 50 push eax ; /hobject + * 00503932 |. ff15 58e06f00 call dword ptr ds:[<&gdi32.deleteobject>>; \deleteobject + * 00503938 |. 8bb424 cc00000>mov esi,dword ptr ss:[esp+0xcc] + * 0050393f |. 8b8c24 d000000>mov ecx,dword ptr ss:[esp+0xd0] + * 00503946 |. 83c6 03 add esi,0x3 + * 00503949 |. 81e6 fcff0000 and esi,0xfffc + * 0050394f |. 8bd1 mov edx,ecx + * 00503951 |. 0fafd6 imul edx,esi + * 00503954 |. 897424 0c mov dword ptr ss:[esp+0xc],esi + * 00503958 |. 894c24 10 mov dword ptr ss:[esp+0x10],ecx + * 0050395c |. 3bda cmp ebx,edx + * 0050395e |. 74 1a je short bszex.0050397a + */ +bool InsertNeXASHook() +{ + // There are two GetGlyphOutlineA, both of which seem to have the same texts + ULONG addr = MemDbg::findCallAddress((ULONG)::GetGlyphOutlineA, module_base_, module_limit_); + if (!addr) { + ConsoleOutput("vnreng:NexAS: failed"); + return false; + } + + // DWORD GetGlyphOutline( + // _In_ HDC hdc, + // _In_ UINT uChar, + // _In_ UINT uFormat, + // _Out_ LPGLYPHMETRICS lpgm, + // _In_ DWORD cbBuffer, + // _Out_ LPVOID lpvBuffer, + // _In_ const MAT2 *lpmat2 + // ); + enum stack { // current stack + arg1_hdc = 4 * 0 // starting from 0 before function call + , arg2_uChar = 4 * 1 + , arg3_uFormat = 4 * 2 + , arg4_lpgm = 4 * 3 + , arg5_cbBuffer = 4 * 4 + , arg6_lpvBuffer = 4 * 5 + , arg7_lpmat2 = 4 * 6 + }; + + HookParam hp = {}; + //hp.address = (DWORD)::GetGlyphOutlineA; + hp.address = addr; + //hp.type = USING_STRING|USING_SPLIT; + hp.type = BIG_ENDIAN|NO_CONTEXT|USING_SPLIT; + hp.length_offset = 1; // determine string length at runtime + hp.offset = arg2_uChar; // = 0x4, arg2 before the function call, so it is: 0x4 * (2-1) = 4 + + // Either lpgm or lpmat2 are good choices + hp.split = arg4_lpgm; // = 0xc, arg4 + //hp.split = arg7_lpmat2; // = 0x18, arg7 + + ConsoleOutput("vnreng: INSERT NeXAS"); + NewHook(hp, "NeXAS"); + return true; +} + +/** jichi 7/6/2014 YukaSystem2 + * Sample game: セミラミスの天秤 + * + * Observations from Debug: + * - Ollydbg got UTF8 text memory address + * - Hardware break points have loops on 0x4010ED + * - The hooked function seems to take 3 parameters, and arg3 is the right text + * - The text appears character by character + * + * Runtime stack: + * - return address + * - arg1 pointer's pointer + * - arg2 text + * - arg3 pointer's pointer + * - code address or -1, maybe a handle + * - unknown pointer + * - return address + * - usually zero + * + * 0040109d cc int3 + * 0040109e cc int3 + * 0040109f cc int3 + * 004010a0 /$ 55 push ebp + * 004010a1 |. 8bec mov ebp,esp + * 004010a3 |. 8b45 14 mov eax,dword ptr ss:[ebp+0x14] + * 004010a6 |. 50 push eax ; /arg4 + * 004010a7 |. 8b4d 10 mov ecx,dword ptr ss:[ebp+0x10] ; | + * 004010aa |. 51 push ecx ; |arg3 + * 004010ab |. 8b55 0c mov edx,dword ptr ss:[ebp+0xc] ; | + * 004010ae |. 52 push edx ; |arg2 + * 004010af |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8] ; | + * 004010b2 |. 50 push eax ; |arg1 + * 004010b3 |. e8 48ffffff call semirami.00401000 ; \semirami.00401000 + * 004010b8 |. 83c4 10 add esp,0x10 + * 004010bb |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 004010be |. 5d pop ebp + * 004010bf \. c3 retn + * 004010c0 /$ 55 push ebp + * 004010c1 |. 8bec mov ebp,esp + * 004010c3 |. 8b45 14 mov eax,dword ptr ss:[ebp+0x14] + * 004010c6 |. 50 push eax ; /arg4 + * 004010c7 |. 8b4d 10 mov ecx,dword ptr ss:[ebp+0x10] ; | + * 004010ca |. 51 push ecx ; |arg3 + * 004010cb |. 8b55 0c mov edx,dword ptr ss:[ebp+0xc] ; | + * 004010ce |. 52 push edx ; |arg2 + * 004010cf |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8] ; | + * 004010d2 |. 50 push eax ; |arg1 + * 004010d3 |. e8 58ffffff call semirami.00401030 ; \semirami.00401030 + * 004010d8 |. 83c4 10 add esp,0x10 + * 004010db |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 004010de |. 5d pop ebp + * 004010df \. c3 retn + * 004010e0 /$ 55 push ebp ; jichi: function begin, hook here, bp-based frame, arg2 is the text + * 004010e1 |. 8bec mov ebp,esp + * 004010e3 |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8] ; jichi: ebp+0x8 = arg2 + * 004010e6 |. 8b4d 0c mov ecx,dword ptr ss:[ebp+0xc] ; jichi: arg3 is also a pointer of pointer + * 004010e9 |. 8a11 mov dl,byte ptr ds:[ecx] + * 004010eb |. 8810 mov byte ptr ds:[eax],dl ; jichi: eax is the data + * 004010ed |. 5d pop ebp + * 004010ee \. c3 retn + * 004010ef cc int3 + */ + +// Ignore image and music file names +// Sample text: "Voice\tou00012.ogg""運命論って云うのかなあ……神さまを信じてる人が多かったからだろうね、何があっても、それ�神さまが�刁�ちに与えられた試練なんだって、そ぀�ってたみたい。勿論、今でもそ぀��てあ�人はぁ�ぱぁ�るん�けど� +// Though the input string is UTF-8, it should be ASCII compatible. +static bool _yk2garbage(const char *p) +{ + //Q_ASSERT(p); + while (char ch = *p++) { + if (!( + ch >= '0' && ch <= '9' || + ch >= 'A' && ch <= 'z' || // also ignore ASCII 91-96: [ \ ] ^ _ ` + ch == '"' || ch == '.' || ch == '-' || ch == '#' + )) + return false; + } + return true; +} + +// Get text from arg2 +static void SpecialHookYukaSystem2(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD arg2 = argof(2, esp_base), // [esp+0x8] + arg3 = argof(3, esp_base); // [esp+0xc] + //arg4 = argof(4, esp_base); // there is no arg4. arg4 is properlly a function pointer + LPCSTR text = (LPCSTR)arg2; + if (*text && !_yk2garbage(text)) { // I am sure this could be null + *data = (DWORD)text; + *len = ::strlen(text); // UTF-8 is null-terminated + if (arg3) + *split = *(DWORD *)arg3; + } +} + +bool InsertYukaSystem2Hook() +{ + const BYTE bytes[] = { + 0x55, // 004010e0 /$ 55 push ebp ; jichi; hook here + 0x8b,0xec, // 004010e1 |. 8bec mov ebp,esp + 0x8b,0x45, 0x08, // 004010e3 |. 8b45 08 mov eax,dword ptr ss:[ebp+0x8] ; jichi: ebp+0x8 = arg2 + 0x8b,0x4d, 0x0c, // 004010e6 |. 8b4d 0c mov ecx,dword ptr ss:[ebp+0xc] + 0x8a,0x11, // 004010e9 |. 8a11 mov dl,byte ptr ds:[ecx] + 0x88,0x10, // 004010eb |. 8810 mov byte ptr ds:[eax],dl ; jichi: eax is the address to text + 0x5d, // 004010ed |. 5d pop ebp + 0xc3 // 004010ee \. c3 retn + }; + //enum { addr_offset = 0 }; + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //GROWL_DWORD(addr); // supposed to be 0x4010e0 + if (!addr) { + ConsoleOutput("vnreng:YukaSystem2: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.type = NO_CONTEXT|USING_STRING|USING_UTF8; // UTF-8, though + hp.text_fun = SpecialHookYukaSystem2; + ConsoleOutput("vnreng: INSERT YukaSystem2"); + NewHook(hp, "YukaSystem2"); + return true; +} + +/** jichi 8/2/2014 2RM + * Sample games: + * - [エロイッ�] 父娘� �いけなね�作り2- /HBN-20*0@54925:oyakoai.exe + * - [エロイッ�] ぁ�なね�作り �親友�お母さんに種付けしまくる1週間�-- /HS-1C@46FC9D (not used) + * + * Observations from Debug of 父娘�: + * - The executable shows product name as 2RM - Adventure Engine + * - 2 calls to GetGlyphOutlineA with incompleted game + * - Memory location of the text is fixed + * - The LAST place accessing the text is hooked + * - The actual text has pattern like this {surface,ruby} and hence not hooked + * + * /HBN-20*0@54925:oyakoai.exe + * - addr: 346405 = 0x54925 + * - length_offset: 1 + * - module: 3918223605 + * - off: 4294967260 = 0xffffffdc = -0x24 -- 0x24 comes from mov ebp,dword ptr ss:[esp+0x24] + * - type: 1096 = 0x448 + * + * This is a very long function + * 父娘�: + * - 004548e1 |. 84db test bl,bl + * - 004548e3 |. 8b7424 20 mov esi,dword ptr ss:[esp+0x20] + * - 004548e7 |. 74 08 je short oyakoai.004548f1 + * - 004548e9 |. c74424 24 0000>mov dword ptr ss:[esp+0x24],0x0 + * - 004548f1 |> 8b6c24 3c mov ebp,dword ptr ss:[esp+0x3c] + * - 004548f5 |. 837d 5c 00 cmp dword ptr ss:[ebp+0x5c],0x0 + * - 004548f9 |. c74424 18 0000>mov dword ptr ss:[esp+0x18],0x0 + * - 00454901 |. 0f8e da000000 jle oyakoai.004549e1 + * - 00454907 |. 8b6c24 24 mov ebp,dword ptr ss:[esp+0x24] + * - 0045490b |. eb 0f jmp short oyakoai.0045491c + * - 0045490d | 8d49 00 lea ecx,dword ptr ds:[ecx] + * - 00454910 |> 8b15 50bd6c00 mov edx,dword ptr ds:[0x6cbd50] + * - 00454916 |. 8b0d 94bd6c00 mov ecx,dword ptr ds:[0x6cbd94] + * - 0045491c |> 803f 00 cmp byte ptr ds:[edi],0x0 + * - 0045491f |. 0f84 db000000 je oyakoai.00454a00 + * - 00454925 |. 0fb717 movzx edx,word ptr ds:[edi] ; jichi: hook here + * - 00454928 |. 8b4c24 10 mov ecx,dword ptr ss:[esp+0x10] + * - 0045492c |. 52 push edx + * - 0045492d |. 894c24 2c mov dword ptr ss:[esp+0x2c],ecx + * - 00454931 |. e8 9a980100 call oyakoai.0046e1d0 + * - 00454936 |. 83c4 04 add esp,0x4 + * - 00454939 |. 85c0 test eax,eax + * - 0045493b |. 74 50 je short oyakoai.0045498d + * - 0045493d |. 0335 50bd6c00 add esi,dword ptr ds:[0x6cbd50] + * - 00454943 |. 84db test bl,bl + * - 00454945 |. 74 03 je short oyakoai.0045494a + * - 00454947 |. 83c5 02 add ebp,0x2 + * - 0045494a |> 3b7424 1c cmp esi,dword ptr ss:[esp+0x1c] + * - 0045494e |. a1 54bd6c00 mov eax,dword ptr ds:[0x6cbd54] + * - 00454953 |. 7f 12 jg short oyakoai.00454967 + * - 00454955 |. 84db test bl,bl + * - 00454957 |. 0f84 ea000000 je oyakoai.00454a47 + * - 0045495d |. 3b6c24 40 cmp ebp,dword ptr ss:[esp+0x40] + * - 00454961 |. 0f85 e0000000 jnz oyakoai.00454a47 + * - 00454967 |> 014424 10 add dword ptr ss:[esp+0x10],eax + * - 0045496b |. 84db test bl,bl + * - 0045496d |. 8b7424 20 mov esi,dword ptr ss:[esp+0x20] + * - 00454971 |. 0f84 d0000000 je oyakoai.00454a47 + * - 00454977 |. 3b6c24 40 cmp ebp,dword ptr ss:[esp+0x40] + * - 0045497b |. 0f85 c6000000 jnz oyakoai.00454a47 + * - 00454981 |. 33ed xor ebp,ebp + * - 00454983 |. 83c7 02 add edi,0x2 + * - 00454986 |. 834424 18 01 add dword ptr ss:[esp+0x18],0x1 + * - 0045498b |. eb 3c jmp short oyakoai.004549c9 + * - 0045498d |> a1 50bd6c00 mov eax,dword ptr ds:[0x6cbd50] + * - 00454992 |. d1e8 shr eax,1 + * - 00454994 |. 03f0 add esi,eax + * - 00454996 |. 84db test bl,bl + * - 00454998 |. 74 03 je short oyakoai.0045499d + * - 0045499a |. 83c5 01 add ebp,0x1 + * - 0045499d |> 3b7424 1c cmp esi,dword ptr ss:[esp+0x1c] + * - 004549a1 |. a1 54bd6c00 mov eax,dword ptr ds:[0x6cbd54] + * - 004549a6 |. 7f 0a jg short oyakoai.004549b2 + * - 004549a8 |. 84db test bl,bl + * + * ぁ�なね�作り: + * 00454237 c74424 24 020000>mov dword ptr ss:[esp+0x24],0x2 + * 0045423f 3bf5 cmp esi,ebp + * 00454241 7f 0e jg short .00454251 + * 00454243 84db test bl,bl + * 00454245 74 1e je short .00454265 + * 00454247 8b6c24 24 mov ebp,dword ptr ss:[esp+0x24] + * 0045424b 3b6c24 40 cmp ebp,dword ptr ss:[esp+0x40] + * 0045424f 75 14 jnz short .00454265 + * 00454251 014424 10 add dword ptr ss:[esp+0x10],eax + * 00454255 84db test bl,bl + * 00454257 8b7424 20 mov esi,dword ptr ss:[esp+0x20] + * 0045425b 74 08 je short .00454265 + * 0045425d c74424 24 000000>mov dword ptr ss:[esp+0x24],0x0 + * 00454265 8b6c24 3c mov ebp,dword ptr ss:[esp+0x3c] + * 00454269 837d 5c 00 cmp dword ptr ss:[ebp+0x5c],0x0 + * 0045426d c74424 18 000000>mov dword ptr ss:[esp+0x18],0x0 + * 00454275 0f8e d7000000 jle .00454352 + * 0045427b 8b6c24 24 mov ebp,dword ptr ss:[esp+0x24] + * 0045427f eb 0c jmp short .0045428d + * 00454281 8b15 18ad6c00 mov edx,dword ptr ds:[0x6cad18] + * 00454287 8b0d 5cad6c00 mov ecx,dword ptr ds:[0x6cad5c] + * 0045428d 803f 00 cmp byte ptr ds:[edi],0x0 + * 00454290 0f84 db000000 je .00454371 + * 00454296 0fb717 movzx edx,word ptr ds:[edi] ; jichi: hook here + * 00454299 8b4c24 10 mov ecx,dword ptr ss:[esp+0x10] + * 0045429d 52 push edx + * 0045429e 894c24 2c mov dword ptr ss:[esp+0x2c],ecx + * 004542a2 e8 498a0100 call .0046ccf0 + * 004542a7 83c4 04 add esp,0x4 + * 004542aa 85c0 test eax,eax + * 004542ac 74 50 je short .004542fe + * 004542ae 0335 18ad6c00 add esi,dword ptr ds:[0x6cad18] + * 004542b4 84db test bl,bl + * 004542b6 74 03 je short .004542bb + * 004542b8 83c5 02 add ebp,0x2 + * 004542bb 3b7424 1c cmp esi,dword ptr ss:[esp+0x1c] + * 004542bf a1 1cad6c00 mov eax,dword ptr ds:[0x6cad1c] + * 004542c4 7f 12 jg short .004542d8 + * 004542c6 84db test bl,bl + * 004542c8 0f84 ea000000 je .004543b8 + * 004542ce 3b6c24 40 cmp ebp,dword ptr ss:[esp+0x40] + * 004542d2 0f85 e0000000 jnz .004543b8 + * 004542d8 014424 10 add dword ptr ss:[esp+0x10],eax + * 004542dc 84db test bl,bl + * 004542de 8b7424 20 mov esi,dword ptr ss:[esp+0x20] + * 004542e2 0f84 d0000000 je .004543b8 + * 004542e8 3b6c24 40 cmp ebp,dword ptr ss:[esp+0x40] + * 004542ec 0f85 c6000000 jnz .004543b8 + * 004542f2 33ed xor ebp,ebp + * 004542f4 83c7 02 add edi,0x2 + * 004542f7 834424 18 01 add dword ptr ss:[esp+0x18],0x1 + * 004542fc eb 3c jmp short .0045433a + * 004542fe a1 18ad6c00 mov eax,dword ptr ds:[0x6cad18] + * 00454303 d1e8 shr eax,1 + * 00454305 03f0 add esi,eax + * 00454307 84db test bl,bl + * 00454309 74 03 je short .0045430e + * 0045430b 83c5 01 add ebp,0x1 + */ +bool Insert2RMHook() +{ + const BYTE bytes[] = { + 0x80,0x3f, 0x00, // 0045428d 803f 00 cmp byte ptr ds:[edi],0x0 + 0x0f,0x84, 0xdb,0x00,0x00,0x00, // 00454290 0f84 db000000 je .00454371 + 0x0f,0xb7,0x17, // 00454296 0fb717 movzx edx,word ptr ds:[edi] ; jichi: hook here + 0x8b,0x4c,0x24, 0x10, // 00454299 8b4c24 10 mov ecx,dword ptr ss:[esp+0x10] + 0x52, // 0045429d 52 push edx + 0x89,0x4c,0x24, 0x2c, // 0045429e 894c24 2c mov dword ptr ss:[esp+0x2c],ecx + 0xe8 //, 498a0100 // 004542a2 e8 498a0100 call .0046ccf0 + }; + enum { addr_offset = 0x00454296 - 0x0045428d }; + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //GROWL_DWORD(addr); // supposed to be 0x4010e0 + if (!addr) { + ConsoleOutput("vnreng:2RM: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.length_offset = 1; + hp.offset = -0x24; + hp.type = NO_CONTEXT|DATA_INDIRECT; + ConsoleOutput("vnreng: INSERT 2RM"); + NewHook(hp, "2RM"); + return true; +} + +/** jichi 8/2/2014 side-B + * Sample games: + * - [side-B] メルトピア -- /HS-4@B4452:Martopia.exe + * + * Observations: + * + * /HS-4@B4452:Martopia.exe + * - addr: 738386 = 0xb4452 + * - module: 3040177000 + * - off: 4294967288 = 0xfffffff8 = -0x8 + * - type: 65 = 0x41 + * + * Sample stack structure: + * - 0016F558 00EB74E9 RETURN to Martopia.00EB74E9 + * - 0016F55C 0060EE30 ; jichi: this is the text + * - 0016F560 0016F5C8 + * - 0016F564 082CAA98 + * - 0016F568 00EBE735 RETURN to Martopia.00EBE735 from Martopia.00EB74C0 + * + * 00f6440e cc int3 + * 00f6440f cc int3 + * 00f64410 55 push ebp ; jichi: hook here, text in arg1 ([EncodeSystemPointer(+4]) + * 00f64411 8bec mov ebp,esp + * 00f64413 6a ff push -0x1 + * 00f64415 68 c025fb00 push martopia.00fb25c0 + * 00f6441a 64:a1 00000000 mov eax,dword ptr fs:[0] + * 00f64420 50 push eax + * 00f64421 83ec 3c sub esp,0x3c + * 00f64424 a1 c8620101 mov eax,dword ptr ds:[0x10162c8] + * 00f64429 33c5 xor eax,ebp + * 00f6442b 8945 f0 mov dword ptr ss:[ebp-0x10],eax + * 00f6442e 53 push ebx + * 00f6442f 56 push esi + * 00f64430 57 push edi + * 00f64431 50 push eax + * 00f64432 8d45 f4 lea eax,dword ptr ss:[ebp-0xc] + * 00f64435 64:a3 00000000 mov dword ptr fs:[0],eax + * 00f6443b 8bf9 mov edi,ecx + * 00f6443d 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + * 00f64440 33db xor ebx,ebx + * 00f64442 3bcb cmp ecx,ebx + * 00f64444 74 40 je short martopia.00f64486 + * 00f64446 8bc1 mov eax,ecx + * 00f64448 c745 e8 0f000000 mov dword ptr ss:[ebp-0x18],0xf + * 00f6444f 895d e4 mov dword ptr ss:[ebp-0x1c],ebx + * 00f64452 885d d4 mov byte ptr ss:[ebp-0x2c],bl ; jichi: or hook here, get text in eax + * 00f64455 8d70 01 lea esi,dword ptr ds:[eax+0x1] + * 00f64458 8a10 mov dl,byte ptr ds:[eax] + * 00f6445a 40 inc eax + * 00f6445b 3ad3 cmp dl,bl + * 00f6445d ^75 f9 jnz short martopia.00f64458 + * 00f6445f 2bc6 sub eax,esi + * 00f64461 50 push eax + * 00f64462 51 push ecx + * 00f64463 8d4d d4 lea ecx,dword ptr ss:[ebp-0x2c] + * 00f64466 e8 f543f5ff call martopia.00eb8860 + * 00f6446b 8d45 d4 lea eax,dword ptr ss:[ebp-0x2c] + * 00f6446e 50 push eax + * 00f6446f 8d4f 3c lea ecx,dword ptr ds:[edi+0x3c] + * 00f64472 895d fc mov dword ptr ss:[ebp-0x4],ebx + * 00f64475 e8 16d7f8ff call martopia.00ef1b90 + * 00f6447a 837d e8 10 cmp dword ptr ss:[ebp-0x18],0x10 + * 00f6447e 72 47 jb short martopia.00f644c7 + * 00f64480 8b4d d4 mov ecx,dword ptr ss:[ebp-0x2c] + * 00f64483 51 push ecx + * 00f64484 eb 38 jmp short martopia.00f644be + * 00f64486 53 push ebx + * 00f64487 68 a11efd00 push martopia.00fd1ea1 + * 00f6448c 8d4d b8 lea ecx,dword ptr ss:[ebp-0x48] + * 00f6448f c745 cc 0f000000 mov dword ptr ss:[ebp-0x34],0xf + * 00f64496 895d c8 mov dword ptr ss:[ebp-0x38],ebx + * 00f64499 885d b8 mov byte ptr ss:[ebp-0x48],bl + * 00f6449c e8 bf43f5ff call martopia.00eb8860 + * 00f644a1 8d55 b8 lea edx,dword ptr ss:[ebp-0x48] + * 00f644a4 52 push edx + * 00f644a5 8d4f 3c lea ecx,dword ptr ds:[edi+0x3c] + * 00f644a8 c745 fc 01000000 mov dword ptr ss:[ebp-0x4],0x1 + * 00f644af e8 dcd6f8ff call martopia.00ef1b90 + * 00f644b4 837d cc 10 cmp dword ptr ss:[ebp-0x34],0x10 + * 00f644b8 72 0d jb short martopia.00f644c7 + * 00f644ba 8b45 b8 mov eax,dword ptr ss:[ebp-0x48] + * 00f644bd 50 push eax + * 00f644be ff15 f891fc00 call dword ptr ds:[<&msvcr100.??3@yaxpax>; msvcr100.??3@yaxpax@z + * 00f644c4 83c4 04 add esp,0x4 + * 00f644c7 8b4d f4 mov ecx,dword ptr ss:[ebp-0xc] + * 00f644ca 64:890d 00000000 mov dword ptr fs:[0],ecx + * 00f644d1 59 pop ecx + * 00f644d2 5f pop edi + * 00f644d3 5e pop esi + * 00f644d4 5b pop ebx + * 00f644d5 8b4d f0 mov ecx,dword ptr ss:[ebp-0x10] + * 00f644d8 33cd xor ecx,ebp + * 00f644da e8 77510400 call martopia.00fa9656 + * 00f644df 8be5 mov esp,ebp + * 00f644e1 5d pop ebp + * 00f644e2 c2 0400 retn 0x4 + * 00f644e5 cc int3 + * 00f644e6 cc int3 + */ +bool InsertSideBHook() +{ + const BYTE bytes[] = { + 0x64,0xa3, 0x00,0x00,0x00,0x00, // 00f64435 64:a3 00000000 mov dword ptr fs:[0],eax + 0x8b,0xf9, // 00f6443b 8bf9 mov edi,ecx + 0x8b,0x4d, 0x08, // 00f6443d 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + 0x33,0xdb, // 00f64440 33db xor ebx,ebx + 0x3b,0xcb, // 00f64442 3bcb cmp ecx,ebx + 0x74, 0x40, // 00f64444 74 40 je short martopia.00f64486 + 0x8b,0xc1, // 00f64446 8bc1 mov eax,ecx + 0xc7,0x45, 0xe8, 0x0f,0x00,0x00,0x00, // 00f64448 c745 e8 0f000000 mov dword ptr ss:[ebp-0x18],0xf + 0x89,0x5d, 0xe4, // 00f6444f 895d e4 mov dword ptr ss:[ebp-0x1c],ebx + 0x88,0x5d, 0xd4 // 00f64452 885d d4 mov byte ptr ss:[ebp-0x2c],bl + }; + enum { addr_offset = 0x00f64410 - 0x00f64435 }; // distance to the beginning of the function + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //GROWL_DWORD(addr); // supposed to be 0x4010e0 + if (!addr) { + ConsoleOutput("vnreng:SideB: pattern not found"); + return false; + } + addr += addr_offset; + enum : BYTE { push_ebp = 0x55 }; // 011d4c80 /$ 55 push ebp + if (*(BYTE *)addr != push_ebp) { + ConsoleOutput("vnreng:SideB: pattern found but the function offset is invalid"); + return false; + } + //GROWL_DWORD(addr); + + HookParam hp = {}; + hp.address = addr; + //hp.length_offset = 1; + hp.offset = 4; // [esp+4] == arg1 + hp.type = USING_STRING|NO_CONTEXT|USING_SPLIT|RELATIVE_SPLIT; // NO_CONTEXT && RELATIVE_SPLIT to get rid of floating return address + //hp.split = 0; // use retaddr as split + ConsoleOutput("vnreng: INSERT SideB"); + NewHook(hp, "SideB"); + return true; +} + +/** jichi 9/8/2014 EXP, http://www.exp-inc.jp + * Maker: EXP, 5pb + * Sample game: 剣の街�異邦人 + * + * There are three matched memory addresses with SHIFT-JIS. + * The middle one is used as it is aligned with zeros. + * The memory address is fixed. + * + * There are three functions found using hardware breakpoints. + * The last one is used as the first two are looped. + * + * reladdr = 0x138020 + * + * baseaddr = 0x00120000 + * + * 0025801d cc int3 + * 0025801e cc int3 + * 0025801f cc int3 + * 00258020 55 push ebp ; jichi: hook here + * 00258021 8bec mov ebp,esp + * 00258023 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 00258026 83ec 08 sub esp,0x8 + * 00258029 85c0 test eax,eax + * 0025802b 0f84 d8000000 je .00258109 + * 00258031 837d 10 00 cmp dword ptr ss:[ebp+0x10],0x0 + * 00258035 0f84 ce000000 je .00258109 + * 0025803b 8b10 mov edx,dword ptr ds:[eax] ; jichi: edx is the text + * 0025803d 8b45 0c mov eax,dword ptr ss:[ebp+0xc] + * 00258040 53 push ebx + * 00258041 56 push esi + * 00258042 c745 f8 00000000 mov dword ptr ss:[ebp-0x8],0x0 + * 00258049 8945 fc mov dword ptr ss:[ebp-0x4],eax + * 0025804c 57 push edi + * 0025804d 8d49 00 lea ecx,dword ptr ds:[ecx] + * 00258050 8a0a mov cl,byte ptr ds:[edx] jichi: text in accessed in edx + * 00258052 8a45 14 mov al,byte ptr ss:[ebp+0x14] + * 00258055 3ac1 cmp al,cl + * 00258057 74 7a je short .002580d3 + * 00258059 8b7d 10 mov edi,dword ptr ss:[ebp+0x10] + * 0025805c 8b5d fc mov ebx,dword ptr ss:[ebp-0x4] + * 0025805f 33f6 xor esi,esi + * 00258061 8bc2 mov eax,edx + * 00258063 80f9 81 cmp cl,0x81 + * 00258066 72 05 jb short .0025806d + * 00258068 80f9 9f cmp cl,0x9f + * 0025806b 76 0a jbe short .00258077 + * 0025806d 80f9 e0 cmp cl,0xe0 + * 00258070 72 1d jb short .0025808f + * 00258072 80f9 fc cmp cl,0xfc + * 00258075 77 18 ja short .0025808f + * 00258077 8b45 fc mov eax,dword ptr ss:[ebp-0x4] + * 0025807a 85c0 test eax,eax + * 0025807c 74 05 je short .00258083 + * 0025807e 8808 mov byte ptr ds:[eax],cl + * 00258080 8d58 01 lea ebx,dword ptr ds:[eax+0x1] + * 00258083 8b7d 10 mov edi,dword ptr ss:[ebp+0x10] + * 00258086 8d42 01 lea eax,dword ptr ds:[edx+0x1] + * 00258089 be 01000000 mov esi,0x1 + * 0025808e 4f dec edi + * 0025808f 85ff test edi,edi + * 00258091 74 36 je short .002580c9 + * 00258093 85db test ebx,ebx + * 00258095 74 04 je short .0025809b + * 00258097 8a08 mov cl,byte ptr ds:[eax] + * 00258099 880b mov byte ptr ds:[ebx],cl + * 0025809b 46 inc esi + * 0025809c 33c0 xor eax,eax + * 0025809e 66:3bc6 cmp ax,si + * 002580a1 7f 47 jg short .002580ea + * 002580a3 0fbfce movsx ecx,si + * 002580a6 03d1 add edx,ecx + * 002580a8 3945 fc cmp dword ptr ss:[ebp-0x4],eax + * 002580ab 74 03 je short .002580b0 + * 002580ad 014d fc add dword ptr ss:[ebp-0x4],ecx + * 002580b0 294d 10 sub dword ptr ss:[ebp+0x10],ecx + * 002580b3 014d f8 add dword ptr ss:[ebp-0x8],ecx + * 002580b6 8a0a mov cl,byte ptr ds:[edx] + * 002580b8 80f9 0a cmp cl,0xa + * 002580bb 74 20 je short .002580dd + * 002580bd 80f9 0d cmp cl,0xd + * 002580c0 74 1b je short .002580dd + * 002580c2 3945 10 cmp dword ptr ss:[ebp+0x10],eax + * 002580c5 ^75 89 jnz short .00258050 + * 002580c7 eb 21 jmp short .002580ea + * 002580c9 85db test ebx,ebx + * 002580cb 74 1d je short .002580ea + * 002580cd c643 ff 00 mov byte ptr ds:[ebx-0x1],0x0 + * 002580d1 eb 17 jmp short .002580ea + * 002580d3 84c0 test al,al + * 002580d5 74 13 je short .002580ea + * 002580d7 42 inc edx + * 002580d8 ff45 f8 inc dword ptr ss:[ebp-0x8] + * 002580db eb 0d jmp short .002580ea + * 002580dd 8a42 01 mov al,byte ptr ds:[edx+0x1] + * 002580e0 42 inc edx + * 002580e1 3c 0a cmp al,0xa + * 002580e3 74 04 je short .002580e9 + * 002580e5 3c 0d cmp al,0xd + * 002580e7 75 01 jnz short .002580ea + * 002580e9 42 inc edx + * 002580ea 8b45 fc mov eax,dword ptr ss:[ebp-0x4] + * 002580ed 5f pop edi + * 002580ee 5e pop esi + * 002580ef 5b pop ebx + * 002580f0 85c0 test eax,eax + * 002580f2 74 09 je short .002580fd + * 002580f4 837d 10 00 cmp dword ptr ss:[ebp+0x10],0x0 + * 002580f8 74 03 je short .002580fd + * 002580fa c600 00 mov byte ptr ds:[eax],0x0 + * 002580fd 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + * 00258100 8b45 f8 mov eax,dword ptr ss:[ebp-0x8] + * 00258103 8911 mov dword ptr ds:[ecx],edx + * 00258105 8be5 mov esp,ebp + * 00258107 5d pop ebp + * 00258108 c3 retn + * 00258109 33c0 xor eax,eax + * 0025810b 8be5 mov esp,ebp + * 0025810d 5d pop ebp + * 0025810e c3 retn + * 0025810f cc int3 + * + * Stack: + * 0f14f87c 00279177 return to .00279177 from .00258020 + * 0f14f880 0f14f8b0 ; arg1 address of the text's pointer + * 0f14f884 0f14f8c0 ; arg2 pointed to zero, maybe a buffer + * 0f14f888 00000047 ; arg3 it is zero if no text, this value might be text size + 1 + * 0f14f88c ffffff80 ; constant, used as split + * 0f14f890 005768c8 .005768c8 + * 0f14f894 02924340 ; text is at 02924350 + * 0f14f898 00000001 ; this might also be a good split + * 0f14f89c 1b520020 + * 0f14f8a0 00000000 + * 0f14f8a4 00000000 + * 0f14f8a8 029245fc + * 0f14f8ac 0004bfd3 + * 0f14f8b0 0f14fae0 + * 0f14f8b4 00000000 + * 0f14f8b8 00000000 + * 0f14f8bc 02924340 + * 0f14f8c0 00000000 + * + * Registers: + * eax 0f14f8c0 ; floating at runtime + * ecx 0f14f8b0; floating at runtime + * edx 00000000 + * ebx 0f14fae0; floating at runtime + * esp 0f14f87c; floating at runtime + * ebp 0f14facc; floating at runtime + * esi 00000047 + * edi 02924340 ; text is in 02924350 + * eip 00258020 .00258020 + * + * Memory access pattern: + * For long sentences, it first render the first line, then the second line, and so on. + * So, the second line is a subtext of the entire dialog. + */ +static void SpecialHookExp(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + static DWORD lasttext; + // 00258020 55 push ebp ; jichi: hook here + // 00258021 8bec mov ebp,esp + // 00258023 8b45 08 mov eax,dword ptr ss:[ebp+0x8] ; jichi: move arg1 to eax + // 00258029 85c0 test eax,eax ; check if text is null + // 0025802b 0f84 d8000000 je .00258109 + // 00258031 837d 10 00 cmp dword ptr ss:[ebp+0x10],0x0 ; jichi: compare 0 with arg3, which is size+1 + // 00258035 0f84 ce000000 je .00258109 + // 0025803b 8b10 mov edx,dword ptr ds:[eax] ; move text address to edx + DWORD arg1 = argof(1, esp_base), // mov eax,dword ptr ss:[ebp+0x8] + arg3 = argof(3, esp_base); // size - 1 + if (arg1 && arg3) + if (DWORD text = *(DWORD *)arg1) + if (!(text > lasttext && text < lasttext + VNR_TEXT_CAPACITY)) { // text is not a subtext of lastText + *data = lasttext = text; // mov edx,dword ptr ds:[eax] + //*len = arg3 - 1; // the last char is the '\0', so -1, but this value is not reliable + *len = ::strlen((LPCSTR)text); + // Registers are not used as split as all of them are floating at runtime + //*split = argof(4, esp_base); // arg4, always -8, this will merge all threads and result in repetition + *split = argof(7, esp_base); // reduce repetition, but still have sub-text repeat + } +} +bool InsertExpHook() +{ + const BYTE bytes[] = { + 0x55, // 00258020 55 push ebp ; jichi: hook here, function starts, text in [arg1], size+1 in arg3 + 0x8b,0xec, // 00258021 8bec mov ebp,esp + 0x8b,0x45, 0x08, // 00258023 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + 0x83,0xec, 0x08, // 00258026 83ec 08 sub esp,0x8 + 0x85,0xc0, // 00258029 85c0 test eax,eax + 0x0f,0x84, XX4, // 0025802b 0f84 d8000000 je .00258109 + 0x83,0x7d, 0x10, 0x00, // 00258031 837d 10 00 cmp dword ptr ss:[ebp+0x10],0x0 + 0x0f,0x84, XX4, // 00258035 0f84 ce000000 je .00258109 + 0x8b,0x10, // 0025803b 8b10 mov edx,dword ptr ds:[eax] ; jichi: edx is the text + 0x8b,0x45, 0x0c, // 0025803d 8b45 0c mov eax,dword ptr ss:[ebp+0xc] + 0x53, // 00258040 53 push ebx + 0x56, // 00258041 56 push esi + 0xc7,0x45, 0xf8, 0x00,0x00,0x00,0x00, // 00258042 c745 f8 00000000 mov dword ptr ss:[ebp-0x8],0x0 + 0x89,0x45, 0xfc, // 00258049 8945 fc mov dword ptr ss:[ebp-0x4],eax + 0x57, // 0025804c 57 push edi + 0x8d,0x49, 0x00, // 0025804d 8d49 00 lea ecx,dword ptr ds:[ecx] + 0x8a,0x0a // 00258050 8a0a mov cl,byte ptr ds:[edx] ; jichi: text accessed in edx + }; + enum { addr_offset = 0 }; + ULONG range = min(module_limit_ - module_base_, MAX_REL_ADDR); + ULONG addr = MemDbg::matchBytes(bytes, sizeof(bytes), module_base_, module_base_ + range); + //GROWL_DWORD(addr); + if (!addr) { + ConsoleOutput("vnreng:EXP: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.type = NO_CONTEXT|USING_STRING; // NO_CONTEXT to get rid of floating address + hp.text_fun = SpecialHookExp; + ConsoleOutput("vnreng: INSERT EXP"); + NewHook(hp, "EXP"); // FIXME: text displayed line by line + + ConsoleOutput("vnreng:EXP: disable GDI hooks"); // There are no GDI functions hooked though + DisableGDIHooks(); + return true; +} + +/** 10/20/2014 jichi: HorkEye, http://horkeye.com + * Sample game: [150226] 結城友奈�勀��ある 体験版 + * + * No GDI functions are used by this game. + * + * Debug method: + * There are two matched texts. + * The one having fixed address is used to insert hw breakpoints. + * + * I found are two functions addressing the address, both of which seems to be good. + * The first one is used: + * + * 013cda60 8d4c24 1c lea ecx,dword ptr ss:[esp+0x1c] + * 013cda64 51 push ecx + * 013cda65 68 48a8c201 push .01c2a848 ; ascii "if" + * 013cda6a e8 d1291600 call .01530440 + * 013cda6f 83c4 0c add esp,0xc + * 013cda72 6a 01 push 0x1 + * 013cda74 83ec 1c sub esp,0x1c + * 013cda77 8bcc mov ecx,esp + * 013cda79 896424 30 mov dword ptr ss:[esp+0x30],esp + * 013cda7d 6a 10 push 0x10 + * 013cda7f c741 14 0f000000 mov dword ptr ds:[ecx+0x14],0xf + * 013cda86 c741 10 00000000 mov dword ptr ds:[ecx+0x10],0x0 + * 013cda8d 68 80125601 push .01561280 + * 013cda92 c601 00 mov byte ptr ds:[ecx],0x0 + * 013cda95 e8 5681ffff call .013c5bf0 + * 013cda9a e8 717a0900 call .01465510 + * 013cda9f 83c4 20 add esp,0x20 + * 013cdaa2 b8 01000000 mov eax,0x1 + * 013cdaa7 8b8c24 b8000000 mov ecx,dword ptr ss:[esp+0xb8] + * 013cdaae 5f pop edi + * 013cdaaf 5e pop esi + * 013cdab0 5d pop ebp + * 013cdab1 5b pop ebx + * 013cdab2 33cc xor ecx,esp + * 013cdab4 e8 c7361600 call .01531180 + * 013cdab9 81c4 ac000000 add esp,0xac + * 013cdabf c3 retn + * 013cdac0 83ec 40 sub esp,0x40 + * 013cdac3 a1 24805d01 mov eax,dword ptr ds:[0x15d8024] + * 013cdac8 8b15 c4709901 mov edx,dword ptr ds:[0x19970c4] + * 013cdace 8d0c00 lea ecx,dword ptr ds:[eax+eax] + * 013cdad1 a1 9c506b01 mov eax,dword ptr ds:[0x16b509c] + * 013cdad6 0305 18805d01 add eax,dword ptr ds:[0x15d8018] + * 013cdadc 53 push ebx + * 013cdadd 8b5c24 48 mov ebx,dword ptr ss:[esp+0x48] + * 013cdae1 55 push ebp + * 013cdae2 8b6c24 50 mov ebp,dword ptr ss:[esp+0x50] + * 013cdae6 894c24 34 mov dword ptr ss:[esp+0x34],ecx + * 013cdaea 8b0d 20805d01 mov ecx,dword ptr ds:[0x15d8020] + * 013cdaf0 894424 18 mov dword ptr ss:[esp+0x18],eax + * 013cdaf4 a1 1c805d01 mov eax,dword ptr ds:[0x15d801c] + * 013cdaf9 03c8 add ecx,eax + * 013cdafb 56 push esi + * 013cdafc 33f6 xor esi,esi + * 013cdafe d1f8 sar eax,1 + * 013cdb00 45 inc ebp + * 013cdb01 896c24 24 mov dword ptr ss:[esp+0x24],ebp + * 013cdb05 897424 0c mov dword ptr ss:[esp+0xc],esi + * 013cdb09 894c24 18 mov dword ptr ss:[esp+0x18],ecx + * 013cdb0d 8a0c1a mov cl,byte ptr ds:[edx+ebx] jichi: here + * 013cdb10 894424 30 mov dword ptr ss:[esp+0x30],eax + * 013cdb14 8a441a 01 mov al,byte ptr ds:[edx+ebx+0x1] + * 013cdb18 57 push edi + * 013cdb19 897424 14 mov dword ptr ss:[esp+0x14],esi + * 013cdb1d 3935 c8709901 cmp dword ptr ds:[0x19970c8],esi + * + * The hooked place is only accessed once. + * 013cdb0d 8a0c1a mov cl,byte ptr ds:[edx+ebx] jichi: here + * ebx is the text to be base address. + * edx is the offset to skip character name. + * + * 023B66A0 81 79 89 C4 EA A3 2C 53 30 30 35 5F 42 5F 30 30 【夏偾,S005_B_00 + * 023B66B0 30 32 81 7A 81 75 83 6F 81 5B 83 65 83 62 83 4E 02】「バーッ�ク + * 023B66C0 83 58 82 CD 82 B1 82 C1 82 BF 82 CC 93 73 8D 87 スはこっちの都� * 023B66D0 82 C8 82 C7 82 A8 8D 5C 82 A2 82 C8 82 B5 81 63 などお構いなし… + * + * There are garbage in character name. + * + * 1/15/2015 + * Alternative hook that might not need a text filter: + * http://www.hongfire.com/forum/showthread.php/36807-AGTH-text-extraction-tool-for-games-translation/page753 + * /HA-4@552B5:姉小路直子と銀色の死�exe + * If this hook no longer works, try that one instead. + */ +// Skip text between "," and "�, and remove [n] +// ex:【夏偾,S005_B_0002】「バーッ�ク +static bool HorkEyeFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + size_t len = *size; + char *str = reinterpret_cast(data), + *start, + *stop; + + // Remove text between , and ] + // FIXME: This does not work well because of the ascii encoding + if ((start = (char *)::memchr(str, ',', len)) && + (stop = cpp_strnstr(start, "\x81\x7a", len - (start - str))) && + (len -= stop - start)) // = u'�.encode('sjis') + ::memmove(start, stop, len - (start - str)); + + // Remove [n] + enum { skip_len = 3 }; // = length of "[n]" + while (len >= skip_len && + (start = cpp_strnstr(str, "[n]", len)) && + (len -= skip_len)) + ::memmove(start, start + skip_len, len - (start - str)); + + *size = len; + return true; +} +bool InsertHorkEyeHook() +{ + const BYTE bytes[] = { + 0x89,0x6c,0x24, 0x24, // 013cdb01 896c24 24 mov dword ptr ss:[esp+0x24],ebp + 0x89,0x74,0x24, 0x0c, // 013cdb05 897424 0c mov dword ptr ss:[esp+0xc],esi + 0x89,0x4c,0x24, 0x18, // 013cdb09 894c24 18 mov dword ptr ss:[esp+0x18],ecx + 0x8a,0x0c,0x1a // 013cdb0d 8a0c1a mov cl,byte ptr ds:[edx+ebx] jichi: here + }; + enum { addr_offset = sizeof(bytes) - 3 }; // 8a0c1a + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + if (!addr) { + ConsoleOutput("vnreng:HorkEye: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.offset = pusha_ebx_off -4; + hp.type = USING_STRING|NO_CONTEXT|FIXING_SPLIT; // floating address + hp.filter_fun = HorkEyeFilter; + ConsoleOutput("vnreng: INSERT HorkEye"); + NewHook(hp, "HorkEye"); + return true; +} + +/** jichi 12/2/2014 5pb + * + * Sample game: [140924] CROSS�CHANNEL 〜FINAL COMPLETE� * See: http://sakuradite.com/topic/528 + * + * Debugging method: insert breakpoint. + * The first matched function cannot extract prelude text. + * The second matched function can extract anything but contains garbage. + * + * Function for scenario: + * 0016d90e cc int3 + * 0016d90f cc int3 + * 0016d910 8b15 782b6e06 mov edx,dword ptr ds:[0x66e2b78] ; .00b43bfe + * 0016d916 8a0a mov cl,byte ptr ds:[edx] ; jichi: hook here + * 0016d918 33c0 xor eax,eax + * 0016d91a 84c9 test cl,cl + * 0016d91c 74 41 je short .0016d95f + * 0016d91e 8bff mov edi,edi + * 0016d920 80f9 25 cmp cl,0x25 + * 0016d923 75 11 jnz short .0016d936 + * 0016d925 8a4a 01 mov cl,byte ptr ds:[edx+0x1] + * 0016d928 42 inc edx + * 0016d929 80f9 4e cmp cl,0x4e + * 0016d92c 74 05 je short .0016d933 + * 0016d92e 80f9 6e cmp cl,0x6e + * 0016d931 75 26 jnz short .0016d959 + * 0016d933 42 inc edx + * 0016d934 eb 23 jmp short .0016d959 + * 0016d936 80f9 81 cmp cl,0x81 + * 0016d939 72 05 jb short .0016d940 + * 0016d93b 80f9 9f cmp cl,0x9f + * 0016d93e 76 0a jbe short .0016d94a + * 0016d940 80f9 e0 cmp cl,0xe0 + * 0016d943 72 0c jb short .0016d951 + * 0016d945 80f9 fc cmp cl,0xfc + * 0016d948 77 07 ja short .0016d951 + * 0016d94a b9 02000000 mov ecx,0x2 + * 0016d94f eb 05 jmp short .0016d956 + * 0016d951 b9 01000000 mov ecx,0x1 + * 0016d956 40 inc eax + * 0016d957 03d1 add edx,ecx + * 0016d959 8a0a mov cl,byte ptr ds:[edx] + * 0016d95b 84c9 test cl,cl + * 0016d95d ^75 c1 jnz short .0016d920 + * 0016d95f c3 retn + * + * Function for everything: + * 001e9a76 8bff mov edi,edi + * 001e9a78 55 push ebp + * 001e9a79 8bec mov ebp,esp + * 001e9a7b 51 push ecx + * 001e9a7c 8365 fc 00 and dword ptr ss:[ebp-0x4],0x0 + * 001e9a80 53 push ebx + * 001e9a81 8b5d 10 mov ebx,dword ptr ss:[ebp+0x10] + * 001e9a84 85db test ebx,ebx + * 001e9a86 75 07 jnz short .001e9a8f + * 001e9a88 33c0 xor eax,eax + * 001e9a8a e9 9a000000 jmp .001e9b29 + * 001e9a8f 56 push esi + * 001e9a90 83fb 04 cmp ebx,0x4 + * 001e9a93 72 75 jb short .001e9b0a + * 001e9a95 8d73 fc lea esi,dword ptr ds:[ebx-0x4] + * 001e9a98 85f6 test esi,esi + * 001e9a9a 74 6e je short .001e9b0a + * 001e9a9c 8b4d 0c mov ecx,dword ptr ss:[ebp+0xc] + * 001e9a9f 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 001e9aa2 8a10 mov dl,byte ptr ds:[eax] + * 001e9aa4 83c0 04 add eax,0x4 + * 001e9aa7 83c1 04 add ecx,0x4 + * 001e9aaa 84d2 test dl,dl + * 001e9aac 74 52 je short .001e9b00 + * 001e9aae 3a51 fc cmp dl,byte ptr ds:[ecx-0x4] + * 001e9ab1 75 4d jnz short .001e9b00 + * 001e9ab3 8a50 fd mov dl,byte ptr ds:[eax-0x3] + * 001e9ab6 84d2 test dl,dl + * 001e9ab8 74 3c je short .001e9af6 + * 001e9aba 3a51 fd cmp dl,byte ptr ds:[ecx-0x3] + * 001e9abd 75 37 jnz short .001e9af6 + * 001e9abf 8a50 fe mov dl,byte ptr ds:[eax-0x2] + * 001e9ac2 84d2 test dl,dl + * 001e9ac4 74 26 je short .001e9aec + * 001e9ac6 3a51 fe cmp dl,byte ptr ds:[ecx-0x2] + * 001e9ac9 75 21 jnz short .001e9aec + * 001e9acb 8a50 ff mov dl,byte ptr ds:[eax-0x1] + * 001e9ace 84d2 test dl,dl + * 001e9ad0 74 10 je short .001e9ae2 + * 001e9ad2 3a51 ff cmp dl,byte ptr ds:[ecx-0x1] + * 001e9ad5 75 0b jnz short .001e9ae2 + * 001e9ad7 8345 fc 04 add dword ptr ss:[ebp-0x4],0x4 + * 001e9adb 3975 fc cmp dword ptr ss:[ebp-0x4],esi + * 001e9ade ^72 c2 jb short .001e9aa2 + * 001e9ae0 eb 2e jmp short .001e9b10 + * 001e9ae2 0fb640 ff movzx eax,byte ptr ds:[eax-0x1] + * 001e9ae6 0fb649 ff movzx ecx,byte ptr ds:[ecx-0x1] + * 001e9aea eb 46 jmp short .001e9b32 + * 001e9aec 0fb640 fe movzx eax,byte ptr ds:[eax-0x2] + * 001e9af0 0fb649 fe movzx ecx,byte ptr ds:[ecx-0x2] + * 001e9af4 eb 3c jmp short .001e9b32 + * 001e9af6 0fb640 fd movzx eax,byte ptr ds:[eax-0x3] + * 001e9afa 0fb649 fd movzx ecx,byte ptr ds:[ecx-0x3] + * 001e9afe eb 32 jmp short .001e9b32 + * 001e9b00 0fb640 fc movzx eax,byte ptr ds:[eax-0x4] + * 001e9b04 0fb649 fc movzx ecx,byte ptr ds:[ecx-0x4] + * 001e9b08 eb 28 jmp short .001e9b32 + * 001e9b0a 8b4d 0c mov ecx,dword ptr ss:[ebp+0xc] + * 001e9b0d 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 001e9b10 8b75 fc mov esi,dword ptr ss:[ebp-0x4] + * 001e9b13 eb 0d jmp short .001e9b22 + * 001e9b15 8a10 mov dl,byte ptr ds:[eax] ; jichi: here, word by word + * 001e9b17 84d2 test dl,dl + * 001e9b19 74 11 je short .001e9b2c + * 001e9b1b 3a11 cmp dl,byte ptr ds:[ecx] + * 001e9b1d 75 0d jnz short .001e9b2c + * 001e9b1f 40 inc eax + * 001e9b20 46 inc esi + * 001e9b21 41 inc ecx + * 001e9b22 3bf3 cmp esi,ebx + * 001e9b24 ^72 ef jb short .001e9b15 + * 001e9b26 33c0 xor eax,eax + * 001e9b28 5e pop esi + * 001e9b29 5b pop ebx + * 001e9b2a c9 leave + * 001e9b2b c3 retn + */ +namespace { // unnamed + +// Characters to ignore: [%0-9A-Z] +bool Insert5pbHook1() +{ + const BYTE bytes[] = { + 0xcc, // 0016d90e cc int3 + 0xcc, // 0016d90f cc int3 + 0x8b,0x15, XX4, // 0016d910 8b15 782b6e06 mov edx,dword ptr ds:[0x66e2b78] ; .00b43bfe + 0x8a,0x0a, // 0016d916 8a0a mov cl,byte ptr ds:[edx] ; jichi: hook here + 0x33,0xc0, // 0016d918 33c0 xor eax,eax + 0x84,0xc9 // 0016d91a 84c9 test cl,cl + }; + enum { addr_offset = 0x0016d916 - 0x0016d90e }; + + ULONG addr = MemDbg::matchBytes(bytes, sizeof(bytes), module_base_, module_limit_); + //GROWL_DWORD3(addr+addr_offset, module_base_,module_limit_); + if (!addr) { + ConsoleOutput("vnreng:5pb1: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.offset = pusha_edx_off - 4; + hp.type = USING_STRING; + ConsoleOutput("vnreng: INSERT 5pb1"); + NewHook(hp, "5pb1"); + + // GDI functions are not used by 5pb games anyway. + //ConsoleOutput("vnreng:5pb: disable GDI hooks"); + //DisableGDIHooks(); + return true; +} + +// Characters to ignore: [%@A-z] +inline bool _5pb2garbage_ch(char c) +{ return c == '%' || c == '@' || c >= 'A' && c <= 'z'; } + +// 001e9b15 8a10 mov dl,byte ptr ds:[eax] ; jichi: here, word by word +void SpecialHook5pb2(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + static DWORD lasttext; + DWORD text = regof(eax, esp_base); + if (lasttext == text) + return; + BYTE c = *(BYTE *)text; + if (!c) + return; + BYTE size = ::LeadByteTable[c]; // 1, 2, or 3 + if (size == 1 && _5pb2garbage_ch(*(LPCSTR)text)) + return; + lasttext = text; + *data = text; + *len = size; +} + +bool Insert5pbHook2() +{ + const BYTE bytes[] = { + 0x8a,0x10, // 001e9b15 8a10 mov dl,byte ptr ds:[eax] ; jichi: here, word by word + 0x84,0xd2, // 001e9b17 84d2 test dl,dl + 0x74,0x11 // 001e9b19 74 11 je short .001e9b2c + }; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + //GROWL_DWORD3(addr, module_base_,module_limit_); + if (!addr) { + ConsoleOutput("vnreng:5pb2: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.type = USING_STRING; + hp.text_fun = SpecialHook5pb2; + //hp.offset = pusha_eax_off - 4; + //hp.length_offset = 1; + ConsoleOutput("vnreng: INSERT 5pb2"); + NewHook(hp, "5pb2"); + + // GDI functions are not used by 5pb games anyway. + //ConsoleOutput("vnreng:5pb: disable GDI hooks"); + //DisableGDIHooks(); + return true; +} + +/** jichi 2/2/2015: New 5pb hook + * Sample game: Hyperdimension.Neptunia.ReBirth1 + * + * Debugging method: hardware breakpoint and find function in msvc110 + * Then, backtrack the function stack to find proper function. + * + * Hooked function: 558BEC56FF750C8BF1FF75088D460850 + * + * 0025A12E CC INT3 + * 0025A12F CC INT3 + * 0025A130 55 PUSH EBP + * 0025A131 8BEC MOV EBP,ESP + * 0025A133 56 PUSH ESI + * 0025A134 FF75 0C PUSH DWORD PTR SS:[EBP+0xC] + * 0025A137 8BF1 MOV ESI,ECX + * 0025A139 FF75 08 PUSH DWORD PTR SS:[EBP+0x8] + * 0025A13C 8D46 08 LEA EAX,DWORD PTR DS:[ESI+0x8] + * 0025A13F 50 PUSH EAX + * 0025A140 E8 DB100100 CALL .0026B220 + * 0025A145 8B8E 988D0000 MOV ECX,DWORD PTR DS:[ESI+0x8D98] + * 0025A14B 8988 80020000 MOV DWORD PTR DS:[EAX+0x280],ECX + * 0025A151 8B8E A08D0000 MOV ECX,DWORD PTR DS:[ESI+0x8DA0] + * 0025A157 8988 88020000 MOV DWORD PTR DS:[EAX+0x288],ECX + * 0025A15D 8B8E A88D0000 MOV ECX,DWORD PTR DS:[ESI+0x8DA8] + * 0025A163 8988 90020000 MOV DWORD PTR DS:[EAX+0x290],ECX + * 0025A169 8B8E B08D0000 MOV ECX,DWORD PTR DS:[ESI+0x8DB0] + * 0025A16F 8988 98020000 MOV DWORD PTR DS:[EAX+0x298],ECX + * 0025A175 83C4 0C ADD ESP,0xC + * 0025A178 8D8E 188B0000 LEA ECX,DWORD PTR DS:[ESI+0x8B18] + * 0025A17E E8 DDD8FEFF CALL .00247A60 + * 0025A183 5E POP ESI + * 0025A184 5D POP EBP + * 0025A185 C2 0800 RETN 0x8 + * 0025A188 CC INT3 + * 0025A189 CC INT3 + * + * Runtime stack, text in arg1, and name in arg2: + * + * 0015F93C 00252330 RETURN to .00252330 from .0025A130 + * 0015F940 181D0D4C ASCII "That's my line! I won't let any of you + * take the title of True Goddess!" + * 0015F944 0B8B4D20 ASCII " White Heart " + * 0015F948 0B8B5528 + * 0015F94C 0B8B5524 + * 0015F950 /0015F980 + * 0015F954 |0026000F RETURN to .0026000F from .002521D0 + * + * + * Another candidate funciton for backup usage. + * Previous text in arg1. + * Current text in arg2. + * Current name in arg3. + * + * 0026B21C CC INT3 + * 0026B21D CC INT3 + * 0026B21E CC INT3 + * 0026B21F CC INT3 + * 0026B220 55 PUSH EBP + * 0026B221 8BEC MOV EBP,ESP + * 0026B223 81EC A0020000 SUB ESP,0x2A0 + * 0026B229 BA A0020000 MOV EDX,0x2A0 + * 0026B22E 53 PUSH EBX + * 0026B22F 8B5D 08 MOV EBX,DWORD PTR SS:[EBP+0x8] + * 0026B232 56 PUSH ESI + * 0026B233 57 PUSH EDI + * 0026B234 8D041A LEA EAX,DWORD PTR DS:[EDX+EBX] + * 0026B237 B9 A8000000 MOV ECX,0xA8 + * 0026B23C 8BF3 MOV ESI,EBX + * 0026B23E 8DBD 60FDFFFF LEA EDI,DWORD PTR SS:[EBP-0x2A0] + * 0026B244 F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS> + * 0026B246 B9 A8000000 MOV ECX,0xA8 + * 0026B24B 8BF0 MOV ESI,EAX + * 0026B24D 8BFB MOV EDI,EBX + * 0026B24F F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS> + * 0026B251 81C2 A0020000 ADD EDX,0x2A0 + * 0026B257 B9 A8000000 MOV ECX,0xA8 + * 0026B25C 8DB5 60FDFFFF LEA ESI,DWORD PTR SS:[EBP-0x2A0] + * 0026B262 8BF8 MOV EDI,EAX + * 0026B264 F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS> + * 0026B266 81FA 40830000 CMP EDX,0x8340 + * 0026B26C ^7C C6 JL SHORT .0026B234 + * 0026B26E 8BCB MOV ECX,EBX + * 0026B270 E8 EBC7FDFF CALL .00247A60 + * 0026B275 FF75 0C PUSH DWORD PTR SS:[EBP+0xC] + * 0026B278 8B35 D8525000 MOV ESI,DWORD PTR DS:[0x5052D8] ; msvcr110.sprintf + * 0026B27E 68 805C5000 PUSH .00505C80 ; ASCII "%s" + * 0026B283 53 PUSH EBX + * 0026B284 FFD6 CALL ESI + * 0026B286 FF75 10 PUSH DWORD PTR SS:[EBP+0x10] + * 0026B289 8D83 00020000 LEA EAX,DWORD PTR DS:[EBX+0x200] + * 0026B28F 68 805C5000 PUSH .00505C80 ; ASCII "%s" + * 0026B294 50 PUSH EAX + * 0026B295 FFD6 CALL ESI + * 0026B297 83C4 18 ADD ESP,0x18 + * 0026B29A 8BC3 MOV EAX,EBX + * 0026B29C 5F POP EDI + * 0026B29D 5E POP ESI + * 0026B29E 5B POP EBX + * 0026B29F 8BE5 MOV ESP,EBP + * 0026B2A1 5D POP EBP + * 0026B2A2 C3 RETN + * 0026B2A3 CC INT3 + * 0026B2A4 CC INT3 + * 0026B2A5 CC INT3 + * 0026B2A6 CC INT3 + */ +void SpecialHook5pb3(DWORD esp_base, HookParam *, BYTE index, DWORD *data, DWORD *split, DWORD *len) +{ + // Text in arg1, name in arg2 + if (LPCSTR text = (LPCSTR)argof(index+1, esp_base)) + if (*text) { + if (index) // trim spaces in character name + while (*text == ' ') text++; + size_t sz = ::strlen(text); + if (index) + while (sz && text[sz-1] == ' ') sz--; + *data = (DWORD)text; + *len = sz; + *split = FIXED_SPLIT_VALUE << index; + } +} +bool Insert5pbHook3() +{ + const BYTE bytes[] = { // function starts + 0x55, // 0025A130 55 PUSH EBP + 0x8b,0xec, // 0025A131 8BEC MOV EBP,ESP + 0x56, // 0025A133 56 PUSH ESI + 0xff,0x75, 0x0c, // 0025A134 FF75 0C PUSH DWORD PTR SS:[EBP+0xC] + 0x8b,0xf1, // 0025A137 8BF1 MOV ESI,ECX + 0xff,0x75, 0x08, // 0025A139 FF75 08 PUSH DWORD PTR SS:[EBP+0x8] + 0x8d,0x46, 0x08, // 0025A13C 8D46 08 LEA EAX,DWORD PTR DS:[ESI+0x8] + 0x50, // 0025A13F 50 PUSH EAX + 0xe8 // 0025A140 E8 DB100100 CALL .0026B220 + }; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + //GROWL_DWORD3(addr, module_base_,module_limit_); + if (!addr) { + ConsoleOutput("vnreng:5pb2: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.type = USING_STRING|NO_CONTEXT; + hp.text_fun = SpecialHook5pb3; + hp.extra_text_count = 1; // extract character name in arg1 + hp.filter_fun = NewLineCharToSpaceFilter; // replace '\n' by ' ' + ConsoleOutput("vnreng: INSERT 5pb3"); + NewHook(hp, "5pb3"); + // GDI functions are not used by 5pb games anyway. + //ConsoleOutput("vnreng:5pb: disable GDI hooks"); + //DisableGDIHooks(); + return true; +} + +} // unnamed namespace + +bool Insert5pbHook() +{ + bool ok = Insert5pbHook1(); + ok = Insert5pbHook2() || ok; + ok = Insert5pbHook3() || ok; + return ok; +} + +/** 12/23/2014 jichi: Mink games (not sure the engine name) + * Sample game: + * - [130111] [Mink EGO] お�ちも�にはぜったい言えなぁ�ぁ�つなこと�-- /HB-4*0:64@45164A + * - [141219] [Mink] しすた�・すきーむ3 + * + * Observations from sisters3: + * - GetGlyphOutlineA can get text, but it is cached. + * - It's caller's first argument is the correct text, but I failed to find where it is called + * - Debugging text in memory caused looping + * + * /HB-4*0:64@45164A + * - addr: 0x45164a + * - length_offset: 1 + * - split: 0x64 + * - off: 0xfffffff8 = -8 + * - type: 0x18 + * + * Observations from Onechan: + * - There are lots of threads + * - The one with -1 split value is correct, but not sure for all games + * - The result texts still contain garbage, but can be split using return values. + * + * 00451611 e9 ee000000 jmp .00451704 + * 00451616 8b45 0c mov eax,dword ptr ss:[ebp+0xc] + * 00451619 3bc3 cmp eax,ebx + * 0045161b 75 2b jnz short .00451648 + * 0045161d e8 a9340000 call .00454acb + * 00451622 53 push ebx + * 00451623 53 push ebx + * 00451624 53 push ebx + * 00451625 53 push ebx + * 00451626 53 push ebx + * 00451627 c700 16000000 mov dword ptr ds:[eax],0x16 + * 0045162d e8 16340000 call .00454a48 + * 00451632 83c4 14 add esp,0x14 + * 00451635 385d f4 cmp byte ptr ss:[ebp-0xc],bl + * 00451638 74 07 je short .00451641 + * 0045163a 8b45 f0 mov eax,dword ptr ss:[ebp-0x10] + * 0045163d 8360 70 fd and dword ptr ds:[eax+0x70],0xfffffffd + * 00451641 33c0 xor eax,eax + * 00451643 e9 bc000000 jmp .00451704 + * 00451648 3818 cmp byte ptr ds:[eax],bl + * 0045164a 75 14 jnz short .00451660 ; jichi: hook here + * 0045164c 385d f4 cmp byte ptr ss:[ebp-0xc],bl + * 0045164f 74 07 je short .00451658 + * 00451651 8b45 f0 mov eax,dword ptr ss:[ebp-0x10] + * 00451654 8360 70 fd and dword ptr ds:[eax+0x70],0xfffffffd + * 00451658 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + * 0045165b e9 a4000000 jmp .00451704 + * 00451660 56 push esi + * 00451661 8b75 08 mov esi,dword ptr ss:[ebp+0x8] + * 00451664 3bf3 cmp esi,ebx + * 00451666 75 28 jnz short .00451690 + * 00451668 e8 5e340000 call .00454acb + * 0045166d 53 push ebx + * 0045166e 53 push ebx + * 0045166f 53 push ebx + * 00451670 53 push ebx + * 00451671 53 push ebx + * 00451672 c700 16000000 mov dword ptr ds:[eax],0x16 + * 00451678 e8 cb330000 call .00454a48 + * 0045167d 83c4 14 add esp,0x14 + * 00451680 385d f4 cmp byte ptr ss:[ebp-0xc],bl + * 00451683 74 07 je short .0045168c + * 00451685 8b45 f0 mov eax,dword ptr ss:[ebp-0x10] + * 00451688 8360 70 fd and dword ptr ds:[eax+0x70],0xfffffffd + * 0045168c 33c0 xor eax,eax + * 0045168e eb 73 jmp short .00451703 + * 00451690 57 push edi + * 00451691 50 push eax + * 00451692 8bfe mov edi,esi + * 00451694 e8 a7600000 call .00457740 + * 00451699 8975 f8 mov dword ptr ss:[ebp-0x8],esi + * 0045169c 2945 f8 sub dword ptr ss:[ebp-0x8],eax + * 0045169f 56 push esi + * 004516a0 e8 9b600000 call .00457740 + * 004516a5 0345 f8 add eax,dword ptr ss:[ebp-0x8] + * 004516a8 59 pop ecx + * 004516a9 59 pop ecx + * 004516aa 381e cmp byte ptr ds:[esi],bl + * 004516ac 74 46 je short .004516f4 + * 004516ae 2b75 0c sub esi,dword ptr ss:[ebp+0xc] + * 004516b1 3bf8 cmp edi,eax + * 004516b3 77 3f ja short .004516f4 + * 004516b5 8a17 mov dl,byte ptr ds:[edi] + * 004516b7 8b4d 0c mov ecx,dword ptr ss:[ebp+0xc] + * 004516ba 8855 ff mov byte ptr ss:[ebp-0x1],dl + * 004516bd 3ad3 cmp dl,bl + * 004516bf 74 11 je short .004516d2 + * 004516c1 8a11 mov dl,byte ptr ds:[ecx] + * 004516c3 3ad3 cmp dl,bl + * 004516c5 74 40 je short .00451707 + * 004516c7 38140e cmp byte ptr ds:[esi+ecx],dl + * 004516ca 75 06 jnz short .004516d2 + * 004516cc 41 inc ecx + * 004516cd 381c0e cmp byte ptr ds:[esi+ecx],bl + * 004516d0 ^75 ef jnz short .004516c1 + * 004516d2 3819 cmp byte ptr ds:[ecx],bl + * 004516d4 74 31 je short .00451707 + * 004516d6 0fb64d ff movzx ecx,byte ptr ss:[ebp-0x1] + * 004516da 8b55 ec mov edx,dword ptr ss:[ebp-0x14] + * 004516dd 8a4c11 1d mov cl,byte ptr ds:[ecx+edx+0x1d] + */ + +#if 0 // hook to the caller of dynamic GetGlyphOutlineA +/** + * @param addr function address + * @param frame real address of the function, supposed to be the same as addr + * @param stack address of current stack - 4 + * @return If suceess + */ +static bool InsertMinkDynamicHook(LPVOID fun, DWORD frame, DWORD stack) +{ + CC_UNUSED(frame); + if (fun != ::GetGlyphOutlineA) + return false; + DWORD addr = *(DWORD *)(stack + 4); + if (!addr) { + ConsoleOutput("vnreng:Mink: missing function return addr, this should never happen"); + return true; + } + addr = MemDbg::findEnclosingAlignedFunction(addr, 0x200); // range is around 0x120 + if (!addr) { + ConsoleOutput("vnreng:Mink: failed to caller address"); + return true; + } + + HookParam hp = {}; + hp.address = addr; // hook to the beginning of the caller function + hp.offset = 4 * 1; // text character is in arg1 + hp.length_offset = 1; // only 1 character + hp.type = BIG_ENDIAN; + NewHook(hp, "Mink"); + + ConsoleOutput("vnreng:Mink: disable GDI hooks"); + DisableGDIHooks(); + return true; +} +#endif // 0 + +static void SpecialHookMink(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + //DWORD addr = *(DWORD *)(esp_base + hp->offset); // default value + DWORD addr = regof(eax, esp_base); + if (!IthGetMemoryRange((LPVOID)(addr), 0, 0)) + return; + DWORD ch = *(DWORD *)addr; + DWORD size = LeadByteTable[ch & 0xff]; // Slightly faster than IsDBCSLeadByte + if (size == 1 && ::ispunct(ch & 0xff)) // skip ascii punctuations, since garbage is like ":text:" + return; + + *len = size; + *data = ch; + + // Issue: still have lots of garbage + *split = *(DWORD *)(esp_base + 0x64); + //*split = *(DWORD *)(esp_base + 0x48); +} + +bool InsertMinkHook() +{ + const BYTE bytes[] = { + 0x38,0x18, // 00451648 3818 cmp byte ptr ds:[eax],bl + 0x75, 0x14, // 0045164a 75 14 jnz short .00451660 ; jichi: hook here + 0x38,0x5d, 0xf4, // 0045164c 385d f4 cmp byte ptr ss:[ebp-0xc],bl + 0x74, 0x07, // 0045164f 74 07 je short .00451658 + 0x8b,0x45, 0xf0, // 00451651 8b45 f0 mov eax,dword ptr ss:[ebp-0x10] + 0x83,0x60, 0x70, 0xfd, // 00451654 8360 70 fd and dword ptr ds:[eax+0x70],0xfffffffd + 0x8b,0x45, 0x08 // 00451658 8b45 08 mov eax,dword ptr ss:[ebp+0x8] + }; + enum { addr_offset = 2 }; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + //ULONG addr = 0x45164a; + //ULONG addr = 0x451648; + //ULONG addr = 0x4521a8; + //GROWL_DWORD(addr); + if (!addr) { + ConsoleOutput("vnreng:Mink: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.offset = pusha_eax_off - 4; // -8 + hp.length_offset = 1; + hp.split = 0x64; + hp.type = USING_SPLIT|DATA_INDIRECT; // 0x18 + hp.text_fun = SpecialHookMink; + ConsoleOutput("vnreng: INSERT Mink"); + NewHook(hp, "Mink"); + + //ConsoleOutput("vnreng:Mink: disable GDI hooks"); + //DisableGDIHooks(); + return true; +} + +/** jichi 12/25/2014: Leaf/AQUAPLUS + * Sample game: [141224] [AQUAPLUS] WHITE ALBUM2 ミニアフタースト�リー + * Debug method: hardware break found text + * The text address is fixed. + * There are three matched functions. + * It can find both character name and scenario. + * + * The scenario text contains "\n" or "\k". + * + * 0045145C CC INT3 + * 0045145D CC INT3 + * 0045145E CC INT3 + * 0045145F CC INT3 + * 00451460 D9EE FLDZ + * 00451462 56 PUSH ESI + * 00451463 8B7424 08 MOV ESI,DWORD PTR SS:[ESP+0x8] + * 00451467 D95E 0C FSTP DWORD PTR DS:[ESI+0xC] + * 0045146A 57 PUSH EDI + * 0045146B 8BF9 MOV EDI,ECX + * 0045146D 8B97 B0A00000 MOV EDX,DWORD PTR DS:[EDI+0xA0B0] + * 00451473 33C0 XOR EAX,EAX + * 00451475 3BD0 CMP EDX,EAX + * 00451477 C706 05000000 MOV DWORD PTR DS:[ESI],0x5 + * 0045147D C746 04 03000000 MOV DWORD PTR DS:[ESI+0x4],0x3 + * 00451484 8946 10 MOV DWORD PTR DS:[ESI+0x10],EAX + * 00451487 8946 08 MOV DWORD PTR DS:[ESI+0x8],EAX + * 0045148A 7F 0D JG SHORT .00451499 + * 0045148C 8987 B0A00000 MOV DWORD PTR DS:[EDI+0xA0B0],EAX + * 00451492 5F POP EDI + * 00451493 8BC6 MOV EAX,ESI + * 00451495 5E POP ESI + * 00451496 C2 0400 RETN 0x4 + * 00451499 8D0492 LEA EAX,DWORD PTR DS:[EDX+EDX*4] + * 0045149C 53 PUSH EBX + * 0045149D 8B9C87 B08C0000 MOV EBX,DWORD PTR DS:[EDI+EAX*4+0x8CB0] + * 004514A4 8D0487 LEA EAX,DWORD PTR DS:[EDI+EAX*4] + * 004514A7 55 PUSH EBP + * 004514A8 8D6B FF LEA EBP,DWORD PTR DS:[EBX-0x1] + * 004514AB B9 04000000 MOV ECX,0x4 + * 004514B0 3BE9 CMP EBP,ECX + * 004514B2 0F87 10020000 JA .004516C8 + * 004514B8 FF24AD E8164500 JMP DWORD PTR DS:[EBP*4+0x4516E8] + * 004514BF 8B80 C08C0000 MOV EAX,DWORD PTR DS:[EAX+0x8CC0] + * 004514C5 8D0480 LEA EAX,DWORD PTR DS:[EAX+EAX*4] + * 004514C8 03C0 ADD EAX,EAX + * 004514CA 0FBE9400 6416BC0>MOVSX EDX,BYTE PTR DS:[EAX+EAX+0xBC1664] + * 004514D2 03C0 ADD EAX,EAX + * 004514D4 8D5A FF LEA EBX,DWORD PTR DS:[EDX-0x1] + * 004514D7 3BD9 CMP EBX,ECX + * 004514D9 0F87 B9000000 JA .00451598 + * 004514DF FF249D FC164500 JMP DWORD PTR DS:[EBX*4+0x4516FC] + * 004514E6 0FB688 6516BC00 MOVZX ECX,BYTE PTR DS:[EAX+0xBC1665] + * 004514ED FF8F B0A00000 DEC DWORD PTR DS:[EDI+0xA0B0] + * 004514F3 5D POP EBP + * 004514F4 5B POP EBX + * 004514F5 5F POP EDI + * 004514F6 894E 10 MOV DWORD PTR DS:[ESI+0x10],ECX + * 004514F9 8BC6 MOV EAX,ESI + * 004514FB 5E POP ESI + * 004514FC C2 0400 RETN 0x4 + * 004514FF 0FBF90 6616BC00 MOVSX EDX,WORD PTR DS:[EAX+0xBC1666] + * 00451506 FF8F B0A00000 DEC DWORD PTR DS:[EDI+0xA0B0] + * 0045150C 5D POP EBP + * 0045150D 5B POP EBX + * 0045150E 5F POP EDI + * 0045150F 8956 10 MOV DWORD PTR DS:[ESI+0x10],EDX + * 00451512 8BC6 MOV EAX,ESI + * 00451514 5E POP ESI + * 00451515 C2 0400 RETN 0x4 + * 00451518 8B80 6816BC00 MOV EAX,DWORD PTR DS:[EAX+0xBC1668] + * 0045151E FF8F B0A00000 DEC DWORD PTR DS:[EDI+0xA0B0] + * 00451524 5D POP EBP + * 00451525 5B POP EBX + * 00451526 8946 10 MOV DWORD PTR DS:[ESI+0x10],EAX + * 00451529 5F POP EDI + * 0045152A 8BC6 MOV EAX,ESI + * 0045152C 5E POP ESI + * 0045152D C2 0400 RETN 0x4 + * 00451530 D980 6C16BC00 FLD DWORD PTR DS:[EAX+0xBC166C] + * 00451536 FF8F B0A00000 DEC DWORD PTR DS:[EDI+0xA0B0] + * 0045153C 5D POP EBP + * 0045153D D95E 0C FSTP DWORD PTR DS:[ESI+0xC] + * 00451540 5B POP EBX + * 00451541 5F POP EDI + * 00451542 894E 04 MOV DWORD PTR DS:[ESI+0x4],ECX + * 00451545 8BC6 MOV EAX,ESI + * 00451547 5E POP ESI + * 00451548 C2 0400 RETN 0x4 + * 0045154B 8B80 7016BC00 MOV EAX,DWORD PTR DS:[EAX+0xBC1670] + * 00451551 8D58 01 LEA EBX,DWORD PTR DS:[EAX+0x1] + * 00451554 8A10 MOV DL,BYTE PTR DS:[EAX] + * 00451556 40 INC EAX + * 00451557 84D2 TEST DL,DL + * 00451559 ^75 F9 JNZ SHORT .00451554 + * 0045155B 2BC3 SUB EAX,EBX + * 0045155D 8D58 01 LEA EBX,DWORD PTR DS:[EAX+0x1] + * 00451560 53 PUSH EBX + * 00451561 6A 00 PUSH 0x0 + * 00451563 53 PUSH EBX + * 00451564 6A 00 PUSH 0x0 + * 00451566 FF15 74104A00 CALL DWORD PTR DS:[0x4A1074] ; kernel32.GetProcessHeap + * 0045156C 50 PUSH EAX + * 0045156D FF15 B4104A00 CALL DWORD PTR DS:[0x4A10B4] ; ntdll.RtlAllocateHeap + * 00451573 50 PUSH EAX + * 00451574 E8 373F0200 CALL .004754B0 + * 00451579 8B8F B0A00000 MOV ECX,DWORD PTR DS:[EDI+0xA0B0] + * 0045157F 8D0C89 LEA ECX,DWORD PTR DS:[ECX+ECX*4] + * 00451582 8B8C8F C08C0000 MOV ECX,DWORD PTR DS:[EDI+ECX*4+0x8CC0] + * 00451589 8D1489 LEA EDX,DWORD PTR DS:[ECX+ECX*4] + * 0045158C 8B0C95 7016BC00 MOV ECX,DWORD PTR DS:[EDX*4+0xBC1670] + * 00451593 E9 0C010000 JMP .004516A4 + * 00451598 52 PUSH EDX + * 00451599 68 A8644A00 PUSH .004A64A8 + * 0045159E E9 2B010000 JMP .004516CE + * 004515A3 8D9492 2D230000 LEA EDX,DWORD PTR DS:[EDX+EDX*4+0x232D] + * 004515AA 8B1C97 MOV EBX,DWORD PTR DS:[EDI+EDX*4] + * 004515AD 85DB TEST EBX,EBX + * 004515AF 0F8C 23010000 JL .004516D8 + * 004515B5 8B80 C08C0000 MOV EAX,DWORD PTR DS:[EAX+0x8CC0] + * 004515BB 99 CDQ + * 004515BC BD 1A000000 MOV EBP,0x1A + * 004515C1 F7FD IDIV EBP + * 004515C3 C1E2 04 SHL EDX,0x4 + * 004515C6 03D3 ADD EDX,EBX + * 004515C8 85C0 TEST EAX,EAX + * 004515CA 74 1C JE SHORT .004515E8 + * 004515CC D98497 34A70000 FLD DWORD PTR DS:[EDI+EDX*4+0xA734] + * 004515D3 FF8F B0A00000 DEC DWORD PTR DS:[EDI+0xA0B0] + * 004515D9 5D POP EBP + * 004515DA D95E 0C FSTP DWORD PTR DS:[ESI+0xC] + * 004515DD 5B POP EBX + * 004515DE 5F POP EDI + * 004515DF 894E 04 MOV DWORD PTR DS:[ESI+0x4],ECX + * 004515E2 8BC6 MOV EAX,ESI + * 004515E4 5E POP ESI + * 004515E5 C2 0400 RETN 0x4 + * 004515E8 8B8497 B4A00000 MOV EAX,DWORD PTR DS:[EDI+EDX*4+0xA0B4] + * 004515EF FF8F B0A00000 DEC DWORD PTR DS:[EDI+0xA0B0] + * 004515F5 5D POP EBP + * 004515F6 5B POP EBX + * 004515F7 8946 10 MOV DWORD PTR DS:[ESI+0x10],EAX + * 004515FA 5F POP EDI + * 004515FB 8BC6 MOV EAX,ESI + * 004515FD 5E POP ESI + * 004515FE C2 0400 RETN 0x4 + * 00451601 8B88 C08C0000 MOV ECX,DWORD PTR DS:[EAX+0x8CC0] + * 00451607 D980 BC8C0000 FLD DWORD PTR DS:[EAX+0x8CBC] + * 0045160D 894E 10 MOV DWORD PTR DS:[ESI+0x10],ECX + * 00451610 D95E 0C FSTP DWORD PTR DS:[ESI+0xC] + * 00451613 8B88 B88C0000 MOV ECX,DWORD PTR DS:[EAX+0x8CB8] + * 00451619 894E 08 MOV DWORD PTR DS:[ESI+0x8],ECX + * 0045161C 8D9492 2D230000 LEA EDX,DWORD PTR DS:[EDX+EDX*4+0x232D] + * 00451623 8B0C97 MOV ECX,DWORD PTR DS:[EDI+EDX*4] + * 00451626 894E 04 MOV DWORD PTR DS:[ESI+0x4],ECX + * 00451629 33C9 XOR ECX,ECX + * 0045162B 8988 B08C0000 MOV DWORD PTR DS:[EAX+0x8CB0],ECX + * 00451631 8988 B48C0000 MOV DWORD PTR DS:[EAX+0x8CB4],ECX + * 00451637 8988 B88C0000 MOV DWORD PTR DS:[EAX+0x8CB8],ECX + * 0045163D 5D POP EBP + * 0045163E 8988 BC8C0000 MOV DWORD PTR DS:[EAX+0x8CBC],ECX + * 00451644 8988 C08C0000 MOV DWORD PTR DS:[EAX+0x8CC0],ECX + * 0045164A FF8F B0A00000 DEC DWORD PTR DS:[EDI+0xA0B0] + * 00451650 5B POP EBX + * 00451651 5F POP EDI + * 00451652 8BC6 MOV EAX,ESI + * 00451654 5E POP ESI + * 00451655 C2 0400 RETN 0x4 + * 00451658 8B90 C08C0000 MOV EDX,DWORD PTR DS:[EAX+0x8CC0] + * 0045165E 8B8497 14080000 MOV EAX,DWORD PTR DS:[EDI+EDX*4+0x814] ; jichi: text in eax + * 00451665 8D58 01 LEA EBX,DWORD PTR DS:[EAX+0x1] ; jichi: hook here would crash + * 00451668 8A10 MOV DL,BYTE PTR DS:[EAX] ; jichi: text accessed here in eax + * 0045166A 40 INC EAX + * 0045166B 84D2 TEST DL,DL + * 0045166D ^75 F9 JNZ SHORT .00451668 + * 0045166F 2BC3 SUB EAX,EBX ; jichi: hook here, text in ebx-1 + * 00451671 8D58 01 LEA EBX,DWORD PTR DS:[EAX+0X1] + * 00451674 53 PUSH EBX + * 00451675 6A 00 PUSH 0x0 + * 00451677 53 PUSH EBX + * 00451678 6A 00 PUSH 0x0 + * 0045167A FF15 74104A00 CALL DWORD PTR DS:[0x4A1074] ; kernel32.GetProcessHeap + * 00451680 50 PUSH EAX + * 00451681 FF15 B4104A00 CALL DWORD PTR DS:[0x4A10B4] ; ntdll.RtlAllocateHeap + * 00451687 50 PUSH EAX + * 00451688 E8 233E0200 CALL .004754B0 + * 0045168D 8B8F B0A00000 MOV ECX,DWORD PTR DS:[EDI+0xA0B0] + * 00451693 8D0C89 LEA ECX,DWORD PTR DS:[ECX+ECX*4] + * 00451696 8B948F C08C0000 MOV EDX,DWORD PTR DS:[EDI+ECX*4+0x8CC0] + * 0045169D 8B8C97 14080000 MOV ECX,DWORD PTR DS:[EDI+EDX*4+0x814] ; jichi: text in ecx + * 004516A4 53 PUSH EBX + * 004516A5 51 PUSH ECX + * 004516A6 50 PUSH EAX + * 004516A7 8946 08 MOV DWORD PTR DS:[ESI+0x8],EAX + * 004516AA E8 31410200 CALL .004757E0 + * 004516AF 83C4 18 ADD ESP,0x18 + * 004516B2 FF8F B0A00000 DEC DWORD PTR DS:[EDI+0xA0B0] + * 004516B8 5D POP EBP + * 004516B9 5B POP EBX + * 004516BA 5F POP EDI + * 004516BB C746 04 05000000 MOV DWORD PTR DS:[ESI+0x4],0x5 + * 004516C2 8BC6 MOV EAX,ESI + * 004516C4 5E POP ESI + * 004516C5 C2 0400 RETN 0x4 + * 004516C8 53 PUSH EBX + * 004516C9 68 8C644A00 PUSH .004A648C + * 004516CE 6A 00 PUSH 0x0 + * 004516D0 E8 6BABFFFF CALL .0044C240 + * 004516D5 83C4 0C ADD ESP,0xC + * 004516D8 FF8F B0A00000 DEC DWORD PTR DS:[EDI+0xA0B0] + * 004516DE 5D POP EBP + * 004516DF 5B POP EBX + * 004516E0 5F POP EDI + * 004516E1 8BC6 MOV EAX,ESI + * 004516E3 5E POP ESI + * 004516E4 C2 0400 RETN 0x4 + * 004516E7 90 NOP + * 004516E8 BF 144500A3 MOV EDI,0xA3004514 + * 004516ED 15 45005816 ADC EAX,0x16580045 + * 004516F2 45 INC EBP + * 004516F3 00C8 ADD AL,CL + * 004516F5 16 PUSH SS + * 004516F6 45 INC EBP + * 004516F7 0001 ADD BYTE PTR DS:[ECX],AL + * 004516F9 16 PUSH SS + * 004516FA 45 INC EBP + * 004516FB 00E6 ADD DH,AH + * 004516FD 14 45 ADC AL,0x45 + * 004516FF 00FF ADD BH,BH + * 00451701 14 45 ADC AL,0x45 + * 00451703 0018 ADD BYTE PTR DS:[EAX],BL + * 00451705 15 45003015 ADC EAX,0x15300045 + * 0045170A 45 INC EBP + * 0045170B 004B 15 ADD BYTE PTR DS:[EBX+0x15],CL + * 0045170E 45 INC EBP + * 0045170F 0083 7C240800 ADD BYTE PTR DS:[EBX+0x8247C],AL + * 00451715 56 PUSH ESI + * 00451716 8BF1 MOV ESI,ECX + * 00451718 74 29 JE SHORT .00451743 + * 0045171A 8B86 B0A00000 MOV EAX,DWORD PTR DS:[ESI+0xA0B0] + * 00451720 3D FF000000 CMP EAX,0xFF + * 00451725 7C 15 JL SHORT .0045173C + * 00451727 68 74644A00 PUSH .004A6474 + * 0045172C 6A 00 PUSH 0x0 + * 0045172E E8 0DABFFFF CALL .0044C240 + * 00451733 83C4 08 ADD ESP,0x8 + * 00451736 33C0 XOR EAX,EAX + * 00451738 5E POP ESI + * 00451739 C2 0800 RETN 0x8 + * 0045173C 40 INC EAX + * 0045173D 8986 B0A00000 MOV DWORD PTR DS:[ESI+0xA0B0],EAX + * 00451743 8B86 B0A00000 MOV EAX,DWORD PTR DS:[ESI+0xA0B0] + * 00451749 8D0C80 LEA ECX,DWORD PTR DS:[EAX+EAX*4] + * 0045174C 8D0C8E LEA ECX,DWORD PTR DS:[ESI+ECX*4] + * 0045174F 57 PUSH EDI + * 00451750 8BB9 B08C0000 MOV EDI,DWORD PTR DS:[ECX+0x8CB0] + * 00451756 8BD7 MOV EDX,EDI + * 00451758 83EA 01 SUB EDX,0x1 + * 0045175B 74 70 JE SHORT .004517CD + * 0045175D 83EA 01 SUB EDX,0x1 + * 00451760 74 1A JE SHORT .0045177C + * 00451762 57 PUSH EDI + * 00451763 68 CC644A00 PUSH .004A64CC + * 00451768 6A 00 PUSH 0x0 + * 0045176A E8 D1AAFFFF CALL .0044C240 + * 0045176F 83C4 0C ADD ESP,0xC + * 00451772 5F POP EDI + * 00451773 B8 01000000 MOV EAX,0x1 + * 00451778 5E POP ESI + * 00451779 C2 0800 RETN 0x8 + * 0045177C 8D9480 2D230000 LEA EDX,DWORD PTR DS:[EAX+EAX*4+0x232D] + * 00451783 8B3C96 MOV EDI,DWORD PTR DS:[ESI+EDX*4] + * 00451786 85FF TEST EDI,EDI + * 00451788 0F8C C8000000 JL .00451856 + * 0045178E 8B81 C08C0000 MOV EAX,DWORD PTR DS:[ECX+0x8CC0] + * 00451794 99 CDQ + * 00451795 B9 1A000000 MOV ECX,0x1A + * 0045179A F7F9 IDIV ECX + * 0045179C C1E2 04 SHL EDX,0x4 + * 0045179F 03D7 ADD EDX,EDI + * 004517A1 85C0 TEST EAX,EAX + * 004517A3 74 13 JE SHORT .004517B8 + * 004517A5 DB4424 0C FILD DWORD PTR SS:[ESP+0xC] + * 004517A9 5F POP EDI + * 004517AA 8D41 E7 LEA EAX,DWORD PTR DS:[ECX-0x19] + * 004517AD D99C96 34A70000 FSTP DWORD PTR DS:[ESI+EDX*4+0xA734] + * 004517B4 5E POP ESI + * 004517B5 C2 0800 RETN 0x8 + * 004517B8 8B4424 0C MOV EAX,DWORD PTR SS:[ESP+0xC] + * 004517BC 898496 B4A00000 MOV DWORD PTR DS:[ESI+EDX*4+0xA0B4],EAX + * 004517C3 5F POP EDI + * 004517C4 B8 01000000 MOV EAX,0x1 + * 004517C9 5E POP ESI + * 004517CA C2 0800 RETN 0x8 + * 004517CD 8B89 C08C0000 MOV ECX,DWORD PTR DS:[ECX+0x8CC0] + * 004517D3 8D0489 LEA EAX,DWORD PTR DS:[ECX+ECX*4] + * 004517D6 03C0 ADD EAX,EAX + * 004517D8 0FBE9400 6416BC0>MOVSX EDX,BYTE PTR DS:[EAX+EAX+0xBC1664] + * 004517E0 03C0 ADD EAX,EAX + * 004517E2 8D7A FF LEA EDI,DWORD PTR DS:[EDX-0x1] + * 004517E5 83FF 04 CMP EDI,0x4 + * 004517E8 77 41 JA SHORT .0045182B + * 004517EA FF24BD 60184500 JMP DWORD PTR DS:[EDI*4+0x451860] + * 004517F1 8A4C24 0C MOV CL,BYTE PTR SS:[ESP+0xC] + * 004517F5 8888 6516BC00 MOV BYTE PTR DS:[EAX+0xBC1665],CL + * 004517FB EB 3E JMP SHORT .0045183B + * 004517FD 66:8B5424 0C MOV DX,WORD PTR SS:[ESP+0xC] + * 00451802 66:8990 6616BC00 MOV WORD PTR DS:[EAX+0xBC1666],DX + * 00451809 EB 30 JMP SHORT .0045183B + * 0045180B 8B4C24 0C MOV ECX,DWORD PTR SS:[ESP+0xC] + * 0045180F 8988 6816BC00 MOV DWORD PTR DS:[EAX+0xBC1668],ECX + * 00451815 EB 24 JMP SHORT .0045183B + * 00451817 DB4424 0C FILD DWORD PTR SS:[ESP+0xC] + * 0045181B D998 6C16BC00 FSTP DWORD PTR DS:[EAX+0xBC166C] + * 00451821 EB 18 JMP SHORT .0045183B + * 00451823 51 PUSH ECX + * 00451824 68 BC644A00 PUSH .004A64BC + * 00451829 EB 06 JMP SHORT .00451831 + * 0045182B 52 PUSH EDX + * 0045182C 68 A8644A00 PUSH .004A64A8 + * 00451831 6A 00 PUSH 0x0 + * 00451833 E8 08AAFFFF CALL .0044C240 + * 00451838 83C4 0C ADD ESP,0xC + * 0045183B 8B86 B0A00000 MOV EAX,DWORD PTR DS:[ESI+0xA0B0] + * 00451841 8D1480 LEA EDX,DWORD PTR DS:[EAX+EAX*4] + * 00451844 8B8496 C08C0000 MOV EAX,DWORD PTR DS:[ESI+EDX*4+0x8CC0] + * 0045184B 6A 00 PUSH 0x0 + * 0045184D 50 PUSH EAX + * 0045184E E8 FDF0FFFF CALL .00450950 + * 00451853 83C4 08 ADD ESP,0x8 + * 00451856 5F POP EDI + * 00451857 B8 01000000 MOV EAX,0x1 + * 0045185C 5E POP ESI + * 0045185D C2 0800 RETN 0x8 + * 00451860 F1 INT1 + * 00451861 17 POP SS ; Modification of segment register + * 00451862 45 INC EBP + * 00451863 00FD ADD CH,BH + * 00451865 17 POP SS ; Modification of segment register + * 00451866 45 INC EBP + * 00451867 000B ADD BYTE PTR DS:[EBX],CL + * 00451869 1845 00 SBB BYTE PTR SS:[EBP],AL + * 0045186C 17 POP SS ; Modification of segment register + * 0045186D 1845 00 SBB BYTE PTR SS:[EBP],AL + * 00451870 2318 AND EBX,DWORD PTR DS:[EAX] + * 00451872 45 INC EBP + * 00451873 00CC ADD AH,CL + * 00451875 CC INT3 + * 00451876 CC INT3 + * 00451877 CC INT3 + * 00451878 CC INT3 + * 00451879 CC INT3 + * 0045187A CC INT3 + * 0045187B CC INT3 + * 0045187C CC INT3 + * 0045187D CC INT3 + * + * EAX 00000038 + * ECX 00000004 ; jichi: fixed + * EDX 00000000 ; jichi: fixed + * EBX 00321221 + * ESP 0012FD98 + * EBP 00000002 + * ESI 0012FDC4 + * EDI 079047E0 + * EIP 00451671 .00451671 + */ +static void SpecialHookLeaf(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD text = regof(ebx, esp_base) - 1; // = ebx -1 + *data = text; + *len = ::strlen((LPCSTR)text); + *split = FIXED_SPLIT_VALUE; // only caller's address use as split +} +// Remove both \n and \k +static bool LeafFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + LPSTR text = (LPSTR)data; + if (::memchr(text, '\\', *size)) { + StringFilter(text, reinterpret_cast(size), "\\n", 2); + StringFilter(text, reinterpret_cast(size), "\\k", 2); + } + return true; +} +bool InsertLeafHook() +{ + const BYTE bytes[] = { + 0x8b,0x90, XX4, // 00451658 8b90 c08c0000 mov edx,dword ptr ds:[eax+0x8cc0] + 0x8b,0x84,0x97, XX4, // 0045165e 8b8497 14080000 mov eax,dword ptr ds:[edi+edx*4+0x814] + // The above is needed as there are other matches + 0x8d,0x58, 0x01, // 00451665 8d58 01 lea ebx,dword ptr ds:[eax+0x1] ; jichi: hook here would crash because of jump + 0x8a,0x10, // 00451668 8a10 mov dl,byte ptr ds:[eax] ; jichi: text accessed here in eax + 0x40, // 0045166a 40 inc eax + 0x84,0xd2, // 0045166b 84d2 test dl,dl + 0x75, 0xf9, // 0045166d ^75 f9 jnz short .00451668 + 0x2b,0xc3, // 0045166f 2bc3 sub eax,ebx ; jichi: hook here, text in ebx-1 + 0x8d,0x58, 0x01 // 00451671 8d58 01 lea ebx,dword ptr ds:[eax+0x1] + //0x53, // 00451674 53 push ebx + //0x6a, 0x00, // 00451675 6a 00 push 0x0 + //0x53, // 00451677 53 push ebx + //0x6a, 0x00, // 00451678 6a 00 push 0x0 + //0xff,0x15 // 0045167a ff15 74104a00 call dword ptr ds:[0x4a1074] ; kernel32.getprocessheap + }; + ULONG addr = MemDbg::matchBytes(bytes, sizeof(bytes), module_base_, module_limit_); + enum { addr_offset = 0x0045166f - 0x00451658 }; + //GROWL_DWORD(addr); + if (!addr) { + ConsoleOutput("vnreng:Leaf: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + //hp.offset = pusha_eax_off - 4; + hp.type = USING_STRING|USING_SPLIT; // use top of the stack as split + hp.text_fun = SpecialHookLeaf; + //hp.filter_fun = NewLineStringFilter; // remove two characters of "\\n" + hp.filter_fun = LeafFilter; // remove two characters + ConsoleOutput("vnreng: INSERT Leaf"); + NewHook(hp, "Leaf"); + + //ConsoleOutput("vnreng:Leaf: disable GDI hooks"); + //DisableGDIHooks(); + return true; +} + +/** jichi 12/27/2014 LunaSoft + * Sample game: [141226] [LunaSoft] 悪堕ラビリンス -- /hsn8@46C5EF + * + * /hsn8@46C5EF + * - addr: 0x46C5EF + * - off: 8 + * - type: 1025 = 0x401 + * + * - 0046c57e cc int3 + * - 0046c57f cc int3 + * - 0046c580 55 push ebp ; jichi: text in arg1 + * - 0046c581 8bec mov ebp,esp + * - 0046c583 83ec 08 sub esp,0x8 + * - 0046c586 894d f8 mov dword ptr ss:[ebp-0x8],ecx + * - 0046c589 8b4d f8 mov ecx,dword ptr ss:[ebp-0x8] + * - 0046c58c 83c1 1c add ecx,0x1c + * - 0046c58f e8 2cebf9ff call .0040b0c0 + * - 0046c594 8b00 mov eax,dword ptr ds:[eax] + * - 0046c596 8945 fc mov dword ptr ss:[ebp-0x4],eax + * - 0046c599 837d fc 00 cmp dword ptr ss:[ebp-0x4],0x0 + * - 0046c59d 75 21 jnz short .0046c5c0 + * - 0046c59f 8b4d f8 mov ecx,dword ptr ss:[ebp-0x8] + * - 0046c5a2 83c1 28 add ecx,0x28 + * - 0046c5a5 e8 16ebf9ff call .0040b0c0 + * - 0046c5aa 8b08 mov ecx,dword ptr ds:[eax] + * - 0046c5ac 894d fc mov dword ptr ss:[ebp-0x4],ecx + * - 0046c5af 8b55 fc mov edx,dword ptr ss:[ebp-0x4] + * - 0046c5b2 52 push edx + * - 0046c5b3 8b4d f8 mov ecx,dword ptr ss:[ebp-0x8] + * - 0046c5b6 83c1 28 add ecx,0x28 + * - 0046c5b9 e8 82d9f9ff call .00409f40 + * - 0046c5be eb 0f jmp short .0046c5cf + * - 0046c5c0 8b45 fc mov eax,dword ptr ss:[ebp-0x4] + * - 0046c5c3 50 push eax + * - 0046c5c4 8b4d f8 mov ecx,dword ptr ss:[ebp-0x8] + * - 0046c5c7 83c1 1c add ecx,0x1c + * - 0046c5ca e8 71d9f9ff call .00409f40 + * - 0046c5cf 837d fc 00 cmp dword ptr ss:[ebp-0x4],0x0 + * - 0046c5d3 75 02 jnz short .0046c5d7 + * - 0046c5d5 eb 61 jmp short .0046c638 + * - 0046c5d7 8b4d fc mov ecx,dword ptr ss:[ebp-0x4] + * - 0046c5da e8 b1cdf9ff call .00409390 + * - 0046c5df 8b4d 08 mov ecx,dword ptr ss:[ebp+0x8] + * - 0046c5e2 51 push ecx ; jichi: text in ecx + * - 0046c5e3 68 38010000 push 0x138 + * - 0046c5e8 8b55 fc mov edx,dword ptr ss:[ebp-0x4] + * - 0046c5eb 83c2 08 add edx,0x8 + * - 0046c5ee 52 push edx + * - 0046c5ef ff15 88b24c00 call dword ptr ds:[0x4cb288] ; msvcr90.strcpy_s, jichi: text accessed here in arg2 + * - 0046c5f5 83c4 0c add esp,0xc + * - 0046c5f8 8b45 0c mov eax,dword ptr ss:[ebp+0xc] + * - 0046c5fb 50 push eax + * - 0046c5fc 6a 10 push 0x10 + */ +// Remove: \n\s* +// This is dangerous since \n could appear within SJIS +//static bool LunaSoftFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +//{ +// size_t len = *size; +// char *str = reinterpret_cast(data), +// *cur; +// +// while (len && +// (cur = ::memchr(str, '\n', len)) && +// --len) { +// ::memmove(cur, cur + 1, len - (cur - str)); +// while (cur < str + len) +// if (::isspace(*cur) && --len) +// ::memmove(cur, cur + 1, len - (cur - str)); +// else if (len >= 2 && ::iswspace(*(LPCWSTR)cur) && (len-=2)) +// ::memmove(cur, cur + 2, len - (cur - str)); +// else +// break; +// } +// +// *size = len; +// return true; +//} +bool InsertLunaSoftHook() +{ + const BYTE bytes[] = { + 0xcc, // 0046c57e cc int3 + 0xcc, // 0046c57f cc int3 + 0x55, // 0046c580 55 push ebp ; jichi: text in arg1 + 0x8b,0xec, // 0046c581 8bec mov ebp,esp + 0x83,0xec, 0x08, // 0046c583 83ec 08 sub esp,0x8 + 0x89,0x4d, 0xf8, // 0046c586 894d f8 mov dword ptr ss:[ebp-0x8],ecx + 0x8b,0x4d, 0xf8, // 0046c589 8b4d f8 mov ecx,dword ptr ss:[ebp-0x8] + 0x83,0xc1, 0x1c, // 0046c58c 83c1 1c add ecx,0x1c + 0xe8 // 0046c58f e8 2cebf9ff call .0040b0c0 + }; + enum { addr_offset = 2 }; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + //GROWL(addr); + if (!addr) { + ConsoleOutput("vnreng:LunaSoft: pattern not found"); + return false; + } + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.offset = 1 * 4; // arg1 + hp.type = USING_STRING; + //hp.filter_fun = LunaSoftFilter; // remove \n + ConsoleOutput("vnreng: INSERT LunaSoft"); + NewHook(hp, "LunaSoft"); + + // There are no GDI functions anyway + //ConsoleOutput("vnreng:LunaSoft: disable GDI hooks"); + //DisableGDIHooks(); + return true; +} + +/** jichi 2/6/2015 FocasLens (Touhou) + * Sample game: [141227] [FocasLens] 幻想人形演� + * + * Debugging method: + * 1. Find first matched text, which has stable address + * 2. Insert WRITE hw break point + * 3. Find where the text is assigned + * + * The game also invokes GDI functions (GetGlyphOutlineA), where the access is cached and looped. + * + * Issues: + * - This hook cannot find name thread + * - Selected character name is hard-coded to the thread + * + * 001faaed cc int3 + * 001faaee cc int3 + * 001faaef cc int3 + * 001faaf0 55 push ebp + * 001faaf1 8bec mov ebp,esp + * 001faaf3 51 push ecx + * 001faaf4 53 push ebx + * 001faaf5 56 push esi + * 001faaf6 57 push edi + * 001faaf7 8bf0 mov esi,eax + * 001faaf9 e8 98281500 call .0034d396 + * 001faafe 50 push eax + * 001faaff a1 b08bb100 mov eax,dword ptr ds:[0xb18bb0] + * 001fab04 03c6 add eax,esi + * 001fab06 50 push eax + * 001fab07 e8 9b241500 call .0034cfa7 + * 001fab0c 8b0d e88bb100 mov ecx,dword ptr ds:[0xb18be8] + * 001fab12 8b3d b08bb100 mov edi,dword ptr ds:[0xb18bb0] + * 001fab18 83c1 f7 add ecx,-0x9 + * 001fab1b 83c4 08 add esp,0x8 + * 001fab1e 8bd8 mov ebx,eax + * 001fab20 390d ec8bb100 cmp dword ptr ds:[0xb18bec],ecx + * 001fab26 7c 65 jl short .001fab8d + * 001fab28 803c37 20 cmp byte ptr ds:[edi+esi],0x20 + * 001fab2c 74 41 je short .001fab6f + * 001fab2e 803c37 81 cmp byte ptr ds:[edi+esi],0x81 + * 001fab32 75 4d jnz short .001fab81 + * 001fab34 807c37 01 42 cmp byte ptr ds:[edi+esi+0x1],0x42 + * 001fab39 74 34 je short .001fab6f + * 001fab3b 803c37 81 cmp byte ptr ds:[edi+esi],0x81 + * 001fab3f 75 40 jnz short .001fab81 + * 001fab41 807c37 01 41 cmp byte ptr ds:[edi+esi+0x1],0x41 + * 001fab46 74 27 je short .001fab6f + * 001fab48 803c37 81 cmp byte ptr ds:[edi+esi],0x81 + * 001fab4c 75 33 jnz short .001fab81 + * 001fab4e 807c37 01 48 cmp byte ptr ds:[edi+esi+0x1],0x48 + * 001fab53 74 1a je short .001fab6f + * 001fab55 803c37 81 cmp byte ptr ds:[edi+esi],0x81 + * 001fab59 75 26 jnz short .001fab81 + * 001fab5b 807c37 01 49 cmp byte ptr ds:[edi+esi+0x1],0x49 + * 001fab60 74 0d je short .001fab6f + * 001fab62 803c37 81 cmp byte ptr ds:[edi+esi],0x81 + * 001fab66 75 19 jnz short .001fab81 + * 001fab68 807c37 01 40 cmp byte ptr ds:[edi+esi+0x1],0x40 + * 001fab6d 75 12 jnz short .001fab81 + * 001fab6f 803d c58bb100 00 cmp byte ptr ds:[0xb18bc5],0x0 + * 001fab76 75 09 jnz short .001fab81 + * 001fab78 c605 c58bb100 01 mov byte ptr ds:[0xb18bc5],0x1 + * 001fab7f eb 0c jmp short .001fab8d + * 001fab81 e8 7a000000 call .001fac00 + * 001fab86 c605 c58bb100 00 mov byte ptr ds:[0xb18bc5],0x0 + * 001fab8d 8b0d e48bb100 mov ecx,dword ptr ds:[0xb18be4] + * 001fab93 33c0 xor eax,eax + * 001fab95 85db test ebx,ebx + * 001fab97 7e 2b jle short .001fabc4 + * 001fab99 8d1437 lea edx,dword ptr ds:[edi+esi] + * 001fab9c 8b35 ec8bb100 mov esi,dword ptr ds:[0xb18bec] + * 001faba2 8955 fc mov dword ptr ss:[ebp-0x4],edx + * 001faba5 8bd1 mov edx,ecx + * 001faba7 0faf15 e88bb100 imul edx,dword ptr ds:[0xb18be8] + * 001fabae 0315 bc8bb100 add edx,dword ptr ds:[0xb18bbc] ; .00b180f8 + * 001fabb4 03f2 add esi,edx + * 001fabb6 8b55 fc mov edx,dword ptr ss:[ebp-0x4] + * 001fabb9 8a1402 mov dl,byte ptr ds:[edx+eax] + * 001fabbc 881406 mov byte ptr ds:[esi+eax],dl ; jichi: text is in dl in byte + * 001fabbf 40 inc eax + * 001fabc0 3bc3 cmp eax,ebx + * 001fabc2 ^7c f2 jl short .001fabb6 + * 001fabc4 0faf0d e88bb100 imul ecx,dword ptr ds:[0xb18be8] + * 001fabcb 030d bc8bb100 add ecx,dword ptr ds:[0xb18bbc] ; .00b180f8 + * 001fabd1 a1 ec8bb100 mov eax,dword ptr ds:[0xb18bec] + * 001fabd6 03fb add edi,ebx + * 001fabd8 893d b08bb100 mov dword ptr ds:[0xb18bb0],edi + * 001fabde 5f pop edi + * 001fabdf 03c8 add ecx,eax + * 001fabe1 03c3 add eax,ebx + * 001fabe3 5e pop esi + * 001fabe4 c60419 00 mov byte ptr ds:[ecx+ebx],0x0 + * 001fabe8 a3 ec8bb100 mov dword ptr ds:[0xb18bec],eax + * 001fabed 5b pop ebx + * 001fabee 8be5 mov esp,ebp + * 001fabf0 5d pop ebp + * 001fabf1 c3 retn + * 001fabf2 cc int3 + * 001fabf3 cc int3 + * 001fabf4 cc int3 + * 001fabf5 cc int3 + * 001fabf6 cc int3 + * 001fabf7 cc int3 + */ +static void SpecialHookFocasLens(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD addr = esp_base + pusha_edx_off - 4; + if (*(char *)addr) { + *data = addr; + *len = 1; + *split = FIXED_SPLIT_VALUE; + } +} +bool InsertFocasLensHook() +{ + const BYTE bytes[] = { + 0x8a,0x14,0x02, // 001fabb9 8a1402 mov dl,byte ptr ds:[edx+eax] + 0x88,0x14,0x06, // 001fabbc 881406 mov byte ptr ds:[esi+eax],dl ; jichi: text is in dl in byte + 0x40, // 001fabbf 40 inc eax + 0x3b,0xc3 // 001fabc0 3bc3 cmp eax,ebx + }; + enum { addr_offset = 0x001fabbc - 0x001fabb9 }; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + //GROWL(addr); + if (!addr) { + ConsoleOutput("vnreng:FocasLens: pattern not found"); + return false; + } + HookParam hp = {}; + hp.address = addr + addr_offset; + //hp.offset = pusha_edx_off - 4; + //hp.length_offset = 1; // Why this does not work?! + //hp.split = pusha_eax_off - 4; // use eax as split + hp.text_fun = SpecialHookFocasLens; // use special hook to force byte access + hp.type = USING_STRING|USING_SPLIT|FIXING_SPLIT|NO_CONTEXT; // no context to get rid of relative function address + ConsoleOutput("vnreng: INSERT FocasLens"); + NewHook(hp, "FocasLens"); + + // GDI functions are kept in case the font is not cached + //DisableGDIHooks(); + return true; +} + +/** jichi 2/6/2015 Syuntada + * Sample game: [140816] [平安亭] カノジョのお母さん�好きですか-- /HA-18@6944C:kanojo.exe + * + * /HA-18@6944C:kanojo.exe + * - addr: 431180 = 0x6944c + * - module: 1301076281 + * - off: 4294967268 = 0xffffffe4 = - 0x1c + * - length_offset: 1 + * - type: 68 = 0x44 + * + * 004692bd cc int3 + * 004692be cc int3 + * 004692bf cc int3 + * 004692c0 83ec 48 sub esp,0x48 + * 004692c3 53 push ebx + * 004692c4 55 push ebp + * 004692c5 56 push esi + * 004692c6 8bf1 mov esi,ecx + * 004692c8 8b86 d4000000 mov eax,dword ptr ds:[esi+0xd4] + * 004692ce 0386 8c040000 add eax,dword ptr ds:[esi+0x48c] + * 004692d4 8b8e c8010000 mov ecx,dword ptr ds:[esi+0x1c8] + * 004692da 8b9e 90040000 mov ebx,dword ptr ds:[esi+0x490] + * 004692e0 03c0 add eax,eax + * 004692e2 03c0 add eax,eax + * 004692e4 894424 24 mov dword ptr ss:[esp+0x24],eax + * 004692e8 8b86 c4010000 mov eax,dword ptr ds:[esi+0x1c4] + * 004692ee 8986 94040000 mov dword ptr ds:[esi+0x494],eax + * 004692f4 8b4424 60 mov eax,dword ptr ss:[esp+0x60] + * 004692f8 898e 98040000 mov dword ptr ds:[esi+0x498],ecx + * 004692fe 0fb628 movzx ebp,byte ptr ds:[eax] + * 00469301 0fb650 01 movzx edx,byte ptr ds:[eax+0x1] + * 00469305 c1e5 08 shl ebp,0x8 + * 00469308 0bea or ebp,edx + * 0046930a 03db add ebx,ebx + * 0046930c 03db add ebx,ebx + * 0046930e 8d8d 617dffff lea ecx,dword ptr ss:[ebp+0xffff7d61] + * 00469314 57 push edi + * 00469315 895c24 30 mov dword ptr ss:[esp+0x30],ebx + * 00469319 c74424 38 100000>mov dword ptr ss:[esp+0x38],0x10 + * 00469321 896c24 34 mov dword ptr ss:[esp+0x34],ebp + * 00469325 b8 02000000 mov eax,0x2 + * 0046932a 83f9 52 cmp ecx,0x52 + * 0046932d 77 02 ja short .00469331 + * 0046932f 33c0 xor eax,eax + * 00469331 81fd 41810000 cmp ebp,0x8141 + * 00469337 7c 08 jl short .00469341 + * 00469339 81fd 9a820000 cmp ebp,0x829a + * 0046933f 7e 0e jle short .0046934f + * 00469341 8d95 c07cffff lea edx,dword ptr ss:[ebp+0xffff7cc0] + * 00469347 81fa 4f040000 cmp edx,0x44f + * 0046934d 77 09 ja short .00469358 + * 0046934f bf 01000000 mov edi,0x1 + * 00469354 8bc7 mov eax,edi + * 00469356 eb 05 jmp short .0046935d + * 00469358 bf 01000000 mov edi,0x1 + * 0046935d 83e8 00 sub eax,0x0 + * 00469360 74 2a je short .0046938c + * 00469362 2bc7 sub eax,edi + * 00469364 74 0c je short .00469372 + * 00469366 2bc7 sub eax,edi + * 00469368 75 3a jnz short .004693a4 + * 0046936a 8b96 68010000 mov edx,dword ptr ds:[esi+0x168] + * 00469370 eb 20 jmp short .00469392 + * 00469372 8b96 7c090000 mov edx,dword ptr ds:[esi+0x97c] + * 00469378 8b86 64010000 mov eax,dword ptr ds:[esi+0x164] + * 0046937e 8b52 28 mov edx,dword ptr ds:[edx+0x28] + * 00469381 8d8e 7c090000 lea ecx,dword ptr ds:[esi+0x97c] + * 00469387 50 push eax + * 00469388 ffd2 call edx + * 0046938a eb 18 jmp short .004693a4 + * 0046938c 8b96 60010000 mov edx,dword ptr ds:[esi+0x160] + * 00469392 8b86 7c090000 mov eax,dword ptr ds:[esi+0x97c] + * 00469398 8b40 28 mov eax,dword ptr ds:[eax+0x28] + * 0046939b 8d8e 7c090000 lea ecx,dword ptr ds:[esi+0x97c] + * 004693a1 52 push edx + * 004693a2 ffd0 call eax + * 004693a4 39be d40f0000 cmp dword ptr ds:[esi+0xfd4],edi + * 004693aa 75 45 jnz short .004693f1 + * 004693ac 8b8e 90040000 mov ecx,dword ptr ds:[esi+0x490] + * 004693b2 b8 d0020000 mov eax,0x2d0 + * 004693b7 2bc1 sub eax,ecx + * 004693b9 2b86 c8010000 sub eax,dword ptr ds:[esi+0x1c8] + * 004693bf 68 000f0000 push 0xf00 + * 004693c4 8d0480 lea eax,dword ptr ds:[eax+eax*4] + * 004693c7 c1e0 08 shl eax,0x8 + * 004693ca 0386 c4010000 add eax,dword ptr ds:[esi+0x1c4] + * 004693d0 8d1440 lea edx,dword ptr ds:[eax+eax*2] + * 004693d3 8b4424 60 mov eax,dword ptr ss:[esp+0x60] + * 004693d7 52 push edx + * 004693d8 8b50 40 mov edx,dword ptr ds:[eax+0x40] + * 004693db 8b86 c8000000 mov eax,dword ptr ds:[esi+0xc8] + * 004693e1 0386 8c040000 add eax,dword ptr ds:[esi+0x48c] + * 004693e7 52 push edx + * 004693e8 50 push eax + * 004693e9 51 push ecx + * 004693ea 8bce mov ecx,esi + * 004693ec e8 9fc4ffff call .00465890 + * 004693f1 39be d00f0000 cmp dword ptr ds:[esi+0xfd0],edi + * 004693f7 0f85 f2010000 jnz .004695ef + * 004693fd 8d86 20100000 lea eax,dword ptr ds:[esi+0x1020] + * 00469403 50 push eax + * 00469404 55 push ebp + * 00469405 8bce mov ecx,esi + * 00469407 e8 64f4ffff call .00468870 + * 0046940c 8a4e 25 mov cl,byte ptr ds:[esi+0x25] + * 0046940f 8a56 26 mov dl,byte ptr ds:[esi+0x26] + * 00469412 884c24 18 mov byte ptr ss:[esp+0x18],cl + * 00469416 8b4c24 5c mov ecx,dword ptr ss:[esp+0x5c] + * 0046941a 885424 14 mov byte ptr ss:[esp+0x14],dl + * 0046941e 8b51 40 mov edx,dword ptr ds:[ecx+0x40] + * 00469421 895424 20 mov dword ptr ss:[esp+0x20],edx + * 00469425 b9 d0020000 mov ecx,0x2d0 + * 0046942a 2bcb sub ecx,ebx + * 0046942c ba 00000000 mov edx,0x0 + * 00469431 0f98c2 sets dl + * 00469434 8bf8 mov edi,eax + * 00469436 8a46 24 mov al,byte ptr ds:[esi+0x24] + * 00469439 884424 1c mov byte ptr ss:[esp+0x1c],al + * 0046943d 4a dec edx + * 0046943e 23d1 and edx,ecx + * 00469440 69d2 000f0000 imul edx,edx,0xf00 + * 00469446 8bca mov ecx,edx + * 00469448 894c24 24 mov dword ptr ss:[esp+0x24],ecx + * 0046944c 85ff test edi,edi ; jichi: hook here + * 0046944e 74 3a je short .0046948a + * 00469450 8b5424 14 mov edx,dword ptr ss:[esp+0x14] + * 00469454 6a 00 push 0x0 + * 00469456 57 push edi + * 00469457 8d86 c80c0000 lea eax,dword ptr ds:[esi+0xcc8] + * 0046945d 50 push eax + * 0046945e 8b4424 24 mov eax,dword ptr ss:[esp+0x24] + * 00469462 6a 10 push 0x10 + * 00469464 52 push edx + * 00469465 8b5424 30 mov edx,dword ptr ss:[esp+0x30] + * 00469469 50 push eax + * 0046946a 8b4424 38 mov eax,dword ptr ss:[esp+0x38] + * 0046946e 52 push edx + * 0046946f 68 000f0000 push 0xf00 + * 00469474 51 push ecx + * 00469475 8b4c24 4c mov ecx,dword ptr ss:[esp+0x4c] + */ +bool InsertSyuntadaHook() +{ + const BYTE bytes[] = { + 0x4a, // 0046943d 4a dec edx + 0x23,0xd1, // 0046943e 23d1 and edx,ecx + 0x69,0xd2, 0x00,0x0f,0x00,0x00, // 00469440 69d2 000f0000 imul edx,edx,0xf00 + 0x8b,0xca, // 00469446 8bca mov ecx,edx + 0x89,0x4c,0x24, 0x24, // 00469448 894c24 24 mov dword ptr ss:[esp+0x24],ecx + 0x85,0xff, // 0046944c 85ff test edi,edi ; jichi: hook here + 0x74, 0x3a // 0046944e 74 3a je short .0046948a + }; + enum { addr_offset = 0x0046944c - 0x0046943d }; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + //GROWL(addr); + if (!addr) { + ConsoleOutput("vnreng:Syuntada: pattern not found"); + return false; + } + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.offset = -0x1c; + hp.length_offset = 1; + hp.type = BIG_ENDIAN; // 0x4 + ConsoleOutput("vnreng: INSERT Syuntada"); + NewHook(hp, "Syuntada"); + + // TextOutA will produce repeated texts + ConsoleOutput("vnreng:Syuntada: disable GDI hooks"); + DisableGDIHooks(); + return true; +} + +/** + * jichi 5/22/2015: Insert Bootup hook + * Sample games: + * - [090709] [PIL] 仏蘭西少女 + * - [110318] [Daisy2] 三国恋戦� * - [110329] [PIL/SLASH] 神学校 + * - [150527] [Daisy2] 絶対階級学� * + * Properties + * - There is Bootup.dat existing in the game folder. + * - lstrlenW can find text repeating once + * - GetCharABCWidthsW and TextOutW can find cached text that missing characters + * GetCharABCWidthsA and TextOutA for old games. + * - There is only one TextOut (W for new and A for old). + * + * Logic: + * + GDI hook + * - Hook to the caller of TextOut + * + Lstr hook + * - Find last (second) caller of the first GetCharABCWidths after int3 + * - Find the lstrlen function in this caller, and hook to it + * + * Full text is in arg1, shifted one by one. + * Character to paint is also in arg3 + * + * All Bootup games are slightly different + * - 三国恋戦�仏蘭西少女: text in both lstrlenA and caller of TextOutA + * But I didn't find correct lstrlenA to hook. BootupLstrA find nothing for 仏蘭西少女 and name for 三国恋戦� + * - 神学校: text in both lstrlenW and TextOutW, but lstrlenW has repetition + * Caller of TextOutW the same as that of TextOutA + * - 絶対階級学� text in both lstrlenW and TextOutW. But TextOutW's name has repetition + * Caller of TextOutW different 神学校 + * + * Here's the beginning of caller of TextOutW in 絶対階級学� + * 00B61ADD CC INT3 + * 00B61ADE CC INT3 + * 00B61ADF CC INT3 + * 00B61AE0 55 PUSH EBP + * 00B61AE1 8BEC MOV EBP,ESP + * 00B61AE3 81EC 98000000 SUB ESP,0x98 + * 00B61AE9 53 PUSH EBX + * 00B61AEA 56 PUSH ESI + * 00B61AEB 57 PUSH EDI + * 00B61AEC 8BF2 MOV ESI,EDX + * 00B61AEE 8BF9 MOV EDI,ECX + * 00B61AF0 8975 D8 MOV DWORD PTR SS:[EBP-0x28],ESI + * 00B61AF3 897D E0 MOV DWORD PTR SS:[EBP-0x20],EDI + * 00B61AF6 E8 A5FEFFFF CALL .00B619A0 + * 00B61AFB 8BD8 MOV EBX,EAX + * 00B61AFD 895D CC MOV DWORD PTR SS:[EBP-0x34],EBX + * 00B61B00 66:833B 00 CMP WORD PTR DS:[EBX],0x0 + * 00B61B04 0F85 0B020000 JNZ .00B61D15 + * 00B61B0A B8 00010000 MOV EAX,0x100 + * 00B61B0F 66:8933 MOV WORD PTR DS:[EBX],SI + * 00B61B12 66:3BF0 CMP SI,AX + * 00B61B15 72 26 JB SHORT .00B61B3D + * 00B61B17 8B47 3C MOV EAX,DWORD PTR DS:[EDI+0x3C] + * 00B61B1A 85C0 TEST EAX,EAX + * 00B61B1C 74 1F JE SHORT .00B61B3D + * 00B61B1E 8B57 44 MOV EDX,DWORD PTR DS:[EDI+0x44] + * 00B61B21 85D2 TEST EDX,EDX + * 00B61B23 7E 18 JLE SHORT .00B61B3D + * 00B61B25 33C9 XOR ECX,ECX + * 00B61B27 85D2 TEST EDX,EDX + * 00B61B29 7E 12 JLE SHORT .00B61B3D + * 00B61B2B 8B47 40 MOV EAX,DWORD PTR DS:[EDI+0x40] + * 00B61B2E 8BFF MOV EDI,EDI + * 00B61B30 66:3930 CMP WORD PTR DS:[EAX],SI + * 00B61B33 74 6F JE SHORT .00B61BA4 + * 00B61B35 41 INC ECX + * 00B61B36 83C0 02 ADD EAX,0x2 + * 00B61B39 3BCA CMP ECX,EDX + * 00B61B3B ^7C F3 JL SHORT .00B61B30 + * 00B61B3D 33C0 XOR EAX,EAX + * 00B61B3F 66:8945 9E MOV WORD PTR SS:[EBP-0x62],AX + * 00B61B43 8B47 04 MOV EAX,DWORD PTR DS:[EDI+0x4] + * 00B61B46 0FAF47 1C IMUL EAX,DWORD PTR DS:[EDI+0x1C] + * 00B61B4A 0FAF47 1C IMUL EAX,DWORD PTR DS:[EDI+0x1C] + * 00B61B4E 0FAF47 18 IMUL EAX,DWORD PTR DS:[EDI+0x18] + * 00B61B52 50 PUSH EAX + * 00B61B53 6A 00 PUSH 0x0 + * 00B61B55 FF77 14 PUSH DWORD PTR DS:[EDI+0x14] + * 00B61B58 66:8975 9C MOV WORD PTR SS:[EBP-0x64],SI + * 00B61B5C E8 2FC20200 CALL .00B8DD90 + * 00B61B61 83C4 0C ADD ESP,0xC + * 00B61B64 8D45 9C LEA EAX,DWORD PTR SS:[EBP-0x64] + * 00B61B67 6A 01 PUSH 0x1 + * 00B61B69 50 PUSH EAX + * 00B61B6A 6A 00 PUSH 0x0 + * 00B61B6C 6A 00 PUSH 0x0 + * 00B61B6E FF77 10 PUSH DWORD PTR DS:[EDI+0x10] + * 00B61B71 FF15 8820BB00 CALL DWORD PTR DS:[0xBB2088] ; gdi32.TextOutW + * 00B61B77 8B47 1C MOV EAX,DWORD PTR DS:[EDI+0x1C] + * 00B61B7A 8B57 14 MOV EDX,DWORD PTR DS:[EDI+0x14] + * 00B61B7D 8B7F 04 MOV EDI,DWORD PTR DS:[EDI+0x4] + * 00B61B80 8B73 0C MOV ESI,DWORD PTR DS:[EBX+0xC] + * 00B61B83 0FAFF8 IMUL EDI,EAX + * 00B61B86 48 DEC EAX + * 00B61B87 8975 C4 MOV DWORD PTR SS:[EBP-0x3C],ESI + * 00B61B8A 897D C8 MOV DWORD PTR SS:[EBP-0x38],EDI + * + * TextOutW's caller for 神学校 + * 0113183E CC INT3 + * 0113183F CC INT3 + * 01131840 55 PUSH EBP + * 01131841 8BEC MOV EBP,ESP + * 01131843 83EC 74 SUB ESP,0x74 + * 01131846 53 PUSH EBX + * 01131847 56 PUSH ESI + * 01131848 8B75 08 MOV ESI,DWORD PTR SS:[EBP+0x8] + * 0113184B 57 PUSH EDI + * 0113184C 8B7D 0C MOV EDI,DWORD PTR SS:[EBP+0xC] + * 0113184F 8BCF MOV ECX,EDI + * 01131851 8BD6 MOV EDX,ESI + * 01131853 E8 A8FEFFFF CALL .01131700 + * 01131858 8BD8 MOV EBX,EAX + * 0113185A 66:833B 00 CMP WORD PTR DS:[EBX],0x0 + * 0113185E 895D 90 MOV DWORD PTR SS:[EBP-0x70],EBX + * 01131861 0F85 700F0000 JNZ .011327D7 + * 01131867 B8 00010000 MOV EAX,0x100 + * 0113186C 66:893B MOV WORD PTR DS:[EBX],DI + * 0113186F 66:3BF8 CMP DI,AX + * 01131872 72 2E JB SHORT .011318A2 + * 01131874 8B56 3C MOV EDX,DWORD PTR DS:[ESI+0x3C] + * 01131877 85D2 TEST EDX,EDX + * 01131879 74 27 JE SHORT .011318A2 + * 0113187B 8B46 44 MOV EAX,DWORD PTR DS:[ESI+0x44] + * 0113187E 85C0 TEST EAX,EAX + * 01131880 7E 20 JLE SHORT .011318A2 + * 01131882 33FF XOR EDI,EDI + * 01131884 85C0 TEST EAX,EAX + * 01131886 7E 1A JLE SHORT .011318A2 + * 01131888 8B46 40 MOV EAX,DWORD PTR DS:[ESI+0x40] + * 0113188B EB 03 JMP SHORT .01131890 + * 0113188D 8D49 00 LEA ECX,DWORD PTR DS:[ECX] + * 01131890 66:8B4D 0C MOV CX,WORD PTR SS:[EBP+0xC] + * 01131894 66:3908 CMP WORD PTR DS:[EAX],CX + * 01131897 74 74 JE SHORT .0113190D + * 01131899 47 INC EDI + * 0113189A 83C0 02 ADD EAX,0x2 + * 0113189D 3B7E 44 CMP EDI,DWORD PTR DS:[ESI+0x44] + * 011318A0 ^7C EE JL SHORT .01131890 + * 011318A2 66:8B45 0C MOV AX,WORD PTR SS:[EBP+0xC] + * 011318A6 66:8945 8C MOV WORD PTR SS:[EBP-0x74],AX + * 011318AA 8B46 1C MOV EAX,DWORD PTR DS:[ESI+0x1C] + * 011318AD 0FAFC0 IMUL EAX,EAX + * 011318B0 0FAF46 18 IMUL EAX,DWORD PTR DS:[ESI+0x18] + * 011318B4 0FAF46 04 IMUL EAX,DWORD PTR DS:[ESI+0x4] + * 011318B8 8B56 14 MOV EDX,DWORD PTR DS:[ESI+0x14] + * 011318BB 33C9 XOR ECX,ECX + * 011318BD 50 PUSH EAX + * 011318BE 51 PUSH ECX + * 011318BF 52 PUSH EDX + * 011318C0 66:894D 8E MOV WORD PTR SS:[EBP-0x72],CX + * 011318C4 E8 87060200 CALL .01151F50 + * 011318C9 8B4E 10 MOV ECX,DWORD PTR DS:[ESI+0x10] + * 011318CC 83C4 0C ADD ESP,0xC + * 011318CF 6A 01 PUSH 0x1 + * 011318D1 8D45 8C LEA EAX,DWORD PTR SS:[EBP-0x74] + * 011318D4 50 PUSH EAX + * 011318D5 6A 00 PUSH 0x0 + * 011318D7 6A 00 PUSH 0x0 + * 011318D9 51 PUSH ECX + * 011318DA FF15 38101701 CALL DWORD PTR DS:[0x1171038] ; gdi32.TextOutW + * 011318E0 8B4E 1C MOV ECX,DWORD PTR DS:[ESI+0x1C] + * 011318E3 8B46 04 MOV EAX,DWORD PTR DS:[ESI+0x4] + * 011318E6 8B56 14 MOV EDX,DWORD PTR DS:[ESI+0x14] + * 011318E9 0FAFC1 IMUL EAX,ECX + * 011318EC 8B7B 0C MOV EDI,DWORD PTR DS:[EBX+0xC] + */ +namespace { // unnamed +bool BootupGDIHook(DWORD esp_base, HookParam *hp) +{ + DWORD arg2 = argof(2, esp_base); + if ((arg2 & 0xffff0000)) { // if arg2 high bits are there, this is new Bootup game + hp->type |= DATA_INDIRECT; + hp->offset = 4 * 3; // arg3 + hp->split = pusha_ebx_off - 4; // use ebx value to split name out, which has repetitions + } + return false; // run once and stop hooking +} +bool InsertBootupGDIHook() +{ + bool widechar = true; + ULONG addr = MemDbg::findCallerAddressAfterInt3((ULONG)TextOutW, module_base_, module_limit_); + if (!addr) { + addr = MemDbg::findCallerAddressAfterInt3((ULONG)TextOutA, module_base_, module_limit_); + widechar = false; + } + if (!addr) { + ConsoleOutput("vnreng:BootupGDI: failed to find TextOut"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.type = USING_SPLIT|NO_CONTEXT; // use NO_CONTEXT to get rid of floating reladdr + hp.type |= widechar ? USING_UNICODE : BIG_ENDIAN; // use context as split is sufficient, but will produce floating split + hp.length_offset = 1; // character by character + + hp.offset = 4 * 2; // arg2, character in arg2, could be modified by hook + if (widechar) + hp.split = pusha_edx_off - 4; // split name out which contains repetitions + else + hp.split = 4 * 1; // use arg1 to split name out furigana, this cause split to be floating + hp.hook_fun = BootupGDIHook; // adjust hook parameter at runtime + + ConsoleOutput("vnreng: INSERT BootupGDI"); + NewHook(hp, widechar ? "BootupW" : "BootupA"); + + ConsoleOutput("vnreng:BootupGDI: disable GDI hooks"); + DisableGDIHooks(); + return true; +} +bool InsertBootupLstrHook() // for character name +{ + bool widechar = true; + ULONG addr = MemDbg::findLastCallerAddressAfterInt3((ULONG)GetCharABCWidthsW, module_base_, module_limit_); + if (!addr) { + // Do not hook to lstrlenA, which causes text extraction to stop + //addr = MemDbg::findLastCallerAddressAfterInt3((ULONG)GetCharABCWidthsA, module_base_, module_limit_); + //widechar = false; + } + if (!addr) { + ConsoleOutput("vnreng:BootupLstr: failed to find GetCharABCWidths"); + return false; + } + //GROWL_DWORD2(addr, module_base_); + //enum { range = 0x200 }; // 0x012A2CCB - 0x12A2CB0 = 0x1b + addr = MemDbg::findCallAddress(widechar ? (ULONG)::lstrlenW : (ULONG)::lstrlenA, + module_base_, module_limit_, + addr - module_base_); //, range); // no range + if (!addr) { + ConsoleOutput("vnreng:BootupLstr: failed to find lstrlen"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.type = widechar ? USING_UNICODE : USING_STRING; // use context as split is sufficient, but will produce floating split + //hp.type = USING_UNICODE|NO_CONTEXT|USING_SPLIT; // use text address as split + //hp.split = 0; + + ConsoleOutput("vnreng: INSERT BootupLstr"); + NewHook(hp, widechar ? "BootupLstrW" : "BootupLstrA"); + return true; +} +} // unnamed namespace +bool InsertBootupHook() +{ + bool ret = InsertBootupGDIHook(); + InsertBootupLstrHook(); + return ret; +} + +/** jichi 7/23/2015 Escude + * Sample game: Re;Lord ��ルフォルト�魔女とぬぁ�るみ * See: http://capita.tistory.com/m/post/210 + * + * ENCODEKOR,FORCEFONT(5),HOOK(0x0042CB40,TRANS([[ESP+0x4]+0x20],PTRCHEAT,PTRBACKUP,SAFE),RETNPOS(SOURCE)),FONT(Malgun Gothic,-13) + * + * GDI functions: TextOutA, GetTextExtentPoint32A + * It requires changing function to MS Gothic using configure.exe + * + * Text in arg1 + 0x20 + * + * 0042CB3C CC INT3 + * 0042CB3D CC INT3 + * 0042CB3E CC INT3 + * 0042CB3F CC INT3 + * 0042CB40 56 PUSH ESI + * 0042CB41 8B7424 08 MOV ESI,DWORD PTR SS:[ESP+0x8] + * 0042CB45 8B06 MOV EAX,DWORD PTR DS:[ESI] + * 0042CB47 50 PUSH EAX + * 0042CB48 E8 53FC0A00 CALL .004DC7A0 + * 0042CB4D 8B56 04 MOV EDX,DWORD PTR DS:[ESI+0x4] + * 0042CB50 83C4 04 ADD ESP,0x4 + * 0042CB53 5E POP ESI + * 0042CB54 85D2 TEST EDX,EDX + * 0042CB56 74 7E JE SHORT .0042CBD6 + * 0042CB58 85C0 TEST EAX,EAX + * 0042CB5A 74 07 JE SHORT .0042CB63 + * 0042CB5C 8B08 MOV ECX,DWORD PTR DS:[EAX] + * 0042CB5E 8B49 04 MOV ECX,DWORD PTR DS:[ECX+0x4] + * 0042CB61 EB 02 JMP SHORT .0042CB65 + * 0042CB63 33C9 XOR ECX,ECX + * 0042CB65 890A MOV DWORD PTR DS:[EDX],ECX + * 0042CB67 85C0 TEST EAX,EAX + * 0042CB69 74 07 JE SHORT .0042CB72 + * 0042CB6B 8B08 MOV ECX,DWORD PTR DS:[EAX] + * 0042CB6D 8B49 08 MOV ECX,DWORD PTR DS:[ECX+0x8] + * 0042CB70 EB 02 JMP SHORT .0042CB74 + * 0042CB72 33C9 XOR ECX,ECX + * 0042CB74 894A 04 MOV DWORD PTR DS:[EDX+0x4],ECX + * 0042CB77 85C0 TEST EAX,EAX + * 0042CB79 74 08 JE SHORT .0042CB83 + * 0042CB7B 8B08 MOV ECX,DWORD PTR DS:[EAX] + * 0042CB7D 0FB749 0E MOVZX ECX,WORD PTR DS:[ECX+0xE] + * 0042CB81 EB 02 JMP SHORT .0042CB85 + * 0042CB83 33C9 XOR ECX,ECX + * 0042CB85 0FB7C9 MOVZX ECX,CX + * 0042CB88 894A 08 MOV DWORD PTR DS:[EDX+0x8],ECX + * 0042CB8B 85C0 TEST EAX,EAX + * 0042CB8D 74 19 JE SHORT .0042CBA8 + * 0042CB8F 8B08 MOV ECX,DWORD PTR DS:[EAX] + * 0042CB91 8379 04 00 CMP DWORD PTR DS:[ECX+0x4],0x0 + * 0042CB95 76 11 JBE SHORT .0042CBA8 + * 0042CB97 8B49 08 MOV ECX,DWORD PTR DS:[ECX+0x8] + * 0042CB9A 85C9 TEST ECX,ECX + * 0042CB9C 76 0A JBE SHORT .0042CBA8 + * 0042CB9E 49 DEC ECX + * 0042CB9F 0FAF48 0C IMUL ECX,DWORD PTR DS:[EAX+0xC] + * 0042CBA3 0348 04 ADD ECX,DWORD PTR DS:[EAX+0x4] + * 0042CBA6 EB 02 JMP SHORT .0042CBAA + * 0042CBA8 33C9 XOR ECX,ECX + * 0042CBAA 894A 0C MOV DWORD PTR DS:[EDX+0xC],ECX + * 0042CBAD 85C0 TEST EAX,EAX + * 0042CBAF 74 16 JE SHORT .0042CBC7 + * 0042CBB1 8B48 0C MOV ECX,DWORD PTR DS:[EAX+0xC] + * 0042CBB4 F7D9 NEG ECX + * 0042CBB6 894A 10 MOV DWORD PTR DS:[EDX+0x10],ECX + * 0042CBB9 8B00 MOV EAX,DWORD PTR DS:[EAX] + * 0042CBBB 83C0 28 ADD EAX,0x28 + * 0042CBBE 8942 14 MOV DWORD PTR DS:[EDX+0x14],EAX + * 0042CBC1 B8 01000000 MOV EAX,0x1 + * 0042CBC6 C3 RETN + * 0042CBC7 33C9 XOR ECX,ECX + * 0042CBC9 F7D9 NEG ECX + * 0042CBCB 894A 10 MOV DWORD PTR DS:[EDX+0x10],ECX + * 0042CBCE 8B00 MOV EAX,DWORD PTR DS:[EAX] + * 0042CBD0 83C0 28 ADD EAX,0x28 + * 0042CBD3 8942 14 MOV DWORD PTR DS:[EDX+0x14],EAX + * 0042CBD6 B8 01000000 MOV EAX,0x1 + * 0042CBDB C3 RETN + * 0042CBDC CC INT3 + * 0042CBDD CC INT3 + * 0042CBDE CC INT3 + * 0042CBDF CC INT3 + * 0042CBE0 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+0x4] + * 0042CBE4 8B48 10 MOV ECX,DWORD PTR DS:[EAX+0x10] + * 0042CBE7 8B50 0C MOV EDX,DWORD PTR DS:[EAX+0xC] + * 0042CBEA 51 PUSH ECX + * 0042CBEB 8B48 08 MOV ECX,DWORD PTR DS:[EAX+0x8] + * 0042CBEE 52 PUSH EDX + * 0042CBEF 8B50 04 MOV EDX,DWORD PTR DS:[EAX+0x4] + * 0042CBF2 8B00 MOV EAX,DWORD PTR DS:[EAX] + * 0042CBF4 51 PUSH ECX + * 0042CBF5 52 PUSH EDX + * 0042CBF6 50 PUSH EAX + * 0042CBF7 E8 E4FD0A00 CALL .004DC9E0 + * 0042CBFC 83C4 14 ADD ESP,0x14 + * 0042CBFF C3 RETN + * 0042CC00 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+0x4] + * 0042CC04 8B48 10 MOV ECX,DWORD PTR DS:[EAX+0x10] + * 0042CC07 8B50 0C MOV EDX,DWORD PTR DS:[EAX+0xC] + * 0042CC0A 51 PUSH ECX + * 0042CC0B 8B48 08 MOV ECX,DWORD PTR DS:[EAX+0x8] + * 0042CC0E 52 PUSH EDX + * 0042CC0F 8B50 04 MOV EDX,DWORD PTR DS:[EAX+0x4] + * 0042CC12 8B00 MOV EAX,DWORD PTR DS:[EAX] + * 0042CC14 51 PUSH ECX + * 0042CC15 52 PUSH EDX + * 0042CC16 50 PUSH EAX + * 0042CC17 E8 C4FF0A00 CALL .004DCBE0 + * 0042CC1C 83C4 14 ADD ESP,0x14 + * 0042CC1F C3 RETN + * 0042CC20 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+0x4] + * 0042CC24 8B08 MOV ECX,DWORD PTR DS:[EAX] + * 0042CC26 894C24 04 MOV DWORD PTR SS:[ESP+0x4],ECX + * 0042CC2A E9 71FB0A00 JMP .004DC7A0 + * 0042CC2F CC INT3 + * 0042CC30 56 PUSH ESI + * 0042CC31 8B7424 08 MOV ESI,DWORD PTR SS:[ESP+0x8] + * 0042CC35 8B06 MOV EAX,DWORD PTR DS:[ESI] + * 0042CC37 50 PUSH EAX + * 0042CC38 E8 63FB0A00 CALL .004DC7A0 + * 0042CC3D D946 0C FLD DWORD PTR DS:[ESI+0xC] + * 0042CC40 D91C24 FSTP DWORD PTR SS:[ESP] + * 0042CC43 83EC 08 SUB ESP,0x8 + * 0042CC46 D946 08 FLD DWORD PTR DS:[ESI+0x8] + * 0042CC49 D95C24 04 FSTP DWORD PTR SS:[ESP+0x4] + * 0042CC4D D946 04 FLD DWORD PTR DS:[ESI+0x4] + * 0042CC50 D91C24 FSTP DWORD PTR SS:[ESP] + * 0042CC53 50 PUSH EAX + * 0042CC54 E8 27680400 CALL .00473480 + * 0042CC59 83C4 10 ADD ESP,0x10 + * 0042CC5C B8 01000000 MOV EAX,0x1 + * 0042CC61 5E POP ESI + * 0042CC62 C3 RETN + * 0042CC63 CC INT3 + * 0042CC64 CC INT3 + * 0042CC65 CC INT3 + * 0042CC66 CC INT3 + * 0042CC67 CC INT3 + * 0042CC68 CC INT3 + * 0042CC69 CC INT3 * + */ +namespace { // unnamed +/** + * Handle new lines and ruby. + * + * そ�日、彼の言葉に耳を傾ける�ぁ�かった� * ザールラント歴丹�〹� 二ノ月二十日グローセン州 ヘルフォルト区郊� * + * 僁�な霋�の後�r>を開け��r>見覚えのある輪郭が瞳に�り込む� * + * そ�日、彼の言葉に耳を傾ける�ぁ�かった。――尊厳を捨てて媚�る。それが生きることか?――��ぁ�敗北したのた誰しも少年の声を聞かず、蔑み、そして冷笑してぁ�。安寧の世がぁ�までも続くと信じてぁ�から。それでも、私�――。ザールラント歴丹�〹� 二ノ月二十日グローセン州 ヘルフォルト区郊外僅かな霋�の後�r>を開け��r>見覚えのある輪郭が瞳に�り込む + */ +bool EscudeFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + auto text = reinterpret_cast(data); + auto len = reinterpret_cast(size); + StringCharReplacer(text, len, "", 3, '\n'); + + if (cpp_strnstr(text, "", 7); + StringFilterBetween(text, len, "", 2); + } + return true; +} +LPCSTR _escudeltrim(LPCSTR text) +{ + if (text && *text == '<') + for (auto p = text; (signed char)*p > 0; p++) + if (*p == '>') + return p + 1; + return text; +} +void SpecialHookEscude(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD arg1 = argof(1, esp_base); + if (!arg1 || (LONG)arg1 == -1 || ::IsBadWritePtr((LPVOID)arg1, 4)) // this is indispensable + return; + LPCSTR text = (LPCSTR)*(DWORD *)(arg1 + 0x20); + if (!text || ::IsBadWritePtr((LPVOID)text, 1) || !*text) // this is indispensable + return; + text = _escudeltrim(text); + if (!text) + return; + *data = (DWORD)text; + *len = ::strlen(text); + *split = *(DWORD *)arg1; +} +} // unnamed namespace +bool InsertEscudeHook() +{ + const BYTE bytes[] = { + 0x76, 0x0a, // 0042cb9c 76 0a jbe short .0042cba8 + 0x49, // 0042cb9e 49 dec ecx + 0x0f,0xaf,0x48, 0x0c // 0042cb9f 0faf48 0c imul ecx,dword ptr ds:[eax+0xc] + }; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + //GROWL(addr); + if (!addr) { + ConsoleOutput("vnreng:Escude: pattern not found"); + return false; + } + addr = MemDbg::findEnclosingAlignedFunction(addr); + if (!addr) { + ConsoleOutput("vnreng:Escude: enclosing function not found"); + return false; + } + HookParam hp = {}; + hp.address = addr; + hp.text_fun = SpecialHookEscude; + hp.filter_fun = EscudeFilter; + hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; // NO_CONTEXT as this function is only called by one caller anyway + ConsoleOutput("vnreng: INSERT Escude"); + NewHook(hp, "Escude"); + return true; +} + +/** jichi 8/23/2015 Tamamo + * Sample game: 閃光の騎士 ~カリスティアナイト~ Ver1.03 + * + * Debugging method: insert hw breakpoint to the text in memory + * + * 006107A6 76 08 JBE SHORT .006107B0 + * 006107A8 3BF8 CMP EDI,EAX + * 006107AA 0F82 68030000 JB .00610B18 + * 006107B0 0FBA25 F88E7300 01 BT DWORD PTR DS:[0x738EF8],0x1 + * 006107B8 73 07 JNB SHORT .006107C1 + * 006107BA F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] ; jichi: accessed here + * 006107BC E9 17030000 JMP .00610AD8 + * 006107C1 81F9 80000000 CMP ECX,0x80 + * 006107C7 0F82 CE010000 JB .0061099B + * 006107CD 8BC7 MOV EAX,EDI + * 006107CF 33C6 XOR EAX,ESI + * 006107D1 A9 0F000000 TEST EAX,0xF + * 006107D6 75 0E JNZ SHORT .006107E6 + * + * 0012FD7C 0012FE1C + * 0012FD80 00000059 + * 0012FD84 0051C298 RETURN to .0051C298 from .00610790 + * 0012FD88 0207E490 ; jichi: target + * 0012FD8C 0C0BE768 ; jichi: source text + * 0012FD90 00000059 ; jichi: source size + * 0012FD94 002A7C58 + * 0012FD98 0C1E7338 + * 0012FD9C 0012FE1C + * 0012FDA0 /0012FDC0 ; jichi: split + * 0012FDA4 |0056A83F RETURN to .0056A83F from .0051C1C0 + * 0012FDA8 |0C1E733C + * 0012FDAC |00000000 + * 0012FDB0 |FFFFFFFF + * 0012FDB4 |020EDAD0 + * 0012FDB8 |0220CC28 + * 0012FDBC |020EDAD0 + * 0012FDC0 ]0012FE44 + * 0012FDC4 |0055EF84 RETURN to .0055EF84 from .0056A7B0 + * 0012FDC8 |0012FE1C + * 0012FDCC |ED1BC1C5 + * 0012FDD0 |020EDAD0 + * 0012FDD4 |002998A8 + * 0012FDD8 |020EDAD0 + * + * Hooked call: + * 0051C283 5D POP EBP + * 0051C284 C2 0C00 RETN 0xC + * 0051C287 8BD6 MOV EDX,ESI + * 0051C289 85FF TEST EDI,EDI + * 0051C28B 74 0E JE SHORT .0051C29B + * 0051C28D 57 PUSH EDI + * 0051C28E 8D040B LEA EAX,DWORD PTR DS:[EBX+ECX] + * 0051C291 50 PUSH EAX + * 0051C292 52 PUSH EDX + * 0051C293 E8 F8440F00 CALL .00610790 ; jichi: copy invoked here + * 0051C298 83C4 0C ADD ESP,0xC + * 0051C29B 837E 14 10 CMP DWORD PTR DS:[ESI+0x14],0x10 + * 0051C29F 897E 10 MOV DWORD PTR DS:[ESI+0x10],EDI + * 0051C2A2 72 0F JB SHORT .0051C2B3 + * 0051C2A4 8B06 MOV EAX,DWORD PTR DS:[ESI] + * 0051C2A6 C60438 00 MOV BYTE PTR DS:[EAX+EDI],0x0 + * 0051C2AA 8BC6 MOV EAX,ESI + * 0051C2AC 5F POP EDI + * 0051C2AD 5E POP ESI + * 0051C2AE 5B POP EBX + * 0051C2AF 5D POP EBP + * 0051C2B0 C2 0C00 RETN 0xC + * 0051C2B3 8BC6 MOV EAX,ESI + * + * Sample text with new lines: + * + * 0C0BE748 70 00 69 00 2E 00 64 00 6C 00 6C 00 00 00 6C 00 p.i...d.l.l...l. + * 0C0BE758 00 00 00 00 0F 00 00 00 8B 91 3F 66 00 00 00 88 .......拒?f...・ + * 0C0BE768 83 4E 83 8B 83 67 83 93 81 75 8E 84 82 C9 82 CD クルトン「私には + * 0C0BE778 95 90 91 95 82 AA 82 C2 82 A2 82 C4 82 A2 82 DC 武装がついていま + * 0C0BE788 82 B9 82 F1 82 A9 82 E7 81 41 0D 0A 81 40 8D 55 せんから、.. 攻 + * 0C0BE798 82 DF 82 C4 82 B1 82 E7 82 EA 82 BD 82 E7 82 D0 めてこられたらひ + * 0C0BE7A8 82 C6 82 BD 82 DC 82 E8 82 E0 82 A0 82 E8 82 DC とたまりもありま + * 0C0BE7B8 82 B9 82 F1 81 76 3C 65 3E 00 3E 00 3E 00 00 00 せん」.>.>... + * 0C0BE7C8 9E 91 3F 66 99 82 00 88 83 53 83 8D 81 5B 83 93 梠?f凾.・Sローン + * 0C0BE7D8 8C 5A 81 75 82 D6 82 D6 81 42 95 D4 82 B5 82 C4 兄「へへ。返して + * 0C0BE7E8 82 D9 82 B5 82 AF 82 E8 82 E1 82 C2 82 A2 82 C4 ほしけりゃついて + * 0C0BE7F8 82 AB 82 C8 81 42 83 49 83 8C 82 B3 82 DC 82 CC きな。オレさまの + * + * Sample game: 冒険者の町を作ろう!2 Ver1.01 + * + * 0068028B CC INT3 + * 0068028C CC INT3 + * 0068028D CC INT3 + * 0068028E CC INT3 + * 0068028F CC INT3 + * 00680290 55 PUSH EBP + * 00680291 8BEC MOV EBP,ESP + * 00680293 57 PUSH EDI + * 00680294 56 PUSH ESI + * 00680295 8B75 0C MOV ESI,DWORD PTR SS:[EBP+0xC] + * 00680298 8B4D 10 MOV ECX,DWORD PTR SS:[EBP+0x10] + * 0068029B 8B7D 08 MOV EDI,DWORD PTR SS:[EBP+0x8] + * 0068029E 8BC1 MOV EAX,ECX + * 006802A0 8BD1 MOV EDX,ECX + * 006802A2 03C6 ADD EAX,ESI + * 006802A4 3BFE CMP EDI,ESI + * 006802A6 76 08 JBE SHORT .006802B0 + * 006802A8 3BF8 CMP EDI,EAX + * 006802AA 0F82 A4010000 JB .00680454 + * 006802B0 81F9 00010000 CMP ECX,0x100 + * 006802B6 72 1F JB SHORT .006802D7 + * 006802B8 833D 64FB8C00 00 CMP DWORD PTR DS:[0x8CFB64],0x0 + * 006802BF 74 16 JE SHORT .006802D7 + * 006802C1 57 PUSH EDI + * 006802C2 56 PUSH ESI + * 006802C3 83E7 0F AND EDI,0xF + * 006802C6 83E6 0F AND ESI,0xF + * 006802C9 3BFE CMP EDI,ESI + * 006802CB 5E POP ESI + * 006802CC 5F POP EDI + * 006802CD 75 08 JNZ SHORT .006802D7 + * 006802CF 5E POP ESI + * 006802D0 5F POP EDI + * 006802D1 5D POP EBP + * 006802D2 E9 FC090100 JMP .00690CD3 + * 006802D7 F7C7 03000000 TEST EDI,0x3 + * 006802DD 75 15 JNZ SHORT .006802F4 + * 006802DF C1E9 02 SHR ECX,0x2 + * 006802E2 83E2 03 AND EDX,0x3 + * 006802E5 83F9 08 CMP ECX,0x8 + * 006802E8 72 2A JB SHORT .00680314 + * 006802EA F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] jichi: here + * 006802EC FF2495 04046800 JMP DWORD PTR DS:[EDX*4+0x680404] + * 006802F3 90 NOP + * 006802F4 8BC7 MOV EAX,EDI + * 006802F6 BA 03000000 MOV EDX,0x3 + * 006802FB 83E9 04 SUB ECX,0x4 + * 006802FE 72 0C JB SHORT .0068030C + * 00680300 83E0 03 AND EAX,0x3 + * 00680303 03C8 ADD ECX,EAX + * 00680305 FF2485 18036800 JMP DWORD PTR DS:[EAX*4+0x680318] + * 0068030C FF248D 14046800 JMP DWORD PTR DS:[ECX*4+0x680414] + * 00680313 90 NOP + * 00680314 FF248D 98036800 JMP DWORD PTR DS:[ECX*4+0x680398] + * 0068031B 90 NOP + * 0068031C 2803 SUB BYTE PTR DS:[EBX],AL + * 0068031E 68 00540368 PUSH 0x68035400 + * 00680323 0078 03 ADD BYTE PTR DS:[EAX+0x3],BH + * 00680326 68 0023D18A PUSH 0x8AD12300 + * 0068032B 06 PUSH ES + * 0068032C 8807 MOV BYTE PTR DS:[EDI],AL + * 0068032E 8A46 01 MOV AL,BYTE PTR DS:[ESI+0x1] + * 00680331 8847 01 MOV BYTE PTR DS:[EDI+0x1],AL + * 00680334 8A46 02 MOV AL,BYTE PTR DS:[ESI+0x2] + * + * 0067FA4F 8BC6 MOV EAX,ESI + * 0067FA51 EB 45 JMP SHORT .0067FA98 + * 0067FA53 397D 10 CMP DWORD PTR SS:[EBP+0x10],EDI + * 0067FA56 74 16 JE SHORT .0067FA6E + * 0067FA58 3975 0C CMP DWORD PTR SS:[EBP+0xC],ESI + * 0067FA5B 72 11 JB SHORT .0067FA6E + * 0067FA5D 56 PUSH ESI + * 0067FA5E FF75 10 PUSH DWORD PTR SS:[EBP+0x10] + * 0067FA61 FF75 08 PUSH DWORD PTR SS:[EBP+0x8] + * 0067FA64 E8 27080000 CALL .00680290 ; jichi: copy invoked here + * 0067FA69 83C4 0C ADD ESP,0xC + * 0067FA6C ^EB C1 JMP SHORT .0067FA2F + * 0067FA6E FF75 0C PUSH DWORD PTR SS:[EBP+0xC] + * 0067FA71 57 PUSH EDI + * 0067FA72 FF75 08 PUSH DWORD PTR SS:[EBP+0x8] + * + * 0012FC04 00000059 + * 0012FC08 00000000 + * 0012FC0C /0012FC28 + * 0012FC10 |0067FA69 RETURN to .0067FA69 from .00680290 + * 0012FC14 |072CEF78 ; jichi: target text + * 0012FC18 |07261840 ; jichi: source text + * 0012FC1C |00000059 ; jichi: source size + * 0012FC20 |FFFFFFFE + * 0012FC24 |00000000 + * 0012FC28 ]0012FC40 ; jichi: split + * 0012FC2C |00404E58 RETURN to .00404E58 from .0067FA1F + * 0012FC30 |072CEF78 ; jichi: target text + * 0012FC34 |0000005F ; jichi: target capacity + * 0012FC38 |07261840 ; jichi: source text + * 0012FC3C |00000059 ; jichi: source size + * 0012FC40 ]0012FC58 + * 0012FC44 |00404E38 RETURN to .00404E38 from .00404E40 + * 0012FC48 |072CEF78 + * 0012FC4C |0000005F + * 0012FC50 |07261840 + * 0012FC54 |00000059 + * 0012FC58 ]0012FC78 + * 0012FC5C |00404B06 RETURN to .00404B06 from .00404E20 + * 0012FC60 |072CEF78 + * 0012FC64 |0000005F + * 0012FC68 |07261840 + * 0012FC6C |00000059 + * 0012FC70 |00000000 + * 0012FC74 |0012FD30 + * 0012FC78 ]0012FC98 + * 0012FC7C |004025FE RETURN to .004025FE from .00404AE0 + * 0012FC80 |072CEF78 + * 0012FC84 |0000005F + * 0012FC88 |07261840 + * 0012FC8C |00000059 + * 0012FC90 |0012FD30 + * 0012FC94 |00000059 + * 0012FC98 ]0012FCB0 + * 0012FC9C |0040254B RETURN to .0040254B from .00402560 + * 0012FCA0 |074B6EA4 + * 0012FCA4 |00000000 + * 0012FCA8 |FFFFFFFF + * + * 07261840 83 4A 83 43 81 75 82 A0 82 C6 82 CD 82 B1 82 EA カイ「あとはこれ + * 07261850 82 C9 81 41 91 BA 92 B7 82 CC 83 54 83 43 83 93 に、村長のサイン + * 07261860 82 C6 88 F3 8A D3 82 F0 81 63 81 63 82 C1 82 C6 と印鑑を……っと + * 07261870 81 42 0D 0A 81 40 82 6E 82 6A 81 41 82 AB 82 E5 。.. OK、きょ + * 07261880 82 A4 82 CC 83 66 83 58 83 4E 83 8F 81 5B 83 4E うのデスクワーク + * 07261890 8F 49 97 B9 81 76 3C 65 3E 00 81 76 3C 65 3E 00 終了」.」. + * 072618A0 98 DD 95 48 00 40 00 88 83 4A 83 43 81 75 81 63 俤菱.@.・Jイ「… + * 072618B0 81 63 82 A4 82 F1 81 41 82 BB 82 A4 82 B5 82 E6 …うん、そうしよ + */ +namespace { // unnamed +bool TamamoFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + LPSTR text = (LPSTR)data; + if (::memchr(text, '<', *size)) + StringFilter(text, reinterpret_cast(size), "", 3); + StringFilter(text, reinterpret_cast(size), "\x0d\x0a\x81\x40", 4); // remove \n before space + return true; +} +void SpecialHookTamamo(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + auto text = (LPCSTR)argof(2 - 1, esp_base); // arg2 + auto size = argof(3 - 1, esp_base); // arg3 + size = ::strlen(text); + if (0 < size && size < VNR_TEXT_CAPACITY && size == ::strlen(text) && !all_ascii(text)) { + *data = (DWORD)text; + //*len = argof(esp_base, 3 - 1); + *len = size; + //*split = argof(8 - 1, esp_base); // use parent return address as split + //*split = argof(7 - 1, esp_base); // use the address just before parent retaddr + *split = argof(6 - 1, esp_base); + //if (hp.split) + // *split = *(DWORD *)(esp_base + hp.split); + } +} +} // unnamed namespace +bool InsertTamamoHook() +{ + ULONG addr = 0; + { // for new games + const BYTE bytes[] = { + 0x8b,0xd6, // 0051c287 8bd6 mov edx,esi + 0x85,0xff, // 0051c289 85ff test edi,edi + 0x74, 0x0e, // 0051c28b 74 0e je short .0051c29b + 0x57, // 0051c28d 57 push edi + 0x8d,0x04,0x0b, // 0051c28e 8d040b lea eax,dword ptr ds:[ebx+ecx] + 0x50, // 0051c291 50 push eax + 0x52, // 0051c292 52 push edx + 0xe8 //f8440f00 // 0051c293 e8 f8440f00 call .00610790 ; jichi: copy invoked here + }; + enum { addr_offset = sizeof(bytes) - 1 }; + addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + if (addr) { + addr += addr_offset; + ConsoleOutput("vnreng:Tamamo: pattern for new version found"); + } + } + if (!addr) { // for old games + const BYTE bytes[] = { + 0x72, 0x11, // 0067fa5b 72 11 jb short .0067fa6e + 0x56, // 0067fa5d 56 push esi + 0xff,0x75, 0x10, // 0067fa5e ff75 10 push dword ptr ss:[ebp+0x10] + 0xff,0x75, 0x08, // 0067fa61 ff75 08 push dword ptr ss:[ebp+0x8] + 0xe8 // 27080000 // 0067fa64 e8 27080000 call .00680290 ; jichi: copy invoked here + }; + enum { addr_offset = sizeof(bytes) - 1 }; + addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + if (addr) { + addr += addr_offset; + ConsoleOutput("vnreng:Tamamo: pattern for old version found"); + } + } + if (!addr) { + ConsoleOutput("vnreng:Tamamo: pattern not found"); + return false; + } + HookParam hp = {}; + hp.address = addr; + hp.text_fun = SpecialHookTamamo; + hp.filter_fun = TamamoFilter; + hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; + ConsoleOutput("vnreng: INSERT Tamamo"); + NewHook(hp, "Tamamo"); + return true; +} + +/** Game-specific engines */ + +//static char* ShinyDaysQueueString[0x10]; +//static int ShinyDaysQueueStringLen[0x10]; +//static int ShinyDaysQueueIndex, ShinyDaysQueueNext; +static void SpecialGameHookShinyDays(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + static int ShinyDaysQueueStringLen; + LPWSTR fun_str; + char *text_str; + DWORD l = 0; + __asm + { + mov eax,esp_base + mov ecx,[eax+0x4C] + mov fun_str,ecx + mov esi,[eax+0x70] + mov edi,[eax+0x74] + add esi,0x3C + cmp esi,edi + jae _no_text + mov edx,[esi+0x10] + mov ecx,esi + cmp edx,8 + cmovae ecx,[ecx] + add edx,edx + mov text_str,ecx + mov l,edx +_no_text: + } + if (::memcmp(fun_str, L"[PlayVoice]",0x18) == 0) { + *data = (DWORD)text_buffer; + *len = ShinyDaysQueueStringLen; + } + else if (::memcmp(fun_str, L"[PrintText]",0x18) == 0) { + memcpy(text_buffer, text_str, l); + ShinyDaysQueueStringLen = l; + } +} +bool InsertShinyDaysGameHook() +{ + const BYTE bytes[] = { + 0xff,0x83,0x70,0x03,0x00,0x00,0x33,0xf6, + 0xc6,0x84,0x24,0x90,0x02,0x00,0x00,0x02 + }; + LPVOID addr = (LPVOID)0x42ad94; + if (::memcmp(addr, bytes, sizeof(bytes)) != 0) { + ConsoleOutput("vnreng:ShinyDays: only work for 1.00"); + return false; + } + + HookParam hp = {}; + hp.address = 0x42ad9c; + hp.text_fun = SpecialGameHookShinyDays; + hp.type = USING_UNICODE|USING_STRING|NO_CONTEXT; + ConsoleOutput("vnreng: INSERT ShinyDays"); + NewHook(hp, "ShinyDays 1.00"); + return true; +} + +#if 0 // disabled as lova does not allow module from being modified +/** 7/19/2015: Game engine specific for http://lova.jp + * + * No idea why hooking to this place will crash the game. + * + * Debugging method: + * - Find text in UTF8/UTF16 + * There is one UTF8 matched, and 2 UTF16 + * - Use virtual machine to find where UTF8 is MODIFIED + * It is modified in msvcrt + * - Backtrack the stack to find where text is accessed in main module + * + * Base addr = 05f0000 + * + * 012FF246 C64418 08 00 MOV BYTE PTR DS:[EAX+EBX+0x8],0x0 + * 012FF24B C740 04 01000000 MOV DWORD PTR DS:[EAX+0x4],0x1 + * 012FF252 8918 MOV DWORD PTR DS:[EAX],EBX + * 012FF254 8BF0 MOV ESI,EAX + * 012FF256 8B45 08 MOV EAX,DWORD PTR SS:[EBP+0x8] + * 012FF259 53 PUSH EBX + * 012FF25A 50 PUSH EAX + * 012FF25B 8D4E 08 LEA ECX,DWORD PTR DS:[ESI+0x8] + * 012FF25E 51 PUSH ECX + * 012FF25F E8 CEAE2A00 CALL .015AA132 ; JMP to msvcr100.memcpy, copied here + * 012FF264 8B07 MOV EAX,DWORD PTR DS:[EDI] + * 012FF266 83E0 03 AND EAX,0x3 + * 012FF269 0BF0 OR ESI,EAX + * 012FF26B 83C4 0C ADD ESP,0xC + * 012FF26E 8937 MOV DWORD PTR DS:[EDI],ESI + * 012FF270 8B75 FC MOV ESI,DWORD PTR SS:[EBP-0x4] + */ +bool InsertLovaGameHook() +{ + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng:LOVA: failed to get memory range"); + return false; + } + + const BYTE bytes[] = { + 0xC6,0x44,0x18, 0x08, 0x00, // 012FF246 C64418 08 00 MOV BYTE PTR DS:[EAX+EBX+0x8],0x0 + 0xC7,0x40, 0x04, 0x01,0x00,0x00,0x00, // 012FF24B C740 04 01000000 MOV DWORD PTR DS:[EAX+0x4],0x1 + 0x89,0x18, // 012FF252 8918 MOV DWORD PTR DS:[EAX],EBX + 0x8B,0xF0, // 012FF254 8BF0 MOV ESI,EAX + 0x8B,0x45, 0x08, // 012FF256 8B45 08 MOV EAX,DWORD PTR SS:[EBP+0x8] + 0x53, // 012FF259 53 PUSH EBX + 0x50, // 012FF25A 50 PUSH EAX + 0x8D,0x4E, 0x08, // 012FF25B 8D4E 08 LEA ECX,DWORD PTR DS:[ESI+0x8] + 0x51, // 012FF25E 51 PUSH ECX + 0xE8 //CEAE2A00 // 012FF25F E8 CEAE2A00 CALL .015AA132 ; JMP to msvcr100.memcpy, copied here + }; + enum { addr_offset = sizeof(bytes) - 1 }; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), startAddress, stopAddress); + if (!addr) { + ConsoleOutput("vnreng:LOVA: could not find instruction pattern"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + //hp.text_fun = SpecialGameHookLova; + hp.offset = 4 * 2; // source in arg2 + hp.type = USING_STRING|RELATIVE_SPLIT; + ConsoleOutput("vnreng: INSERT LOVA"); + NewHook(hp, "LOVA"); + return true; +} +#endif // 0 + +/** + * jichi 4/15/2014: Insert Adobe AIR hook + * Sample games: + * 華アワセ 蛟編: /HW-C*0:D8@4D04B5:Adobe AIR.dll + * 華アワセ 姫空木編: /HW-C*0:d8@4E69A7:Adobe AIR.dll + * + * Issue: The game will hang if the hook is injected before loading + * + * /HW-C*0:D8@4D04B5:ADOBE AIR.DLL + * - addr: 5047477 = 0x4d04b5 + * -length_offset: 1 + * - module: 3506957663 = 0xd107ed5f + * - off: 4294967280 = 0xfffffff0 = -0x10 + * - split: 216 = 0xd8 + * - type: 90 = 0x5a + * + * 0f8f0497 |. eb 69 jmp short adobe_ai.0f8f0502 + * 0f8f0499 |> 83c8 ff or eax,0xffffffff + * 0f8f049c |. eb 67 jmp short adobe_ai.0f8f0505 + * 0f8f049e |> 8b7d 0c mov edi,dword ptr ss:[ebp+0xc] + * 0f8f04a1 |. 85ff test edi,edi + * 0f8f04a3 |. 7e 5d jle short adobe_ai.0f8f0502 + * 0f8f04a5 |. 8b55 08 mov edx,dword ptr ss:[ebp+0x8] + * 0f8f04a8 |. b8 80000000 mov eax,0x80 + * 0f8f04ad |. be ff030000 mov esi,0x3ff + * 0f8f04b2 |> 0fb70a /movzx ecx,word ptr ds:[edx] + * 0f8f04b5 |. 8bd8 |mov ebx,eax ; jichi: hook here + * 0f8f04b7 |. 4f |dec edi + * 0f8f04b8 |. 66:3bcb |cmp cx,bx + * 0f8f04bb |. 73 05 |jnb short adobe_ai.0f8f04c2 + * 0f8f04bd |. ff45 fc |inc dword ptr ss:[ebp-0x4] + * 0f8f04c0 |. eb 3a |jmp short adobe_ai.0f8f04fc + * 0f8f04c2 |> bb 00080000 |mov ebx,0x800 + * 0f8f04c7 |. 66:3bcb |cmp cx,bx + * 0f8f04ca |. 73 06 |jnb short adobe_ai.0f8f04d2 + * 0f8f04cc |. 8345 fc 02 |add dword ptr ss:[ebp-0x4],0x2 + * 0f8f04d0 |. eb 2a |jmp short adobe_ai.0f8f04fc + * 0f8f04d2 |> 81c1 00280000 |add ecx,0x2800 + * 0f8f04d8 |. 8bde |mov ebx,esi + * 0f8f04da |. 66:3bcb |cmp cx,bx + * 0f8f04dd |. 77 19 |ja short adobe_ai.0f8f04f8 + * 0f8f04df |. 4f |dec edi + * 0f8f04e0 |.^78 b7 |js short adobe_ai.0f8f0499 + * 0f8f04e2 |. 42 |inc edx + * 0f8f04e3 |. 42 |inc edx + * 0f8f04e4 |. 0fb70a |movzx ecx,word ptr ds:[edx] + * 0f8f04e7 |. 81c1 00240000 |add ecx,0x2400 + * 0f8f04ed |. 66:3bcb |cmp cx,bx + * 0f8f04f0 |. 77 06 |ja short adobe_ai.0f8f04f8 + * 0f8f04f2 |. 8345 fc 04 |add dword ptr ss:[ebp-0x4],0x4 + * 0f8f04f6 |. eb 04 |jmp short adobe_ai.0f8f04fc + * 0f8f04f8 |> 8345 fc 03 |add dword ptr ss:[ebp-0x4],0x3 + * 0f8f04fc |> 42 |inc edx + * 0f8f04fd |. 42 |inc edx + * 0f8f04fe |. 85ff |test edi,edi + * 0f8f0500 |.^7f b0 \jg short adobe_ai.0f8f04b2 + * 0f8f0502 |> 8b45 fc mov eax,dword ptr ss:[ebp-0x4] + * 0f8f0505 |> 5f pop edi + * 0f8f0506 |. 5e pop esi + * 0f8f0507 |. 5b pop ebx + * 0f8f0508 |. c9 leave + * 0f8f0509 \. c3 retn + */ +bool InsertAdobeAirHook() +{ + enum { module = 0xd107ed5f }; // hash of "Adobe AIR.dll" + DWORD base = Util::FindModuleBase(module); + if (!base) { + ConsoleOutput("vnreng:Adobe AIR: module not found"); + return false; + } + + //ULONG startAddress, stopAddress; + //if (!NtInspect::getModuleMemoryRange(L"Adobe AIR.dll", &startAddress, &stopAddress)) { + // ConsoleOutput("vnreng:Adobe AIR: module not found"); + // return false; + //} + + const BYTE bytes[] = { + 0x0f,0xb7,0x0a, // 0f8f04b2 |> 0fb70a /movzx ecx,word ptr ds:[edx] + 0x8b,0xd8, // 0f8f04b5 |. 8bd8 |mov ebx,eax ; jichi: hook here + 0x4f, // 0f8f04b7 |. 4f |dec edi + 0x66,0x3b,0xcb, // 0f8f04b8 |. 66:3bcb |cmp cx,bx + 0x73, 0x05, // 0f8f04bb |. 73 05 |jnb short adobe_ai.0f8f04c2 + 0xff,0x45, 0xfc, // 0f8f04bd |. ff45 fc |inc dword ptr ss:[ebp-0x4] + 0xeb, 0x3a // 0f8f04c0 |. eb 3a |jmp short adobe_ai.0f8f04fc + }; + enum { addr_offset = 0x0f8f04b5 - 0x0f8f04b2 }; // = 3. 0 also works. + enum { range = 0x600000 }; // larger than relative addresses + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), base, base + range); + //GROWL(reladdr); + if (!addr) { + ConsoleOutput("vnreng:Adobe AIR: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr + addr_offset; + //hp.module = module; + hp.length_offset = 1; + hp.offset = -0x10; + hp.split = 0xd8; + //hp.type = USING_SPLIT|MODULE_OFFSET|USING_UNICODE|DATA_INDIRECT; // 0x5a; + hp.type = USING_SPLIT|USING_UNICODE|DATA_INDIRECT; + + ConsoleOutput("vnreng: INSERT Adobe AIR"); + NewHook(hp, "Adobe AIR"); + return true; +} + +/** jichi 10/31/2014 Adobe Flash Player v10 + * + * Sample game: [141031] [ヂ�ンクルベル] 輪舞曲Duo + * + * Debug method: Hex utf16 text, then insert hw breakpoints + * 21:51 3110% hexstr 『何よ utf16 + * 0e30554f8830 + * + * There are also UTF-8 strings in the memory. I could not find a good place to hook + * using hw breakpoints. + * + * There are lots of matches. One is selected. Then, the enclosing function is selected. + * arg1 is the UNICODE text. + * + * Pattern: + * + * 0161293a 8bc6 mov eax,esi + * 0161293c 5e pop esi + * 0161293d c2 0800 retn 0x8 + * + * Function starts + * 01612940 8b4c24 0c mov ecx,dword ptr ss:[esp+0xc] ; jichi: hook here + * 01612944 53 push ebx + * 01612945 55 push ebp + * 01612946 56 push esi + * 01612947 57 push edi + * 01612948 33ff xor edi,edi + * 0161294a 85c9 test ecx,ecx + * 0161294c 0f84 5f010000 je ron2.01612ab1 + * 01612952 397c24 18 cmp dword ptr ss:[esp+0x18],edi + * 01612956 0f8e ba010000 jle ron2.01612b16 + * 0161295c 8b6c24 14 mov ebp,dword ptr ss:[esp+0x14] + * 01612960 be 01000000 mov esi,0x1 + * 01612965 eb 09 jmp short ron2.01612970 + * 01612967 8da424 00000000 lea esp,dword ptr ss:[esp] + * 0161296e 8bff mov edi,edi + * 01612970 0fb755 00 movzx edx,word ptr ss:[ebp] + * 01612974 297424 18 sub dword ptr ss:[esp+0x18],esi + * 01612978 b8 80000000 mov eax,0x80 + * 0161297d 66:3bd0 cmp dx,ax + * 01612980 73 15 jnb short ron2.01612997 + * 01612982 297424 20 sub dword ptr ss:[esp+0x20],esi + * 01612986 0f88 1d010000 js ron2.01612aa9 + * 0161298c 8811 mov byte ptr ds:[ecx],dl + * 0161298e 03ce add ecx,esi + * 01612990 03fe add edi,esi + * 01612992 e9 fd000000 jmp ron2.01612a94 + * 01612997 b8 00080000 mov eax,0x800 + * 0161299c 66:3bd0 cmp dx,ax + * 0161299f 73 2a jnb short ron2.016129cb + * 016129a1 836c24 20 02 sub dword ptr ss:[esp+0x20],0x2 + * 016129a6 0f88 fd000000 js ron2.01612aa9 + * 016129ac 8bc2 mov eax,edx + * 016129ae c1e8 06 shr eax,0x6 + * 016129b1 24 1f and al,0x1f + * 016129b3 0c c0 or al,0xc0 + * 016129b5 8801 mov byte ptr ds:[ecx],al + * 016129b7 80e2 3f and dl,0x3f + * 016129ba 03ce add ecx,esi + * 016129bc 80ca 80 or dl,0x80 + * 016129bf 8811 mov byte ptr ds:[ecx],dl + * 016129c1 03ce add ecx,esi + * 016129c3 83c7 02 add edi,0x2 + * 016129c6 e9 c9000000 jmp ron2.01612a94 + * 016129cb 8d82 00280000 lea eax,dword ptr ds:[edx+0x2800] + * 016129d1 bb ff030000 mov ebx,0x3ff + * 016129d6 66:3bc3 cmp ax,bx + * 016129d9 77 7b ja short ron2.01612a56 + * 016129db 297424 18 sub dword ptr ss:[esp+0x18],esi + * 016129df 0f88 c4000000 js ron2.01612aa9 + * 016129e5 0fb775 02 movzx esi,word ptr ss:[ebp+0x2] + * 016129e9 83c5 02 add ebp,0x2 + * 016129ec 8d86 00240000 lea eax,dword ptr ds:[esi+0x2400] + * 016129f2 66:3bc3 cmp ax,bx + * 016129f5 77 58 ja short ron2.01612a4f + * 016129f7 0fb7d2 movzx edx,dx + * 016129fa 81ea f7d70000 sub edx,0xd7f7 + * 01612a00 0fb7c6 movzx eax,si + * 01612a03 c1e2 0a shl edx,0xa + * 01612a06 03d0 add edx,eax + * 01612a08 836c24 20 04 sub dword ptr ss:[esp+0x20],0x4 + * 01612a0d 0f88 96000000 js ron2.01612aa9 + * 01612a13 8bc2 mov eax,edx + * 01612a15 c1e8 12 shr eax,0x12 + * 01612a18 24 07 and al,0x7 + * 01612a1a 0c f0 or al,0xf0 + * 01612a1c 8801 mov byte ptr ds:[ecx],al + * 01612a1e 8bc2 mov eax,edx + * 01612a20 c1e8 0c shr eax,0xc + * 01612a23 24 3f and al,0x3f + * 01612a25 be 01000000 mov esi,0x1 + * 01612a2a 0c 80 or al,0x80 + * 01612a2c 880431 mov byte ptr ds:[ecx+esi],al + * 01612a2f 03ce add ecx,esi + * 01612a31 8bc2 mov eax,edx + * 01612a33 c1e8 06 shr eax,0x6 + * 01612a36 03ce add ecx,esi + * 01612a38 24 3f and al,0x3f + * 01612a3a 0c 80 or al,0x80 + * 01612a3c 8801 mov byte ptr ds:[ecx],al + * 01612a3e 80e2 3f and dl,0x3f + * 01612a41 03ce add ecx,esi + * 01612a43 80ca 80 or dl,0x80 + * 01612a46 8811 mov byte ptr ds:[ecx],dl + * 01612a48 03ce add ecx,esi + * 01612a4a 83c7 04 add edi,0x4 + * 01612a4d eb 45 jmp short ron2.01612a94 + * 01612a4f be 01000000 mov esi,0x1 + * 01612a54 eb 0b jmp short ron2.01612a61 + * 01612a56 8d82 00240000 lea eax,dword ptr ds:[edx+0x2400] + * 01612a5c 66:3bc3 cmp ax,bx + * 01612a5f 77 05 ja short ron2.01612a66 + * 01612a61 ba fdff0000 mov edx,0xfffd + * 01612a66 836c24 20 03 sub dword ptr ss:[esp+0x20],0x3 + * 01612a6b 78 3c js short ron2.01612aa9 + * 01612a6d 8bc2 mov eax,edx + * 01612a6f c1e8 0c shr eax,0xc + * 01612a72 24 0f and al,0xf + * 01612a74 0c e0 or al,0xe0 + * 01612a76 8801 mov byte ptr ds:[ecx],al + * 01612a78 8bc2 mov eax,edx + * 01612a7a c1e8 06 shr eax,0x6 + * 01612a7d 03ce add ecx,esi + * 01612a7f 24 3f and al,0x3f + * 01612a81 0c 80 or al,0x80 + * 01612a83 8801 mov byte ptr ds:[ecx],al + * 01612a85 80e2 3f and dl,0x3f + * 01612a88 03ce add ecx,esi + * 01612a8a 80ca 80 or dl,0x80 + * 01612a8d 8811 mov byte ptr ds:[ecx],dl + * 01612a8f 03ce add ecx,esi + * 01612a91 83c7 03 add edi,0x3 + * 01612a94 83c5 02 add ebp,0x2 + * 01612a97 837c24 18 00 cmp dword ptr ss:[esp+0x18],0x0 + * 01612a9c ^0f8f cefeffff jg ron2.01612970 + * 01612aa2 8bc7 mov eax,edi + * 01612aa4 5f pop edi + * 01612aa5 5e pop esi + * 01612aa6 5d pop ebp + * 01612aa7 5b pop ebx + * 01612aa8 c3 retn + * 01612aa9 5f pop edi + * 01612aaa 5e pop esi + * 01612aab 5d pop ebp + * 01612aac 83c8 ff or eax,0xffffffff + * 01612aaf 5b pop ebx + * 01612ab0 c3 retn + * 01612ab1 8b4424 18 mov eax,dword ptr ss:[esp+0x18] + * 01612ab5 85c0 test eax,eax + * 01612ab7 7e 5d jle short ron2.01612b16 + * 01612ab9 8b5424 14 mov edx,dword ptr ss:[esp+0x14] + * 01612abd 8d49 00 lea ecx,dword ptr ds:[ecx] + * 01612ac0 0fb70a movzx ecx,word ptr ds:[edx] ; jichi: this is where the text is accessed + * 01612ac3 be 80000000 mov esi,0x80 + * 01612ac8 48 dec eax + * 01612ac9 66:3bce cmp cx,si + * 01612acc 73 03 jnb short ron2.01612ad1 + * 01612ace 47 inc edi + * 01612acf eb 3e jmp short ron2.01612b0f + * 01612ad1 be 00080000 mov esi,0x800 + * 01612ad6 66:3bce cmp cx,si + * 01612ad9 73 05 jnb short ron2.01612ae0 + * 01612adb 83c7 02 add edi,0x2 + * 01612ade eb 2f jmp short ron2.01612b0f + * 01612ae0 81c1 00280000 add ecx,0x2800 + * 01612ae6 be ff030000 mov esi,0x3ff + * 01612aeb 66:3bce cmp cx,si + * 01612aee 77 1c ja short ron2.01612b0c + * 01612af0 83e8 01 sub eax,0x1 + * 01612af3 ^78 b4 js short ron2.01612aa9 + * 01612af5 0fb74a 02 movzx ecx,word ptr ds:[edx+0x2] + * 01612af9 83c2 02 add edx,0x2 + * 01612afc 81c1 00240000 add ecx,0x2400 + * 01612b02 66:3bce cmp cx,si + * 01612b05 77 05 ja short ron2.01612b0c + * 01612b07 83c7 04 add edi,0x4 + * 01612b0a eb 03 jmp short ron2.01612b0f + * 01612b0c 83c7 03 add edi,0x3 + * 01612b0f 83c2 02 add edx,0x2 + * 01612b12 85c0 test eax,eax + * 01612b14 ^7f aa jg short ron2.01612ac0 + * 01612b16 8bc7 mov eax,edi + * 01612b18 5f pop edi + * 01612b19 5e pop esi + * 01612b1a 5d pop ebp + * 01612b1b 5b pop ebx + * 01612b1c c3 retn + * 01612b1d cc int3 + * 01612b1e cc int3 + * 01612b1f cc int3 + * + * Runtime stack: + * 0019e974 0161640e return to Ron2.0161640e from Ron2.01612940 + * 0019e978 1216c180 UNICODE "Dat/Chr/HAL_061.swf" + * 0019e97c 00000013 + * 0019e980 12522838 + * 0019e984 00000013 + * 0019e988 0210da80 + * 0019e98c 0019ecb0 + * 0019e990 0019e9e0 + * 0019e994 0019ea24 + * 0019e998 0019e9cc + * + * Runtime registers: + * EAX 12522838 + * ECX 1216C180 UNICODE "Dat/Chr/HAL_061.swf" + * EDX 0C5E9898 + * EBX 12532838 + * ESP 0019E974 + * EBP 00000013 + * ESI 00000013 + * EDI 0019E9CC + * EIP 01612940 Ron2.01612940 + */ +// Skip ASCII garbage such as: Dat/Chr/HAL_061.swf +static bool AdobeFlashFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + // TODO: Remove [0-9a-zA-Z./]{4,} as garbage + LPCWSTR p = reinterpret_cast(data); + size_t len = *size / 2; + for (size_t i = 0; i < len; i++) + if (p[i] & 0xff00) + return true; + return false; +} +bool InsertAdobeFlash10Hook() +{ + const BYTE bytes[] = { + 0x8b,0x4c,0x24, 0x0c, // 01612940 8b4c24 0c mov ecx,dword ptr ss:[esp+0xc] ; jichi: hook here + 0x53, // 01612944 53 push ebx + 0x55, // 01612945 55 push ebp + 0x56, // 01612946 56 push esi + 0x57, // 01612947 57 push edi + 0x33,0xff, // 01612948 33ff xor edi,edi + 0x85,0xc9, // 0161294a 85c9 test ecx,ecx + 0x0f,0x84 //, 5f010000 // 0161294c 0f84 5f010000 je ron2.01612ab1 + }; + ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), module_base_, module_limit_); + //addr = 0x01612940; + //addr = 0x01612AC0; + if (!addr) { + ConsoleOutput("vnreng:AdobeFlash10: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.offset = 1 * 4; // arg1 + //hp.length_offset = 2 * 4; // arg2 might be the length + hp.type = USING_UNICODE; + hp.filter_fun = AdobeFlashFilter; + ConsoleOutput("vnreng: INSERT Adobe Flash 10"); + NewHook(hp, "Adobe Flash 10"); + + ConsoleOutput("vnreng:AdobeFlash10: disable GDI hooks"); + DisableGDIHooks(); + return true; +} + +/** jichi 12/26/2014 Mono + * Sample game: [141226] ハ�レ�めいと + */ +static void SpecialHookMonoString(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + if (auto p = (MonoString *)argof(1, esp_base)) { + *data = (DWORD)p->chars; + *len = p->length * 2; // for widechar + + auto s = regof(ecx, esp_base); + for (int i = 0; i < 0x10; i++) // traverse pointers until a non-readable address is met + if (s && !::IsBadReadPtr((LPCVOID)s, sizeof(DWORD))) + s = *(DWORD *)s; + else + break; + if (!s) + s = hp->address; + *split = s; + } +} + +bool InsertMonoHooks() +{ + HMODULE h = ::GetModuleHandleA("mono.dll"); + if (!h) + return false; + + bool ret = false; + + // mono_unichar2* mono_string_to_utf16 (MonoString *s); + // char* mono_string_to_utf8 (MonoString *s); + HookParam hp = {}; + const MonoFunction funcs[] = { MONO_FUNCTIONS_INITIALIZER }; + enum { FunctionCount = sizeof(funcs) / sizeof(*funcs) }; + for (int i = 0; i < FunctionCount; i++) { + const auto &it = funcs[i]; + if (FARPROC addr = ::GetProcAddress(h, it.functionName)) { + hp.address = (DWORD)addr; + hp.type = it.hookType|NO_ASCII; // 11/27/2015: Disable ascii string + hp.offset = it.textIndex * 4; + hp.length_offset = it.lengthIndex * 4; + hp.text_fun = (HookParam::text_fun_t)it.text_fun; + ConsoleOutput("vnreng: Mono: INSERT"); + NewHook(hp, it.functionName); + ret = true; + } + } + + if (!ret) + ConsoleOutput("vnreng: Mono: failed to find function address"); + return ret; +} + +/** jichi 7/20/2014 Dolphin + * Tested with Dolphin 4.0 + */ +bool InsertGCHooks() +{ + // TODO: Add generic hooks + return InsertVanillawareGCHook(); + //return false; +} + +/** jichi 7/20/2014 Vanillaware + * Tested game: 朧村正 + * + * Debugging method: grep the saving message + * + * 1609415e cc int3 + * 1609415f cc int3 + * 16094160 77 0f ja short 16094171 + * 16094162 c705 00fb6701 80>mov dword ptr ds:[0x167fb00],0x80216b80 + * 1609416c -e9 f9be06f1 jmp 0710006a + * 16094171 8b35 8cf86701 mov esi,dword ptr ds:[0x167f88c] + * 16094177 81c6 ffffffff add esi,-0x1 + * 1609417d 8bce mov ecx,esi + * 1609417f 81c1 01000000 add ecx,0x1 + * 16094185 f7c1 0000000c test ecx,0xc000000 + * 1609418b 74 0b je short 16094198 + * 1609418d 51 push ecx + * 1609418e e8 36bff9f2 call 090300c9 + * 16094193 83c4 04 add esp,0x4 + * 16094196 eb 11 jmp short 160941a9 + * 16094198 8bc1 mov eax,ecx + * 1609419a 81e0 ffffff3f and eax,0x3fffffff + * 160941a0 0fb680 00000810 movzx eax,byte ptr ds:[eax+0x10080000] ; jichi: hook here + * 160941a7 66:90 nop + * 160941a9 81c6 01000000 add esi,0x1 + * 160941af 8905 80f86701 mov dword ptr ds:[0x167f880],eax + * 160941b5 813d 80f86701 00>cmp dword ptr ds:[0x167f880],0x0 + * 160941bf c705 8cf86701 00>mov dword ptr ds:[0x167f88c],0x0 + * 160941c9 8935 90f86701 mov dword ptr ds:[0x167f890],esi + * 160941cf 7c 14 jl short 160941e5 + * 160941d1 7f 09 jg short 160941dc + * 160941d3 c605 0cfb6701 02 mov byte ptr ds:[0x167fb0c],0x2 + * 160941da eb 26 jmp short 16094202 + * 160941dc c605 0cfb6701 04 mov byte ptr ds:[0x167fb0c],0x4 + * 160941e3 eb 07 jmp short 160941ec + * 160941e5 c605 0cfb6701 08 mov byte ptr ds:[0x167fb0c],0x8 + * 160941ec 832d 7c4cb101 06 sub dword ptr ds:[0x1b14c7c],0x6 + * 160941f3 e9 20000000 jmp 16094218 + * 160941f8 0188 6b2180e9 add dword ptr ds:[eax+0xe980216b],ecx + * 160941fe 0e push cs + * 160941ff be 06f1832d mov esi,0x2d83f106 + * 16094204 7c 4c jl short 16094252 + * 16094206 b1 01 mov cl,0x1 + * 16094208 06 push es + * 16094209 e9 c2000000 jmp 160942d0 + * 1609420e 0198 6b2180e9 add dword ptr ds:[eax+0xe980216b],ebx + * 16094214 f8 clc + * 16094215 bd 06f1770f mov ebp,0xf77f106 + * 1609421a c705 00fb6701 88>mov dword ptr ds:[0x167fb00],0x80216b88 + * 16094224 -e9 41be06f1 jmp 0710006a + * 16094229 8b0d 90f86701 mov ecx,dword ptr ds:[0x167f890] + * 1609422f 81c1 01000000 add ecx,0x1 + * 16094235 f7c1 0000000c test ecx,0xc000000 + * 1609423b 74 0b je short 16094248 + * 1609423d 51 push ecx + * 1609423e e8 86bef9f2 call 090300c9 + * 16094243 83c4 04 add esp,0x4 + * 16094246 eb 11 jmp short 16094259 + * 16094248 8bc1 mov eax,ecx + * 1609424a 81e0 ffffff3f and eax,0x3fffffff + * 16094250 0fb680 00000810 movzx eax,byte ptr ds:[eax+0x10080000] + * 16094257 66:90 nop + * 16094259 8b35 90f86701 mov esi,dword ptr ds:[0x167f890] + * 1609425f 81c6 01000000 add esi,0x1 + * 16094265 8905 80f86701 mov dword ptr ds:[0x167f880],eax + * 1609426b 8105 8cf86701 01>add dword ptr ds:[0x167f88c],0x1 + * 16094275 813d 80f86701 00>cmp dword ptr ds:[0x167f880],0x0 + * 1609427f 8935 90f86701 mov dword ptr ds:[0x167f890],esi + * 16094285 7c 14 jl short 1609429b + * 16094287 7f 09 jg short 16094292 + * 16094289 c605 0cfb6701 02 mov byte ptr ds:[0x167fb0c],0x2 + * 16094290 eb 26 jmp short 160942b8 + * 16094292 c605 0cfb6701 04 mov byte ptr ds:[0x167fb0c],0x4 + * 16094299 eb 07 jmp short 160942a2 + * 1609429b c605 0cfb6701 08 mov byte ptr ds:[0x167fb0c],0x8 + * 160942a2 832d 7c4cb101 04 sub dword ptr ds:[0x1b14c7c],0x4 + * 160942a9 ^e9 6affffff jmp 16094218 + * 160942ae 0188 6b2180e9 add dword ptr ds:[eax+0xe980216b],ecx + * 160942b4 58 pop eax + * 160942b5 bd 06f1832d mov ebp,0x2d83f106 + * 160942ba 7c 4c jl short 16094308 + * 160942bc b1 01 mov cl,0x1 + * 160942be 04 e9 add al,0xe9 + * 160942c0 0c 00 or al,0x0 + * 160942c2 0000 add byte ptr ds:[eax],al + * 160942c4 0198 6b2180e9 add dword ptr ds:[eax+0xe980216b],ebx + * 160942ca 42 inc edx + * 160942cb bd 06f1cccc mov ebp,0xccccf106 + * 160942d0 77 0f ja short 160942e1 + * 160942d2 c705 00fb6701 98>mov dword ptr ds:[0x167fb00],0x80216b98 + * 160942dc -e9 89bd06f1 jmp 0710006a + * 160942e1 8b05 84fb6701 mov eax,dword ptr ds:[0x167fb84] + * 160942e7 81e0 fcffffff and eax,0xfffffffc + * 160942ed 8905 00fb6701 mov dword ptr ds:[0x167fb00],eax + * 160942f3 832d 7c4cb101 01 sub dword ptr ds:[0x1b14c7c],0x1 + * 160942fa -e9 11bd06f1 jmp 07100010 + * 160942ff 832d 7c4cb101 01 sub dword ptr ds:[0x1b14c7c],0x1 + * 16094306 ^e9 91f8ffff jmp 16093b9c + * 1609430b cc int3 + */ +namespace { // unnamed + +// Return true if the text is a garbage character +inline bool _vanillawaregarbage_ch(char c) +{ + return c == ' ' || c == '.' || c == '/' + || c >= '0' && c <= '9' + || c >= 'A' && c <= 'z' // also ignore ASCII 91-96: [ \ ] ^ _ ` + ; +} + +// Return true if the text is full of garbage characters +bool _vanillawaregarbage(LPCSTR p) +{ + enum { MAX_LENGTH = VNR_TEXT_CAPACITY }; + for (int count = 0; *p && count < MAX_LENGTH; count++, p++) + if (!_vanillawaregarbage_ch(*p)) + return false; + return true; +} +} // unnamed namespace + +static void SpecialGCHookVanillaware(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD eax = regof(eax, esp_base); + LPCSTR text = LPCSTR(eax + hp->user_value); + static LPCSTR lasttext; + if (lasttext != text && *text && !_vanillawaregarbage(text)) { + lasttext = text; + *data = (DWORD)text; + *len = ::strlen(text); // SHIFT-JIS + *split = regof(ecx, esp_base); + //*split = FIXED_SPLIT_VALUE; + } +} + +bool InsertVanillawareGCHook() +{ + ConsoleOutput("vnreng: Vanillaware GC: enter"); + + const BYTE bytes[] = { + 0x83,0xc4, 0x04, // 16094193 83c4 04 add esp,0x4 + 0xeb, 0x11, // 16094196 eb 11 jmp short 160941a9 + 0x8b,0xc1, // 16094198 8bc1 mov eax,ecx + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1609419a 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0x80, XX4, // 160941a0 0fb680 00000810 movzx eax,byte ptr ds:[eax+0x10080000] ; jichi: hook here + 0x66,0x90, // 160941a7 66:90 nop + 0x81,0xc6, 0x01,0x00,0x00,0x00 // 160941a9 81c6 01000000 add esi,0x1 + //0x89,05 80f86701 // 160941af 8905 80f86701 mov dword ptr ds:[0x167f880],eax + //0x81,3d 80f86701 00 // 160941b5 813d 80f86701 00>cmp dword ptr ds:[0x167f880],0x0 + //0xc7,05 8cf86701 00 // 160941bf c705 8cf86701 00>mov dword ptr ds:[0x167f88c],0x0 + //0x89,35 90f86701 // 160941c9 8935 90f86701 mov dword ptr ds:[0x167f890],esi + //0x7c, 14 // 160941cf 7c 14 jl short 160941e5 + //0x7f, 09 // 160941d1 7f 09 jg short 160941dc + //0xc6,05 0cfb6701 02 // 160941d3 c605 0cfb6701 02 mov byte ptr ds:[0x167fb0c],0x2 + //0xeb, 26 // 160941da eb 26 jmp short 16094202 + }; + enum { memory_offset = 3 }; // 160941a0 0fb680 00000810 movzx eax,byte ptr ds:[eax+0x10080000] + enum { addr_offset = 0x160941a0 - 0x16094193 }; + + DWORD addr = SafeMatchBytesInGCMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: Vanillaware GC: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.text_fun = SpecialGCHookVanillaware; + hp.type = USING_STRING|NO_CONTEXT; // no context is needed to get rid of variant retaddr + ConsoleOutput("vnreng: Vanillaware GC: INSERT"); + NewHook(hp, "Vanillaware GC"); + } + + ConsoleOutput("vnreng: Vanillaware GC: leave"); + return addr; +} + +/** jichi 7/12/2014 PPSSPP + * Tested with PPSSPP 0.9.8. + */ +void SpecialPSPHook(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD offset = *(DWORD *)(esp_base + hp->offset); + LPCSTR text = LPCSTR(offset + hp->user_value); + static LPCSTR lasttext; + if (*text) { + if (hp->user_flags & HPF_IgnoreSameAddress) { + if (text == lasttext) + return; + lasttext = text; + } + *data = (DWORD)text; + // I only considered SHIFT-JIS/UTF-8 case + if (hp->length_offset == 1) + *len = 1; // only read 1 byte + else if (hp->length_offset) + *len = *(DWORD *)(esp_base + hp->length_offset); + else + *len = ::strlen(text); // should only be applied to hp->type|USING_STRING + if (hp->type & USING_SPLIT) { + if (hp->type & FIXING_SPLIT) + *split = FIXED_SPLIT_VALUE; + else + *split = *(DWORD *)(esp_base + hp->split); + } + } +} + +bool InsertPPSSPPHLEHooks() +{ + ConsoleOutput("vnreng: PPSSPP HLE: enter"); + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng:PPSSPP HLE: failed to get memory range"); + return false; + } + + // 0x400000 - 0x139f000 + //GROWL_DWORD2(startAddress, stopAddress); + + HookParam hp = {}; + hp.length_offset = 1; // determine string length at runtime + + const PPSSPPFunction funcs[] = { PPSSPP_FUNCTIONS_INITIALIZER }; + enum { FunctionCount = sizeof(funcs) / sizeof(*funcs) }; + for (size_t i = 0; i < FunctionCount; i++) { + const auto &it = funcs[i]; + ULONG addr = MemDbg::findBytes(it.pattern, ::strlen(it.pattern), startAddress, stopAddress); + if (addr + && (addr = MemDbg::findPushAddress(addr, startAddress, stopAddress)) + && (addr = SafeFindEnclosingAlignedFunction(addr, 0x200)) // range = 0x200, use the safe version or it might raise + ) { + hp.address = addr; + hp.type = it.hookType; + hp.offset = 4 * it.argIndex; + hp.split = it.hookSplit; + if (hp.split) + hp.type |= USING_SPLIT; + NewHook(hp, it.hookName); + } + if (addr) + ConsoleOutput("vnreng: PPSSPP HLE: found pattern"); + else + ConsoleOutput("vnreng: PPSSPP HLE: not found pattern"); + //ConsoleOutput(it.hookName); // wchar_t not supported + ConsoleOutput(it.pattern); + } + ConsoleOutput("vnreng: PPSSPP HLE: leave"); + return true; +} + +bool InsertPPSSPPHooks() +{ + //if (PPSSPP_VERSION[1] == 9 && (PPSSPP_VERSION[2] > 9 || PPSSPP_VERSION[2] == 9 && PPSSPP_VERSION[3] >= 1)) // >= 0.9.9.1 + + ConsoleOutput("vnreng: PPSSPP: enter"); + + if (!WinVersion::queryFileVersion(process_path_, PPSSPP_VERSION)) + ConsoleOutput("vnreng: failed to get PPSSPP version"); + + InsertPPSSPPHLEHooks(); + + if (PPSSPP_VERSION[1] == 9 && PPSSPP_VERSION[2] == 9 && PPSSPP_VERSION[3] == 0) // 0.9.9.0 + InsertOtomatePPSSPPHook(); + + //bool engineFound = false; + Insert5pbPSPHook(); + InsertCyberfrontPSPHook(); + InsertImageepoch2PSPHook(); + InsertFelistellaPSPHook(); + + InsertBroccoliPSPHook(); + InsertIntensePSPHook(); + //InsertKadokawaNamePSPHook(); // disabled + InsertKonamiPSPHook(); + + if (PPSSPP_VERSION[1] == 9 && PPSSPP_VERSION[2] == 8) { // only works for 0.9.8 anyway + InsertNippon1PSPHook(); + InsertNippon2PSPHook(); // This could crash PPSSPP 099 just like 5pb + } + + //InsertTecmoPSPHook(); + + // Generic hooks + + bool bandaiFound = InsertBandaiPSPHook(); + InsertBandaiNamePSPHook(); + + // Hooks whose pattern is not generic enouph + + InsertYetiPSPHook(); + InsertYeti2PSPHook(); + + InsertAlchemistPSPHook(); + InsertAlchemist2PSPHook(); + + //InsertTypeMoonPSPHook() // otomate is creating too many garbage + //|| InsertOtomatePSPHook(); + InsertOtomatePSPHook(); + + if (!bandaiFound) { + // KID pattern is a subset of BANDAI, and hence MUST NOT be together with BANDAI + // Sample BANDAI game that could be broken by KID: 寮�のサクリファイス + InsertKidPSPHook(); // KID could lose text, could exist in multiple game + + InsertImageepochPSPHook(); // Imageepoch could crash vnrcli for School Rumble PSP + } + + ConsoleOutput("vnreng: PPSSPP: leave"); + return true; +} + +/** 7/13/2014 jichi alchemist-net.co.jp PSP engine, 0.9.8 only, not work on 0.9.9 + * Sample game: your diary+ (moe-ydp.iso) + * The memory address is fixed. + * Note: This pattern seems to be common that not only exists in Alchemist games. + * + * Not work on 0.9.9: Amnesia Crowd + * + * Debug method: simply add hardware break points to the matched memory + * + * PPSSPP 0.9.8, your diary+ + * 134076f2 cc int3 + * 134076f3 cc int3 + * 134076f4 77 0f ja short 13407705 + * 134076f6 c705 a8aa1001 40>mov dword ptr ds:[0x110aaa8],0x8931040 + * 13407700 -e9 ff88f2f3 jmp 07330004 + * 13407705 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + * 1340770b 81e0 ffffff3f and eax,0x3fffffff + * 13407711 0fbeb0 00004007 movsx esi,byte ptr ds:[eax+0x7400000] // jichi: hook here + * 13407718 8b3d 78a71001 mov edi,dword ptr ds:[0x110a778] + * 1340771e 8b2d 7ca71001 mov ebp,dword ptr ds:[0x110a77c] + * 13407724 81c5 01000000 add ebp,0x1 + * 1340772a 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 13407730 81e0 ffffff3f and eax,0x3fffffff + * 13407736 8bd6 mov edx,esi + * 13407738 8890 00004007 mov byte ptr ds:[eax+0x7400000],dl // jichi: alternatively hook here + * 1340773e 8b15 78a71001 mov edx,dword ptr ds:[0x110a778] + * 13407744 81c2 01000000 add edx,0x1 + * 1340774a 8bcd mov ecx,ebp + * 1340774c 8935 88a71001 mov dword ptr ds:[0x110a788],esi + * 13407752 8bf2 mov esi,edx + * 13407754 813d 88a71001 00>cmp dword ptr ds:[0x110a788],0x0 + * 1340775e 893d 70a71001 mov dword ptr ds:[0x110a770],edi + * 13407764 8935 78a71001 mov dword ptr ds:[0x110a778],esi + * 1340776a 890d 7ca71001 mov dword ptr ds:[0x110a77c],ecx + * 13407770 8915 80a71001 mov dword ptr ds:[0x110a780],edx + * 13407776 892d 84a71001 mov dword ptr ds:[0x110a784],ebp + * 1340777c 0f85 16000000 jnz 13407798 + * 13407782 832d c4aa1001 08 sub dword ptr ds:[0x110aac4],0x8 + * 13407789 e9 ce000000 jmp 1340785c + * 1340778e 017c10 93 add dword ptr ds:[eax+edx-0x6d],edi + * 13407792 08e9 or cl,ch + * 13407794 8b88 f2f3832d mov ecx,dword ptr ds:[eax+0x2d83f3f2] + * 1340779a c4aa 100108e9 les ebp,fword ptr ds:[edx+0xe9080110] ; modification of segment register + * 134077a0 0c 00 or al,0x0 + * 134077a2 0000 add byte ptr ds:[eax],al + * 134077a4 0160 10 add dword ptr ds:[eax+0x10],esp + * 134077a7 93 xchg eax,ebx + * 134077a8 08e9 or cl,ch + * 134077aa ^75 88 jnz short 13407734 + * 134077ac f2: prefix repne: ; superfluous prefix + * 134077ad f3: prefix rep: ; superfluous prefix + * 134077ae 90 nop + * 134077af cc int3 + */ + +namespace { // unnamed + +// Return true if the text is a garbage character +inline bool _alchemistgarbage_ch(char c) +{ + return c == '.' || c == '/' + || c == '#' || c == ':' // garbage in alchemist2 hook + || c >= '0' && c <= '9' + || c >= 'A' && c <= 'z' // also ignore ASCII 91-96: [ \ ] ^ _ ` + ; +} + +// Return true if the text is full of garbage characters +bool _alchemistgarbage(LPCSTR p) +{ + enum { MAX_LENGTH = VNR_TEXT_CAPACITY }; + for (int count = 0; *p && count < MAX_LENGTH; count++, p++) + if (!_alchemistgarbage_ch(*p)) + return false; + return true; +} + +// 7/20/2014 jichi: Trim Rejet garbage. Sample game: 月華繚乱ROMANCE +// Such as: #Pos[1,2] +inline bool _rejetgarbage_ch(char c) +{ + return c == '#' || c == ' ' || c == '[' || c == ']' || c == ',' + || c >= 'A' && c <= 'z' // also ignore ASCII 91-96: [ \ ] ^ _ ` + || c >= '0' && c <= '9'; +} + +bool _rejetgarbage(LPCSTR p) +{ + enum { MAX_LENGTH = VNR_TEXT_CAPACITY }; + for (int count = 0; *p && count < MAX_LENGTH; count++, p++) + if (!_rejetgarbage_ch(*p)) + return false; + return true; +} + +// Trim leading garbage +LPCSTR _rejetltrim(LPCSTR p) +{ + enum { MAX_LENGTH = VNR_TEXT_CAPACITY }; + if (p) + for (int count = 0; *p && count < MAX_LENGTH; count++, p++) + if (!_rejetgarbage_ch(*p)) + return p; + return nullptr; +} + +// Trim trailing garbage +size_t _rejetstrlen(LPCSTR text) +{ + if (!text) + return 0; + size_t len = ::strlen(text), + ret = len; + while (len && _rejetgarbage_ch(text[len - 1])) { + len--; + if (text[len] == '#') // in case trim UTF-8 trailing bytes + ret = len; + } + return ret; +} + +} // unnamed namespace + +static void SpecialPSPHookAlchemist(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD eax = regof(eax, esp_base); + LPCSTR text = LPCSTR(eax + hp->user_value); + if (*text && !_alchemistgarbage(text)) { + text = _rejetltrim(text); + *data = (DWORD)text; + *len = _rejetstrlen(text); + *split = regof(ecx, esp_base); + } +} + +bool InsertAlchemistPSPHook() +{ + ConsoleOutput("vnreng: Alchemist PSP: enter"); + const BYTE bytes[] = { + //0xcc, // 134076f2 cc int3 + //0xcc, // 134076f3 cc int3 + 0x77, 0x0f, // 134076f4 77 0f ja short 13407705 + 0xc7,0x05, XX8, // 134076f6 c705 a8aa1001 40>mov dword ptr ds:[0x110aaa8],0x8931040 + 0xe9, XX4, // 13407700 -e9 ff88f2f3 jmp 07330004 + 0x8b,0x05, XX4, // 13407705 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1340770b 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xbe,0xb0, XX4, // 13407711 0fbeb0 00004007 movsx esi,byte ptr ds:[eax+0x7400000] // jichi: hook here + 0x8b,0x3d, XX4, // 13407718 8b3d 78a71001 mov edi,dword ptr ds:[0x110a778] + 0x8b,0x2d, XX4, // 1340771e 8b2d 7ca71001 mov ebp,dword ptr ds:[0x110a77c] + 0x81,0xc5, 0x01,0x00,0x00,0x00, // 13407724 81c5 01000000 add ebp,0x1 + 0x8b,0x05, XX4, // 1340772a 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13407730 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xd6, // 13407736 8bd6 mov edx,esi + 0x88,0x90 //, XX4 // 13407738 8890 00004007 mov byte ptr ds:[eax+0x7400000],dl // jichi: alternatively hook here + }; + enum { memory_offset = 3 }; // 13407711 0fbeb0 00004007 movsx esi,byte ptr ds:[eax+0x7400000] + enum { addr_offset = 0x13407711 - 0x134076f4 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + //GROWL_DWORD(addr); + if (!addr) + ConsoleOutput("vnreng: Alchemist PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.text_fun = SpecialPSPHookAlchemist; + hp.type = USING_STRING|NO_CONTEXT; // no context is needed to get rid of variant retaddr + ConsoleOutput("vnreng: Alchemist PSP: INSERT"); + NewHook(hp, "Alchemist PSP"); + } + + ConsoleOutput("vnreng: Alchemist PSP: leave"); + return addr; +} + +/** 7/20/2014 jichi alchemist-net.co.jp PSP engine, 0.9.8, 0.9.9 + * An alternative alchemist hook for old alchemist games. + * Sample game: のーふぁ�と (No Fate) + * The memory address is fixed. + * + * Also work on 0.9.9 Otoboku PSP + * + * Debug method: simply add hardware break points to the matched memory + * + * Two candidate functions are seems OK. + * + * Instruction pattern: 81e580808080 // and ebp,0x80808080 + * + * 0.9.8 のーふぁ�と + * 13400ef3 90 nop + * 13400ef4 77 0f ja short 13400f05 + * 13400ef6 c705 a8aa1001 d0>mov dword ptr ds:[0x110aaa8],0x889aad0 + * 13400f00 -e9 fff050f0 jmp 03910004 + * 13400f05 8b35 78a71001 mov esi,dword ptr ds:[0x110a778] + * 13400f0b 8bc6 mov eax,esi + * 13400f0d 81e0 ffffff3f and eax,0x3fffffff + * 13400f13 8bb8 00004007 mov edi,dword ptr ds:[eax+0x7400000] ; jichi + * 13400f19 8bef mov ebp,edi + * 13400f1b 81ed 01010101 sub ebp,0x1010101 + * 13400f21 f7d7 not edi + * 13400f23 23ef and ebp,edi + * 13400f25 81e5 80808080 and ebp,0x80808080 + * 13400f2b 81fd 00000000 cmp ebp,0x0 + * 13400f31 c705 78a71001 80>mov dword ptr ds:[0x110a778],0x80808080 + * 13400f3b c705 7ca71001 01>mov dword ptr ds:[0x110a77c],0x1010101 + * 13400f45 8935 80a71001 mov dword ptr ds:[0x110a780],esi + * 13400f4b 892d 88a71001 mov dword ptr ds:[0x110a788],ebp + * 13400f51 0f84 22000000 je 13400f79 + * 13400f57 8b35 80a71001 mov esi,dword ptr ds:[0x110a780] + * 13400f5d 8935 78a71001 mov dword ptr ds:[0x110a778],esi + * 13400f63 832d c4aa1001 0c sub dword ptr ds:[0x110aac4],0xc + * 13400f6a e9 35ba0000 jmp 1340c9a4 + * 13400f6f 0124ab add dword ptr ds:[ebx+ebp*4],esp + * 13400f72 8908 mov dword ptr ds:[eax],ecx + * 13400f74 -e9 aaf050f0 jmp 03910023 + * 13400f79 832d c4aa1001 0c sub dword ptr ds:[0x110aac4],0xc + * 13400f80 e9 0b000000 jmp 13400f90 + * 13400f85 0100 add dword ptr ds:[eax],eax + * 13400f87 ab stos dword ptr es:[edi] + * 13400f88 8908 mov dword ptr ds:[eax],ecx + * 13400f8a -e9 94f050f0 jmp 03910023 + * 13400f8f 90 nop + */ + +static void SpecialPSPHookAlchemist2(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD eax = regof(eax, esp_base); + LPCSTR text = LPCSTR(eax + hp->user_value); + if (*text && !_alchemistgarbage(text)) { + *data = (DWORD)text; + *len = ::strlen(text); + *split = regof(ecx, esp_base); + } +} + +bool InsertAlchemist2PSPHook() +{ + ConsoleOutput("vnreng: Alchemist2 PSP: enter"); + const BYTE bytes[] = { + 0x77, 0x0f, // 13400ef4 77 0f ja short 13400f05 + 0xc7,0x05, XX8, // 13400ef6 c705 a8aa1001 d0>mov dword ptr ds:[0x110aaa8],0x889aad0 + 0xe9, XX4, // 13400f00 -e9 fff050f0 jmp 03910004 + 0x8b,0x35, XX4, // 13400f05 8b35 78a71001 mov esi,dword ptr ds:[0x110a778] + 0x8b,0xc6, // 13400f0b 8bc6 mov eax,esi + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13400f0d 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xb8, XX4, // 13400f13 8bb8 00004007 mov edi,dword ptr ds:[eax+0x7400000] ; jichi: hook here + 0x8b,0xef, // 13400f19 8bef mov ebp,edi + 0x81,0xed, 0x01,0x01,0x01,0x01, // 13400f1b 81ed 01010101 sub ebp,0x1010101 + 0xf7,0xd7, // 13400f21 f7d7 not edi + 0x23,0xef, // 13400f23 23ef and ebp,edi + 0x81,0xe5, 0x80,0x80,0x80,0x80, // 13400f25 81e5 80808080 and ebp,0x80808080 + 0x81,0xfd, 0x00,0x00,0x00,0x00 // 13400f2b 81fd 00000000 cmp ebp,0x0 + }; + enum { memory_offset = 2 }; // 13400f13 8bb8 00004007 mov edi,dword ptr ds:[eax+0x7400000] + enum { addr_offset = 0x13400f13 - 0x13400ef4 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + //GROWL_DWORD(addr); + if (!addr) + ConsoleOutput("vnreng: Alchemist2 PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.text_fun = SpecialPSPHookAlchemist2; + hp.type = USING_STRING|NO_CONTEXT; // no context is needed to get rid of variant retaddr + ConsoleOutput("vnreng: Alchemist2 PSP: INSERT"); + NewHook(hp, "Alchemist2 PSP"); + } + + ConsoleOutput("vnreng: Alchemist2 PSP: leave"); + return addr; +} + +/** 7/13/2014 jichi 5pb.jp PSP engine, 0.9.8, 0.9.9 + * Sample game: STEINS;GATE + * + * FIXME: The current pattern could crash VNR + * + * Note: searching after 0x15000000 would found a wrong address on 0.9.9. + * Hooking to it would crash PPSSPP. + * + * Float memory addresses: two matches + * + * Debug method: precompute memory address and set break points, then navigate to that scene + * + * Attach to this function for wrong game might cause BEX (buffer overflow) exception. + * + * 135752c7 90 nop + * 135752c8 77 0f ja short 135752d9 + * 135752ca c705 a8aa1001 d4>mov dword ptr ds:[0x110aaa8],0x8888ed4 + * 135752d4 -e9 2badf3ef jmp 034b0004 + * 135752d9 8b35 dca71001 mov esi,dword ptr ds:[0x110a7dc] + * 135752df 8d76 a0 lea esi,dword ptr ds:[esi-0x60] + * 135752e2 8b3d e4a71001 mov edi,dword ptr ds:[0x110a7e4] + * 135752e8 8bc6 mov eax,esi + * 135752ea 81e0 ffffff3f and eax,0x3fffffff + * 135752f0 89b8 1c004007 mov dword ptr ds:[eax+0x740001c],edi + * 135752f6 8b2d bca71001 mov ebp,dword ptr ds:[0x110a7bc] + * 135752fc 8bc6 mov eax,esi + * 135752fe 81e0 ffffff3f and eax,0x3fffffff + * 13575304 89a8 18004007 mov dword ptr ds:[eax+0x7400018],ebp + * 1357530a 8b15 b8a71001 mov edx,dword ptr ds:[0x110a7b8] + * 13575310 8bc6 mov eax,esi + * 13575312 81e0 ffffff3f and eax,0x3fffffff + * 13575318 8990 14004007 mov dword ptr ds:[eax+0x7400014],edx + * 1357531e 8b0d b4a71001 mov ecx,dword ptr ds:[0x110a7b4] + * 13575324 8bc6 mov eax,esi + * 13575326 81e0 ffffff3f and eax,0x3fffffff + * 1357532c 8988 10004007 mov dword ptr ds:[eax+0x7400010],ecx + * 13575332 8b3d b0a71001 mov edi,dword ptr ds:[0x110a7b0] + * 13575338 8bc6 mov eax,esi + * 1357533a 81e0 ffffff3f and eax,0x3fffffff + * 13575340 89b8 0c004007 mov dword ptr ds:[eax+0x740000c],edi + * 13575346 8b3d aca71001 mov edi,dword ptr ds:[0x110a7ac] + * 1357534c 8bc6 mov eax,esi + * 1357534e 81e0 ffffff3f and eax,0x3fffffff + * 13575354 89b8 08004007 mov dword ptr ds:[eax+0x7400008],edi + * 1357535a 8b3d a8a71001 mov edi,dword ptr ds:[0x110a7a8] + * 13575360 8bc6 mov eax,esi + * 13575362 81e0 ffffff3f and eax,0x3fffffff + * 13575368 89b8 04004007 mov dword ptr ds:[eax+0x7400004],edi + * 1357536e 8b15 78a71001 mov edx,dword ptr ds:[0x110a778] + * 13575374 8935 dca71001 mov dword ptr ds:[0x110a7dc],esi + * 1357537a 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + * 13575380 81e0 ffffff3f and eax,0x3fffffff + * 13575386 0fbeb0 00004007 movsx esi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + * 1357538d 8935 78a71001 mov dword ptr ds:[0x110a778],esi + * 13575393 8b35 80a71001 mov esi,dword ptr ds:[0x110a780] + * 13575399 8935 b0a71001 mov dword ptr ds:[0x110a7b0],esi + * 1357539f 8b35 84a71001 mov esi,dword ptr ds:[0x110a784] + * 135753a5 8b0d 7ca71001 mov ecx,dword ptr ds:[0x110a77c] + * 135753ab 813d 78a71001 00>cmp dword ptr ds:[0x110a778],0x0 + * 135753b5 c705 a8a71001 00>mov dword ptr ds:[0x110a7a8],0x0 + * 135753bf 8935 aca71001 mov dword ptr ds:[0x110a7ac],esi + * 135753c5 890d b4a71001 mov dword ptr ds:[0x110a7b4],ecx + * 135753cb 8915 b8a71001 mov dword ptr ds:[0x110a7b8],edx + * 135753d1 0f85 16000000 jnz 135753ed + * 135753d7 832d c4aa1001 0f sub dword ptr ds:[0x110aac4],0xf + * 135753de e9 e5010000 jmp 135755c8 + * 135753e3 01f0 add eax,esi + * 135753e5 90 nop + * 135753e6 8808 mov byte ptr ds:[eax],cl + * 135753e8 -e9 36acf3ef jmp 034b0023 + * 135753ed 832d c4aa1001 0f sub dword ptr ds:[0x110aac4],0xf + * 135753f4 e9 0b000000 jmp 13575404 + * 135753f9 0110 add dword ptr ds:[eax],edx + * 135753fb 8f ??? ; unknown command + * 135753fc 8808 mov byte ptr ds:[eax],cl + * 135753fe -e9 20acf3ef jmp 034b0023 + * 13575403 90 nop + * 13575404 77 0f ja short 13575415 + * 13575406 c705 a8aa1001 10>mov dword ptr ds:[0x110aaa8],0x8888f10 + * 13575410 -e9 efabf3ef jmp 034b0004 + * 13575415 8b35 a8a71001 mov esi,dword ptr ds:[0x110a7a8] + * 1357541b 33c0 xor eax,eax + * 1357541d 3b35 b0a71001 cmp esi,dword ptr ds:[0x110a7b0] + * 13575423 0f9cc0 setl al + * 13575426 8bf8 mov edi,eax + * 13575428 81ff 00000000 cmp edi,0x0 + * 1357542e 893d 74a71001 mov dword ptr ds:[0x110a774],edi + * 13575434 0f84 22000000 je 1357545c + * 1357543a 8b35 b4a71001 mov esi,dword ptr ds:[0x110a7b4] + * 13575440 8935 78a71001 mov dword ptr ds:[0x110a778],esi + * 13575446 832d c4aa1001 03 sub dword ptr ds:[0x110aac4],0x3 + * 1357544d c705 a8aa1001 2c>mov dword ptr ds:[0x110aaa8],0x8888f2c + * 13575457 -e9 c7abf3ef jmp 034b0023 + * 1357545c 832d c4aa1001 03 sub dword ptr ds:[0x110aac4],0x3 + * 13575463 e9 0c000000 jmp 13575474 + * 13575468 011c8f add dword ptr ds:[edi+ecx*4],ebx + * 1357546b 8808 mov byte ptr ds:[eax],cl + * 1357546d -e9 b1abf3ef jmp 034b0023 + * 13575472 90 nop + * 13575473 cc int3 + * 13575474 77 0f ja short 13575485 + * 13575476 c705 a8aa1001 1c>mov dword ptr ds:[0x110aaa8],0x8888f1c + * 13575480 -e9 7fabf3ef jmp 034b0004 + * 13575485 8b35 78a71001 mov esi,dword ptr ds:[0x110a778] + * 1357548b 8b05 b8a71001 mov eax,dword ptr ds:[0x110a7b8] + * 13575491 81e0 ffffff3f and eax,0x3fffffff + * 13575497 8bd6 mov edx,esi + * 13575499 8890 00004007 mov byte ptr ds:[eax+0x7400000],dl + * 1357549f 8b3d b4a71001 mov edi,dword ptr ds:[0x110a7b4] + * 135754a5 8d7f 01 lea edi,dword ptr ds:[edi+0x1] + * 135754a8 8b2d b8a71001 mov ebp,dword ptr ds:[0x110a7b8] + * 135754ae 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + * 135754b1 813d 68a71001 00>cmp dword ptr ds:[0x110a768],0x0 + * 135754bb 893d b4a71001 mov dword ptr ds:[0x110a7b4],edi + * 135754c1 892d b8a71001 mov dword ptr ds:[0x110a7b8],ebp + * 135754c7 0f85 16000000 jnz 135754e3 + * 135754cd 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 135754d4 e9 23000000 jmp 135754fc + * 135754d9 01e4 add esp,esp + * 135754db 90 nop + * 135754dc 8808 mov byte ptr ds:[eax],cl + * 135754de -e9 40abf3ef jmp 034b0023 + * 135754e3 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 135754ea c705 a8aa1001 2c>mov dword ptr ds:[0x110aaa8],0x8888f2c + * 135754f4 -e9 2aabf3ef jmp 034b0023 + * 135754f9 90 nop + * 135754fa cc int3 + * 135754fb cc int3 + */ +namespace { // unnamed + +// Characters to ignore: [%0-9A-Z] +inline bool _5pbgarbage_ch(char c) +{ return c == '%' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9'; } + +// Trim leading garbage +LPCSTR _5pbltrim(LPCSTR p) +{ + enum { MAX_LENGTH = VNR_TEXT_CAPACITY }; + if (p) + for (int count = 0; *p && count < MAX_LENGTH; count++, p++) + if (!_5pbgarbage_ch(*p)) + return p; + return nullptr; +} + +// Trim trailing garbage +size_t _5pbstrlen(LPCSTR text) +{ + if (!text) + return 0; + size_t len = ::strlen(text), + ret = len; + while (len && _5pbgarbage_ch(text[len - 1])) { + len--; + if (text[len] == '%') // in case trim UTF-8 trailing bytes + ret = len; + } + return ret; +} + +} // unnamed namespace + +static void SpecialPSPHook5pb(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD eax = regof(eax, esp_base); + LPCSTR text = LPCSTR(eax + hp->user_value); + if (*text) { + text = _5pbltrim(text); + *data = (DWORD)text; + *len = _5pbstrlen(text); + *split = regof(ecx, esp_base); + //*split = FIXED_SPLIT_VALUE; // there is only one thread, no split used + } +} + +bool Insert5pbPSPHook() +{ + ConsoleOutput("vnreng: 5pb PSP: enter"); + + const BYTE bytes[] = { + //0x90, // 135752c7 90 nop + 0x77, 0x0f, // 135752c8 77 0f ja short 135752d9 + 0xc7,0x05, XX8, // 135752ca c705 a8aa1001 d4>mov dword ptr ds:[0x110aaa8],0x8888ed4 + 0xe9, XX4, // 135752d4 -e9 2badf3ef jmp 034b0004 + 0x8b,0x35, XX4, // 135752d9 8b35 dca71001 mov esi,dword ptr ds:[0x110a7dc] + 0x8d,0x76, 0xa0, // 135752df 8d76 a0 lea esi,dword ptr ds:[esi-0x60] + 0x8b,0x3d, XX4, // 135752e2 8b3d e4a71001 mov edi,dword ptr ds:[0x110a7e4] + 0x8b,0xc6, // 135752e8 8bc6 mov eax,esi + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 135752ea 81e0 ffffff3f and eax,0x3fffffff + 0x89,0xb8, XX4, // 135752f0 89b8 1c004007 mov dword ptr ds:[eax+0x740001c],edi + 0x8b,0x2d, XX4, // 135752f6 8b2d bca71001 mov ebp,dword ptr ds:[0x110a7bc] + 0x8b,0xc6, // 135752fc 8bc6 mov eax,esi + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 135752fe 81e0 ffffff3f and eax,0x3fffffff + 0x89,0xa8, XX4, // 13575304 89a8 18004007 mov dword ptr ds:[eax+0x7400018],ebp + 0x8b,0x15, XX4, // 1357530a 8b15 b8a71001 mov edx,dword ptr ds:[0x110a7b8] + 0x8b,0xc6, // 13575310 8bc6 mov eax,esi + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13575312 81e0 ffffff3f and eax,0x3fffffff + 0x89,0x90, XX4, // 13575318 8990 14004007 mov dword ptr ds:[eax+0x7400014],edx + 0x8b,0x0d, XX4, // 1357531e 8b0d b4a71001 mov ecx,dword ptr ds:[0x110a7b4] + 0x8b,0xc6, // 13575324 8bc6 mov eax,esi + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13575326 81e0 ffffff3f and eax,0x3fffffff + 0x89,0x88, XX4, // 1357532c 8988 10004007 mov dword ptr ds:[eax+0x7400010],ecx + 0x8b,0x3d, XX4, // 13575332 8b3d b0a71001 mov edi,dword ptr ds:[0x110a7b0] + 0x8b,0xc6, // 13575338 8bc6 mov eax,esi + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1357533a 81e0 ffffff3f and eax,0x3fffffff + 0x89,0xb8, XX4, // 13575340 89b8 0c004007 mov dword ptr ds:[eax+0x740000c],edi + 0x8b,0x3d, XX4, // 13575346 8b3d aca71001 mov edi,dword ptr ds:[0x110a7ac] + 0x8b,0xc6, // 1357534c 8bc6 mov eax,esi + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1357534e 81e0 ffffff3f and eax,0x3fffffff + 0x89,0xb8, XX4, // 13575354 89b8 08004007 mov dword ptr ds:[eax+0x7400008],edi + 0x8b,0x3d, XX4, // 1357535a 8b3d a8a71001 mov edi,dword ptr ds:[0x110a7a8] + 0x8b,0xc6, // 13575360 8bc6 mov eax,esi + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13575362 81e0 ffffff3f and eax,0x3fffffff + 0x89,0xb8, XX4, // 13575368 89b8 04004007 mov dword ptr ds:[eax+0x7400004],edi + 0x8b,0x15, XX4, // 1357536e 8b15 78a71001 mov edx,dword ptr ds:[0x110a778] + 0x89,0x35, XX4, // 13575374 8935 dca71001 mov dword ptr ds:[0x110a7dc],esi + 0x8b,0x05, XX4, // 1357537a 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13575380 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xbe,0xb0 //, XX4 // 13575386 0fbeb0 00004007 movsx esi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + }; + enum { memory_offset = 3 }; // 13575386 0fbeb0 00004007 movsx esi,byte ptr ds:[eax+0x7400000] + enum { addr_offset = sizeof(bytes) - memory_offset }; + + enum : DWORD { start = MemDbg::MappedMemoryStartAddress }; + DWORD stop = PPSSPP_VERSION[1] == 9 && PPSSPP_VERSION[2] == 8 ? MemDbg::MemoryStopAddress : 0x15000000; + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes), start, stop); + //GROWL_DWORD(addr); + if (!addr) + ConsoleOutput("vnreng: 5pb PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.text_fun = SpecialPSPHook5pb; + hp.type = USING_STRING|NO_CONTEXT; // no context is needed to get rid of variant retaddr + ConsoleOutput("vnreng: 5pb PSP: INSERT"); + NewHook(hp, "5pb PSP"); + } + + ConsoleOutput("vnreng: 5pb PSP: leave"); + return addr; +} + +/** 7/13/2014 jichi imageepoch.co.jp PSP engine, 0.9.8, 0.9.9 + * Sample game: BLACK�OCK SHOOTER + * + * Float memory addresses: two matches, UTF-8 + * + * 7/29/2014: seems to work on 0.9.9 + * + * Debug method: find current sentence, then find next sentence in the memory + * and add break-points + * + * 1346d34b f0:90 lock nop ; lock prefix is not allowed + * 1346d34d cc int3 + * 1346d34e cc int3 + * 1346d34f cc int3 + * 1346d350 77 0f ja short 1346d361 + * 1346d352 c705 a8aa1001 e4>mov dword ptr ds:[0x110aaa8],0x89609e4 + * 1346d35c -e9 a32c27f0 jmp 036e0004 + * 1346d361 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 1346d367 81e0 ffffff3f and eax,0x3fffffff + * 1346d36d 8bb0 00004007 mov esi,dword ptr ds:[eax+0x7400000] ; jichi: or hook here + * 1346d373 8b3d 78a71001 mov edi,dword ptr ds:[0x110a778] + * 1346d379 8bc6 mov eax,esi + * 1346d37b 81e0 ffffff3f and eax,0x3fffffff + * 1346d381 0fb6a8 00004007 movzx ebp,byte ptr ds:[eax+0x7400000] ; jichi: hook here + * 1346d388 8d56 01 lea edx,dword ptr ds:[esi+0x1] + * 1346d38b 8bc5 mov eax,ebp + * 1346d38d 0fbec8 movsx ecx,al + * 1346d390 8935 70a71001 mov dword ptr ds:[0x110a770],esi + * 1346d396 8bf5 mov esi,ebp + * 1346d398 81f9 00000000 cmp ecx,0x0 + * 1346d39e 892d 74a71001 mov dword ptr ds:[0x110a774],ebp + * 1346d3a4 8935 78a71001 mov dword ptr ds:[0x110a778],esi + * 1346d3aa 8915 7ca71001 mov dword ptr ds:[0x110a77c],edx + * 1346d3b0 890d 80a71001 mov dword ptr ds:[0x110a780],ecx + * 1346d3b6 893d 84a71001 mov dword ptr ds:[0x110a784],edi + * 1346d3bc 0f8d 16000000 jge 1346d3d8 + * 1346d3c2 832d c4aa1001 07 sub dword ptr ds:[0x110aac4],0x7 + * 1346d3c9 e9 22000000 jmp 1346d3f0 + * 1346d3ce 010c0a add dword ptr ds:[edx+ecx],ecx + * 1346d3d1 96 xchg eax,esi + * 1346d3d2 08e9 or cl,ch + * 1346d3d4 4b dec ebx + * 1346d3d5 2c 27 sub al,0x27 + * 1346d3d7 f0:832d c4aa1001>lock sub dword ptr ds:[0x110aac4],0x7 ; lock prefix + * 1346d3df e9 bc380000 jmp 13470ca0 + * 1346d3e4 0100 add dword ptr ds:[eax],eax + * 1346d3e6 0a96 08e9352c or dl,byte ptr ds:[esi+0x2c35e908] + * 1346d3ec 27 daa + * 1346d3ed f0:90 lock nop ; lock prefix is not allowed + * 1346d3ef cc int3 + */ +static void SpecialPSPHookImageepoch(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + // 7/25/2014: I tried using uniquemap to eliminate duplication, which does not work + DWORD eax = regof(eax, esp_base); + DWORD text = eax + hp->user_value; + static DWORD lasttext; // Prevent reading the same address multiple times + if (text != lasttext && *(LPCSTR)text) { + *data = lasttext = text; + *len = ::strlen((LPCSTR)text); // UTF-8 is null-terminated + *split = regof(ecx, esp_base); // use ecx = "this" to split? + } +} + +bool InsertImageepochPSPHook() +{ + ConsoleOutput("vnreng: Imageepoch PSP: enter"); + + const BYTE bytes[] = { + //0xcc, // 1346d34f cc int3 + 0x77, 0x0f, // 1346d350 77 0f ja short 1346d361 + 0xc7,0x05, XX8, // 1346d352 c705 a8aa1001 e4>mov dword ptr ds:[0x110aaa8],0x89609e4 + 0xe9, XX4, // 1346d35c -e9 a32c27f0 jmp 036e0004 + 0x8b,0x05, XX4, // 1346d361 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1346d367 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xb0, XX4, // 1346d36d 8bb0 00004007 mov esi,dword ptr ds:[eax+0x7400000] ; jichi: or hook here + 0x8b,0x3d, XX4, // 1346d373 8b3d 78a71001 mov edi,dword ptr ds:[0x110a778] + 0x8b,0xc6, // 1346d379 8bc6 mov eax,esi + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1346d37b 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xa8, XX4, // 1346d381 0fb6a8 00004007 movzx ebp,byte ptr ds:[eax+0x7400000] ; jichi: hook here + 0x8d,0x56, 0x01, // 1346d388 8d56 01 lea edx,dword ptr ds:[esi+0x1] + 0x8b,0xc5, // 1346d38b 8bc5 mov eax,ebp + 0x0f,0xbe,0xc8 // 1346d38d 0fbec8 movsx ecx,al + }; + enum { memory_offset = 3 }; // 1346d381 0fb6a8 00004007 movzx ebp,byte ptr ds:[eax+0x7400000] + enum { addr_offset = 0x1346d381 - 0x1346d350 }; + //enum { addr_offset = sizeof(bytes) - memory_offset }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: Imageepoch PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; // UTF-8, though + hp.offset = pusha_eax_off - 4; + hp.split = pusha_ecx_off - 4; + hp.user_flags = HPF_IgnoreSameAddress; + //hp.text_fun = SpecialPSPHook; + hp.text_fun = SpecialPSPHookImageepoch; // since this function is common, use its own static lasttext for HPF_IgnoreSameAddress + ConsoleOutput("vnreng: Imageepoch PSP: INSERT"); + NewHook(hp, "Imageepoch PSP"); + } + + ConsoleOutput("vnreng: Imageepoch PSP: leave"); + return addr; +} + +/** 8/9/2014 jichi imageepoch.co.jp PSP engine, 0.9.8, 0.9.9 + * Sample game: Sol Trigger (0.9.8, 0.9.9) + * + * Though Imageepoch1 also exists, it cannot find scenario text. + * + * FIXED memory addresses (different from Imageepoch1): two matches, UTF-8 + * + * Debug method: find current text and add breakpoint. + * + * There a couple of good functions. The first one is used. + * There is only one text threads. But it cannot extract character names. + * + * 135fd497 cc int3 + * 135fd498 77 0f ja short 135fd4a9 + * 135fd49a c705 a8aa1001 20>mov dword ptr ds:[0x110aaa8],0x8952d20 + * 135fd4a4 -e9 5b2b2ef0 jmp 038e0004 + * 135fd4a9 8b35 dca71001 mov esi,dword ptr ds:[0x110a7dc] + * 135fd4af 81c6 04000000 add esi,0x4 + * 135fd4b5 8b05 a8a71001 mov eax,dword ptr ds:[0x110a7a8] + * 135fd4bb 81e0 ffffff3f and eax,0x3fffffff + * 135fd4c1 0fb6b8 00004007 movzx edi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + * 135fd4c8 813d 68a71001 00>cmp dword ptr ds:[0x110a768],0x0 + * 135fd4d2 893d 78a71001 mov dword ptr ds:[0x110a778],edi + * 135fd4d8 c705 aca71001 23>mov dword ptr ds:[0x110a7ac],0x23434623 + * 135fd4e2 c705 b0a71001 30>mov dword ptr ds:[0x110a7b0],0x30303030 + * 135fd4ec 8935 b4a71001 mov dword ptr ds:[0x110a7b4],esi + * 135fd4f2 c705 b8a71001 00>mov dword ptr ds:[0x110a7b8],0x0 + * 135fd4fc 0f85 16000000 jnz 135fd518 + * 135fd502 832d c4aa1001 08 sub dword ptr ds:[0x110aac4],0x8 + * 135fd509 e9 22000000 jmp 135fd530 + * 135fd50e 01642d 95 add dword ptr ss:[ebp+ebp-0x6b],esp + * 135fd512 08e9 or cl,ch + * 135fd514 0b2b or ebp,dword ptr ds:[ebx] + * 135fd516 2e:f0:832d c4aa1>lock sub dword ptr cs:[0x110aac4],0x8 ; lock prefix + * 135fd51f c705 a8aa1001 40>mov dword ptr ds:[0x110aaa8],0x8952d40 + * 135fd529 -e9 f52a2ef0 jmp 038e0023 + * 135fd52e 90 nop + * 135fd52f cc int3 + */ +bool InsertImageepoch2PSPHook() +{ + ConsoleOutput("vnreng: Imageepoch2 PSP: enter"); + + const BYTE bytes[] = { + // 135fd497 cc int3 + 0x77, 0x0f, // 135fd498 77 0f ja short 135fd4a9 + 0xc7,0x05, XX8, // 135fd49a c705 a8aa1001 20>mov dword ptr ds:[0x110aaa8],0x8952d20 + 0xe9, XX4, // 135fd4a4 -e9 5b2b2ef0 jmp 038e0004 + 0x8b,0x35, XX4, // 135fd4a9 8b35 dca71001 mov esi,dword ptr ds:[0x110a7dc] + 0x81,0xc6, 0x04,0x00,0x00,0x00, // 135fd4af 81c6 04000000 add esi,0x4 + 0x8b,0x05, XX4, // 135fd4b5 8b05 a8a71001 mov eax,dword ptr ds:[0x110a7a8] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 135fd4bb 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xb8, XX4, // 135fd4c1 0fb6b8 00004007 movzx edi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + 0x81,0x3d, XX4, 0x00,0x00,0x00,0x00, // 135fd4c8 813d 68a71001 00>cmp dword ptr ds:[0x110a768],0x0 + 0x89,0x3d, XX4, // 135fd4d2 893d 78a71001 mov dword ptr ds:[0x110a778],edi + 0xc7,0x05, XX8, // 135fd4d8 c705 aca71001 23>mov dword ptr ds:[0x110a7ac],0x23434623 + 0xc7,0x05, XX8, // 135fd4e2 c705 b0a71001 30>mov dword ptr ds:[0x110a7b0],0x30303030 + 0x89,0x35, XX4, // 135fd4ec 8935 b4a71001 mov dword ptr ds:[0x110a7b4],esi + 0xc7,0x05, XX4, 0x00,0x00,0x00,0x00, // 135fd4f2 c705 b8a71001 00>mov dword ptr ds:[0x110a7b8],0x0 + 0x0f,0x85 //, XX4, // 135fd4fc 0f85 16000000 jnz 135fd518 + }; + enum { memory_offset = 3 }; // 1346d381 0fb6a8 00004007 movzx ebp,byte ptr ds:[eax+0x7400000] + enum { addr_offset = 0x135fd4c1 - 0x135fd498 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: Imageepoch2 PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; // UTF-8, though + hp.offset = pusha_eax_off - 4; + hp.split = pusha_ecx_off - 4; + hp.text_fun = SpecialPSPHook; + ConsoleOutput("vnreng: Imageepoch2 PSP: INSERT"); + NewHook(hp, "Imageepoch2 PSP"); + } + + ConsoleOutput("vnreng: Imageepoch2 PSP: leave"); + return addr; +} + +/** 7/19/2014 jichi yetigame.jp PSP engine, 0.9.8, 0.9.9 + * Sample game: Secret Game Portable 0.9.8/0.9.9 + * + * Float memory addresses: two matches + * + * Debug method: find current sentence, then find next sentence in the memory + * and add break-points. Need to patch 1 leading \u3000 space. + * + * It seems that each time I ran the game, the instruction pattern would change?! + * == The second time I ran the game == + * + * 14e49ed9 90 nop + * 14e49eda cc int3 + * 14e49edb cc int3 + * 14e49edc 77 0f ja short 14e49eed + * 14e49ede c705 a8aa1001 98>mov dword ptr ds:[0x110aaa8],0x885ff98 + * 14e49ee8 -e9 17619eee jmp 03830004 + * 14e49eed 8b35 70a71001 mov esi,dword ptr ds:[0x110a770] + * 14e49ef3 c1ee 1f shr esi,0x1f + * 14e49ef6 8b05 b4a71001 mov eax,dword ptr ds:[0x110a7b4] + * 14e49efc 81e0 ffffff3f and eax,0x3fffffff + * 14e49f02 8bb8 14deff07 mov edi,dword ptr ds:[eax+0x7ffde14] + * 14e49f08 0335 70a71001 add esi,dword ptr ds:[0x110a770] + * 14e49f0e d1fe sar esi,1 + * 14e49f10 8b05 b0a71001 mov eax,dword ptr ds:[0x110a7b0] + * 14e49f16 81e0 ffffff3f and eax,0x3fffffff + * 14e49f1c 89b8 00000008 mov dword ptr ds:[eax+0x8000000],edi + * 14e49f22 8b05 dca71001 mov eax,dword ptr ds:[0x110a7dc] + * 14e49f28 81e0 ffffff3f and eax,0x3fffffff + * 14e49f2e 89b0 30000008 mov dword ptr ds:[eax+0x8000030],esi + * 14e49f34 8b05 b4a71001 mov eax,dword ptr ds:[0x110a7b4] + * 14e49f3a 81e0 ffffff3f and eax,0x3fffffff + * 14e49f40 8ba8 14deff07 mov ebp,dword ptr ds:[eax+0x7ffde14] + * 14e49f46 8bc5 mov eax,ebp + * 14e49f48 81e0 ffffff3f and eax,0x3fffffff + * 14e49f4e 0fb6b0 00000008 movzx esi,byte ptr ds:[eax+0x8000000] ; jichi: hook here + * 14e49f55 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + * 14e49f58 8b05 b4a71001 mov eax,dword ptr ds:[0x110a7b4] + * + * == The first time I ran the game == + * There are a couple of good break-points, as follows. + * Only the second function is hooked. + * + * 138cf7a2 cc int3 + * 138cf7a3 cc int3 + * 138cf7a4 77 0f ja short 138cf7b5 + * 138cf7a6 c705 a8aa1001 90>mov dword ptr ds:[0x110aaa8],0x885ff90 + * 138cf7b0 -e9 4f08a9f3 jmp 07360004 + * 138cf7b5 8b05 b4a71001 mov eax,dword ptr ds:[0x110a7b4] + * 138cf7bb 81e0 ffffff3f and eax,0x3fffffff + * 138cf7c1 8bb0 14de7f07 mov esi,dword ptr ds:[eax+0x77fde14] + * 138cf7c7 8935 78a71001 mov dword ptr ds:[0x110a778],esi + * 138cf7cd c705 e4a71001 98>mov dword ptr ds:[0x110a7e4],0x885ff98 + * 138cf7d7 832d c4aa1001 02 sub dword ptr ds:[0x110aac4],0x2 + * 138cf7de e9 0d000000 jmp 138cf7f0 + * 138cf7e3 015c48 85 add dword ptr ds:[eax+ecx*2-0x7b],ebx + * 138cf7e7 08e9 or cl,ch + * 138cf7e9 36:08a9 f390cccc or byte ptr ss:[ecx+0xcccc90f3],ch + * 138cf7f0 77 0f ja short 138cf801 + * 138cf7f2 c705 a8aa1001 5c>mov dword ptr ds:[0x110aaa8],0x885485c + * 138cf7fc -e9 0308a9f3 jmp 07360004 + * 138cf801 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 138cf807 81e0 ffffff3f and eax,0x3fffffff + * 138cf80d 0fb6b0 00008007 movzx esi,byte ptr ds:[eax+0x7800000] ; jichi: hook here + * 138cf814 81fe 00000000 cmp esi,0x0 + * 138cf81a 8935 74a71001 mov dword ptr ds:[0x110a774],esi + * 138cf820 c705 80a71001 00>mov dword ptr ds:[0x110a780],0x0 + * 138cf82a c705 84a71001 25>mov dword ptr ds:[0x110a784],0x25 + * 138cf834 c705 88a71001 4e>mov dword ptr ds:[0x110a788],0x4e + * 138cf83e c705 8ca71001 6e>mov dword ptr ds:[0x110a78c],0x6e + * 138cf848 0f85 16000000 jnz 138cf864 + * 138cf84e 832d c4aa1001 06 sub dword ptr ds:[0x110aac4],0x6 + * 138cf855 e9 b6010000 jmp 138cfa10 + * 138cf85a 01bc48 8508e9bf add dword ptr ds:[eax+ecx*2+0xbfe90885],> + * 138cf861 07 pop es ; modification of segment register + * 138cf862 a9 f3832dc4 test eax,0xc42d83f3 + * 138cf867 aa stos byte ptr es:[edi] + * 138cf868 1001 adc byte ptr ds:[ecx],al + * 138cf86a 06 push es + * 138cf86b e9 0c000000 jmp 138cf87c + * 138cf870 017448 85 add dword ptr ds:[eax+ecx*2-0x7b],esi + * 138cf874 08e9 or cl,ch + * 138cf876 a9 07a9f390 test eax,0x90f3a907 + * 138cf87b cc int3 + * + * This function is used. + * 138cfa46 cc int3 + * 138cfa47 cc int3 + * 138cfa48 77 0f ja short 138cfa59 + * 138cfa4a c705 a8aa1001 98>mov dword ptr ds:[0x110aaa8],0x885ff98 + * 138cfa54 -e9 ab05a9f3 jmp 07360004 + * 138cfa59 8b35 70a71001 mov esi,dword ptr ds:[0x110a770] + * 138cfa5f c1ee 1f shr esi,0x1f + * 138cfa62 8b05 b4a71001 mov eax,dword ptr ds:[0x110a7b4] + * 138cfa68 81e0 ffffff3f and eax,0x3fffffff + * 138cfa6e 8bb8 14de7f07 mov edi,dword ptr ds:[eax+0x77fde14] + * 138cfa74 0335 70a71001 add esi,dword ptr ds:[0x110a770] + * 138cfa7a d1fe sar esi,1 + * 138cfa7c 8b05 b0a71001 mov eax,dword ptr ds:[0x110a7b0] + * 138cfa82 81e0 ffffff3f and eax,0x3fffffff + * 138cfa88 89b8 00008007 mov dword ptr ds:[eax+0x7800000],edi + * 138cfa8e 8b05 dca71001 mov eax,dword ptr ds:[0x110a7dc] + * 138cfa94 81e0 ffffff3f and eax,0x3fffffff + * 138cfa9a 89b0 30008007 mov dword ptr ds:[eax+0x7800030],esi + * 138cfaa0 8b05 b4a71001 mov eax,dword ptr ds:[0x110a7b4] + * 138cfaa6 81e0 ffffff3f and eax,0x3fffffff + * 138cfaac 8ba8 14de7f07 mov ebp,dword ptr ds:[eax+0x77fde14] + * 138cfab2 8bc5 mov eax,ebp + * 138cfab4 81e0 ffffff3f and eax,0x3fffffff + * 138cfaba 0fb6b0 00008007 movzx esi,byte ptr ds:[eax+0x7800000] ; jichi: hook here + * 138cfac1 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + * 138cfac4 8b05 b4a71001 mov eax,dword ptr ds:[0x110a7b4] + * 138cfaca 81e0 ffffff3f and eax,0x3fffffff + * 138cfad0 89a8 14de7f07 mov dword ptr ds:[eax+0x77fde14],ebp + * 138cfad6 81fe 00000000 cmp esi,0x0 + * 138cfadc 892d 70a71001 mov dword ptr ds:[0x110a770],ebp + * 138cfae2 8935 74a71001 mov dword ptr ds:[0x110a774],esi + * 138cfae8 893d aca71001 mov dword ptr ds:[0x110a7ac],edi + * 138cfaee 0f84 16000000 je 138cfb0a + * 138cfaf4 832d c4aa1001 0b sub dword ptr ds:[0x110aac4],0xb + * 138cfafb e9 24000000 jmp 138cfb24 + * 138cfb00 01b0 ff8508e9 add dword ptr ds:[eax+0xe90885ff],esi + * 138cfb06 1905 a9f3832d sbb dword ptr ds:[0x2d83f3a9],eax + * 138cfb0c c4aa 10010be9 les ebp,fword ptr ds:[edx+0xe90b0110] ; modification of segment register + * 138cfb12 9a 00000001 c4ff call far ffc4:01000000 ; far call + * 138cfb19 8508 test dword ptr ds:[eax],ecx + * 138cfb1b -e9 0305a9f3 jmp 07360023 + * 138cfb20 90 nop + * 138cfb21 cc int3 + * 138cfb22 cc int3 + * + * 138cfb22 cc int3 + * 138cfb23 cc int3 + * 138cfb24 77 0f ja short 138cfb35 + * 138cfb26 c705 a8aa1001 b0>mov dword ptr ds:[0x110aaa8],0x885ffb0 + * 138cfb30 -e9 cf04a9f3 jmp 07360004 + * 138cfb35 8b05 b4a71001 mov eax,dword ptr ds:[0x110a7b4] + * 138cfb3b 81e0 ffffff3f and eax,0x3fffffff + * 138cfb41 8bb0 14de7f07 mov esi,dword ptr ds:[eax+0x77fde14] + * 138cfb47 8bc6 mov eax,esi + * 138cfb49 81e0 ffffff3f and eax,0x3fffffff + * 138cfb4f 0fb6b8 00008007 movzx edi,byte ptr ds:[eax+0x7800000] ; jichi: hook here + * 138cfb56 8d76 01 lea esi,dword ptr ds:[esi+0x1] + * 138cfb59 8b05 b4a71001 mov eax,dword ptr ds:[0x110a7b4] + * 138cfb5f 81e0 ffffff3f and eax,0x3fffffff + * 138cfb65 89b0 14de7f07 mov dword ptr ds:[eax+0x77fde14],esi + * 138cfb6b 81ff 00000000 cmp edi,0x0 + * 138cfb71 8935 70a71001 mov dword ptr ds:[0x110a770],esi + * 138cfb77 893d 74a71001 mov dword ptr ds:[0x110a774],edi + * 138cfb7d 0f84 16000000 je 138cfb99 + * 138cfb83 832d c4aa1001 05 sub dword ptr ds:[0x110aac4],0x5 + * 138cfb8a ^e9 95ffffff jmp 138cfb24 + * 138cfb8f 01b0 ff8508e9 add dword ptr ds:[eax+0xe90885ff],esi + * 138cfb95 8a04a9 mov al,byte ptr ds:[ecx+ebp*4] + * 138cfb98 f3: prefix rep: ; superfluous prefix + * 138cfb99 832d c4aa1001 05 sub dword ptr ds:[0x110aac4],0x5 + * 138cfba0 e9 0b000000 jmp 138cfbb0 + * 138cfba5 01c4 add esp,eax + * 138cfba7 ff85 08e97404 inc dword ptr ss:[ebp+0x474e908] + * 138cfbad a9 f390770f test eax,0xf7790f3 + * 138cfbb2 c705 a8aa1001 c4>mov dword ptr ds:[0x110aaa8],0x885ffc4 + * 138cfbbc -e9 4304a9f3 jmp 07360004 + * 138cfbc1 f3:0f1015 6c1609>movss xmm2,dword ptr ds:[0x1009166c] + * 138cfbc9 8b05 b0a71001 mov eax,dword ptr ds:[0x110a7b0] + * 138cfbcf 81e0 ffffff3f and eax,0x3fffffff + * 138cfbd5 8bb0 00008007 mov esi,dword ptr ds:[eax+0x7800000] + * 138cfbdb f3:0f101d 641609>movss xmm3,dword ptr ds:[0x10091664] + * 138cfbe3 c7c7 00000000 mov edi,0x0 + * 138cfbe9 893d f4b12b11 mov dword ptr ds:[0x112bb1f4],edi + * 138cfbef 8bc6 mov eax,esi + * 138cfbf1 81e0 ffffff3f and eax,0x3fffffff + * 138cfbf7 0fb6a8 00008007 movzx ebp,byte ptr ds:[eax+0x7800000] ; jichi: hook here + * 138cfbfe 81fd 00000000 cmp ebp,0x0 + * 138cfc04 c705 70a71001 00>mov dword ptr ds:[0x110a770],0x9ac0000 + * 138cfc0e c705 74a71001 00>mov dword ptr ds:[0x110a774],0x8890000 + * 138cfc18 892d a8a71001 mov dword ptr ds:[0x110a7a8],ebp + * 138cfc1e 8935 aca71001 mov dword ptr ds:[0x110a7ac],esi + * 138cfc24 c705 b4a71001 00>mov dword ptr ds:[0x110a7b4],0x8890000 + * 138cfc2e c705 b8a71001 80>mov dword ptr ds:[0x110a7b8],0x80 + * 138cfc38 c705 bca71001 00>mov dword ptr ds:[0x110a7bc],0x0 + * 138cfc42 c705 e0a71001 00>mov dword ptr ds:[0x110a7e0],0x0 + * 138cfc4c f3:0f111d 3ca810>movss dword ptr ds:[0x110a83c],xmm3 + * 138cfc54 f3:0f1115 40a810>movss dword ptr ds:[0x110a840],xmm2 + * 138cfc5c 0f85 16000000 jnz 138cfc78 + * 138cfc62 832d c4aa1001 0d sub dword ptr ds:[0x110aac4],0xd + * 138cfc69 e9 32270000 jmp 138d23a0 + * 138cfc6e 0158 00 add dword ptr ds:[eax],ebx + * 138cfc71 8608 xchg byte ptr ds:[eax],cl + * 138cfc73 -e9 ab03a9f3 jmp 07360023 + * 138cfc78 832d c4aa1001 0d sub dword ptr ds:[0x110aac4],0xd + * 138cfc7f e9 0c000000 jmp 138cfc90 + * 138cfc84 01f8 add eax,edi + * 138cfc86 ff85 08e99503 inc dword ptr ss:[ebp+0x395e908] + * 138cfc8c a9 f390cc77 test eax,0x77cc90f3 + * 138cfc91 0fc7 ??? ; unknown command + * 138cfc93 05 a8aa1001 add eax,0x110aaa8 + * 138cfc98 f8 clc + * 138cfc99 ff85 08e96303 inc dword ptr ss:[ebp+0x363e908] + * 138cfc9f a9 f38b35ac test eax,0xac358bf3 + * 138cfca4 a7 cmps dword ptr ds:[esi],dword ptr es:[ed> + * 138cfca5 1001 adc byte ptr ds:[ecx],al + * 138cfca7 8b3d b4a71001 mov edi,dword ptr ds:[0x110a7b4] + * 138cfcad 81c7 48d6ffff add edi,-0x29b8 + * 138cfcb3 8935 78a71001 mov dword ptr ds:[0x110a778],esi + * 138cfcb9 893d 7ca71001 mov dword ptr ds:[0x110a77c],edi + * 138cfcbf c705 80a71001 02>mov dword ptr ds:[0x110a780],0x2 + * 138cfcc9 c705 e4a71001 08>mov dword ptr ds:[0x110a7e4],0x8860008 + * 138cfcd3 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 138cfcda ^e9 4914f4ff jmp 13811128 + * 138cfcdf 90 nop + * 138cfce0 77 0f ja short 138cfcf1 + * 138cfce2 c705 a8aa1001 74>mov dword ptr ds:[0x110aaa8],0x8844574 + * 138cfcec -e9 1303a9f3 jmp 07360004 + * 138cfcf1 8b35 84a71001 mov esi,dword ptr ds:[0x110a784] + * 138cfcf7 81c6 ffffffff add esi,-0x1 + * 138cfcfd 813d 84a71001 00>cmp dword ptr ds:[0x110a784],0x0 + * 138cfd07 8935 8ca71001 mov dword ptr ds:[0x110a78c],esi + * 138cfd0d 0f85 16000000 jnz 138cfd29 + * 138cfd13 832d c4aa1001 02 sub dword ptr ds:[0x110aac4],0x2 + * 138cfd1a c705 a8aa1001 e0>mov dword ptr ds:[0x110aaa8],0x88445e0 + * 138cfd24 -e9 fa02a9f3 jmp 07360023 + * 138cfd29 832d c4aa1001 02 sub dword ptr ds:[0x110aac4],0x2 + * 138cfd30 ^e9 ab15f4ff jmp 138112e0 + * 138cfd35 90 nop + * 138cfd36 cc int3 + * 138cfd37 cc int3 + * + * 13811266 cc int3 + * 13811267 cc int3 + * 13811268 77 0f ja short 13811279 + * 1381126a c705 a8aa1001 b0>mov dword ptr ds:[0x110aaa8],0x88445b0 + * 13811274 -e9 8bedb4f3 jmp 07360004 + * 13811279 8b35 8ca71001 mov esi,dword ptr ds:[0x110a78c] + * 1381127f 8b3d 88a71001 mov edi,dword ptr ds:[0x110a788] + * 13811285 8b2d 84a71001 mov ebp,dword ptr ds:[0x110a784] + * 1381128b 81c5 ffffffff add ebp,-0x1 + * 13811291 813d 84a71001 00>cmp dword ptr ds:[0x110a784],0x0 + * 1381129b 8935 78a71001 mov dword ptr ds:[0x110a778],esi + * 138112a1 893d 7ca71001 mov dword ptr ds:[0x110a77c],edi + * 138112a7 892d 8ca71001 mov dword ptr ds:[0x110a78c],ebp + * 138112ad 0f84 16000000 je 138112c9 + * 138112b3 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 138112ba e9 21000000 jmp 138112e0 + * 138112bf 017c45 84 add dword ptr ss:[ebp+eax*2-0x7c],edi + * 138112c3 08e9 or cl,ch + * 138112c5 5a pop edx + * 138112c6 ed in eax,dx ; i/o command + * 138112c7 b4 f3 mov ah,0xf3 + * 138112c9 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 138112d0 c705 a8aa1001 c0>mov dword ptr ds:[0x110aaa8],0x88445c0 + * 138112da -e9 44edb4f3 jmp 07360023 + * 138112df 90 nop + * 138112e0 77 0f ja short 138112f1 + * 138112e2 c705 a8aa1001 7c>mov dword ptr ds:[0x110aaa8],0x884457c + * 138112ec -e9 13edb4f3 jmp 07360004 + * 138112f1 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + * 138112f7 81e0 ffffff3f and eax,0x3fffffff + * 138112fd 0fb6b0 00008007 movzx esi,byte ptr ds:[eax+0x7800000] ; jichi: hook here + * 13811304 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 1381130a 81e0 ffffff3f and eax,0x3fffffff + * 13811310 0fbeb8 00008007 movsx edi,byte ptr ds:[eax+0x7800000] ; jichi: hook here + * 13811317 8bc6 mov eax,esi + * 13811319 0fbee8 movsx ebp,al + * 1381131c 3bef cmp ebp,edi + * 1381131e 893d 70a71001 mov dword ptr ds:[0x110a770],edi + * 13811324 892d 74a71001 mov dword ptr ds:[0x110a774],ebp + * 1381132a 8935 80a71001 mov dword ptr ds:[0x110a780],esi + * 13811330 0f85 16000000 jnz 1381134c + * 13811336 832d c4aa1001 05 sub dword ptr ds:[0x110aac4],0x5 + * 1381133d e9 56110000 jmp 13812498 + * 13811342 01c8 add eax,ecx + * 13811344 45 inc ebp + * 13811345 8408 test byte ptr ds:[eax],cl + * 13811347 -e9 d7ecb4f3 jmp 07360023 + * 1381134c 832d c4aa1001 05 sub dword ptr ds:[0x110aac4],0x5 + * 13811353 e9 0c000000 jmp 13811364 + * 13811358 0190 458408e9 add dword ptr ds:[eax+0xe9088445],edx + * 1381135e c1ec b4 shr esp,0xb4 ; shift constant out of range 1..31 + * 13811361 f3: prefix rep: ; superfluous prefix + * 13811362 90 nop + * 13811363 cc int3 + * + * 13811362 90 nop + * 13811363 cc int3 + * 13811364 77 0f ja short 13811375 + * 13811366 c705 a8aa1001 90>mov dword ptr ds:[0x110aaa8],0x8844590 + * 13811370 -e9 8fecb4f3 jmp 07360004 + * 13811375 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 1381137b 81e0 ffffff3f and eax,0x3fffffff + * 13811381 0fb6b0 00008007 movzx esi,byte ptr ds:[eax+0x7800000] ; jichi: hook here + * 13811388 81e6 ff000000 and esi,0xff + * 1381138e 8b3d 80a71001 mov edi,dword ptr ds:[0x110a780] + * 13811394 81e7 ff000000 and edi,0xff + * 1381139a 8bc7 mov eax,edi + * 1381139c 8bfe mov edi,esi + * 1381139e 2bf8 sub edi,eax + * 138113a0 8b05 e4a71001 mov eax,dword ptr ds:[0x110a7e4] + * 138113a6 893d 70a71001 mov dword ptr ds:[0x110a770],edi + * 138113ac 8935 74a71001 mov dword ptr ds:[0x110a774],esi + * 138113b2 8905 a8aa1001 mov dword ptr ds:[0x110aaa8],eax + * 138113b8 832d c4aa1001 05 sub dword ptr ds:[0x110aac4],0x5 + * 138113bf -e9 5fecb4f3 jmp 07360023 + * 138113c4 90 nop + * 138113c5 cc int3 + * 138113c6 cc int3 + * 138113c7 cc int3 + * + * 138124f2 cc int3 + * 138124f3 cc int3 + * 138124f4 77 0f ja short 13812505 + * 138124f6 c705 a8aa1001 d0>mov dword ptr ds:[0x110aaa8],0x88445d0 + * 13812500 -e9 ffdab4f3 jmp 07360004 + * 13812505 813d 74a71001 00>cmp dword ptr ds:[0x110a774],0x0 + * 1381250f c705 90a71001 00>mov dword ptr ds:[0x110a790],0x0 + * 13812519 0f84 16000000 je 13812535 + * 1381251f 832d c4aa1001 02 sub dword ptr ds:[0x110aac4],0x2 + * 13812526 e9 21000000 jmp 1381254c + * 1381252b 018446 8408e9ee add dword ptr ds:[esi+eax*2+0xeee90884],> + * 13812532 dab4f3 832dc4aa fidiv dword ptr ds:[ebx+esi*8+0xaac42d83> + * 13812539 1001 adc byte ptr ds:[ecx],al + * 1381253b 02e9 add ch,cl + * 1381253d 3302 xor eax,dword ptr ds:[edx] + * 1381253f 0000 add byte ptr ds:[eax],al + * 13812541 01d8 add eax,ebx + * 13812543 45 inc ebp + * 13812544 8408 test byte ptr ds:[eax],cl + * 13812546 -e9 d8dab4f3 jmp 07360023 + * 1381254b 90 nop + * 1381254c 77 0f ja short 1381255d + * 1381254e c705 a8aa1001 84>mov dword ptr ds:[0x110aaa8],0x8844684 + * 13812558 -e9 a7dab4f3 jmp 07360004 + * 1381255d 8b35 78a71001 mov esi,dword ptr ds:[0x110a778] + * 13812563 0335 8ca71001 add esi,dword ptr ds:[0x110a78c] + * 13812569 8b3d 88a71001 mov edi,dword ptr ds:[0x110a788] + * 1381256f 8d7f 01 lea edi,dword ptr ds:[edi+0x1] + * 13812572 8b2d 7ca71001 mov ebp,dword ptr ds:[0x110a77c] + * 13812578 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + * 1381257b 8b15 90a71001 mov edx,dword ptr ds:[0x110a790] + * 13812581 3b15 8ca71001 cmp edx,dword ptr ds:[0x110a78c] + * 13812587 892d 7ca71001 mov dword ptr ds:[0x110a77c],ebp + * 1381258d 893d 88a71001 mov dword ptr ds:[0x110a788],edi + * 13812593 8935 94a71001 mov dword ptr ds:[0x110a794],esi + * 13812599 0f85 16000000 jnz 138125b5 + * 1381259f 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 138125a6 c705 a8aa1001 c4>mov dword ptr ds:[0x110aaa8],0x88446c4 + * 138125b0 -e9 6edab4f3 jmp 07360023 + * 138125b5 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 138125bc e9 0b000000 jmp 138125cc + * 138125c1 019446 8408e958 add dword ptr ds:[esi+eax*2+0x58e90884],> + * 138125c8 dab4f3 90770fc7 fidiv dword ptr ds:[ebx+esi*8+0xc70f7790> + * 138125cf 05 a8aa1001 add eax,0x110aaa8 + * 138125d4 94 xchg eax,esp + * 138125d5 46 inc esi + * 138125d6 8408 test byte ptr ds:[eax],cl + * 138125d8 -e9 27dab4f3 jmp 07360004 + * 138125dd 8b05 88a71001 mov eax,dword ptr ds:[0x110a788] + * 138125e3 81e0 ffffff3f and eax,0x3fffffff + * 138125e9 0fb6b0 00008007 movzx esi,byte ptr ds:[eax+0x7800000] ; jichi: hook here + * 138125f0 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + * 138125f6 81e0 ffffff3f and eax,0x3fffffff + * 138125fc 0fb6b8 00008007 movzx edi,byte ptr ds:[eax+0x7800000] + * 13812603 8bc6 mov eax,esi + * 13812605 0fbee8 movsx ebp,al + * 13812608 8bc7 mov eax,edi + * 1381260a 0fbed0 movsx edx,al + * 1381260d 8b0d 90a71001 mov ecx,dword ptr ds:[0x110a790] + * 13812613 8d49 01 lea ecx,dword ptr ds:[ecx+0x1] + * 13812616 3bd5 cmp edx,ebp + * 13812618 892d 70a71001 mov dword ptr ds:[0x110a770],ebp + * 1381261e 8935 74a71001 mov dword ptr ds:[0x110a774],esi + * 13812624 893d 80a71001 mov dword ptr ds:[0x110a780],edi + * 1381262a 8915 84a71001 mov dword ptr ds:[0x110a784],edx + * 13812630 890d 90a71001 mov dword ptr ds:[0x110a790],ecx + * 13812636 0f84 16000000 je 13812652 + * 1381263c 832d c4aa1001 06 sub dword ptr ds:[0x110aac4],0x6 + * 13812643 e9 98d70b00 jmp 138cfde0 + * 13812648 019445 8408e9d1 add dword ptr ss:[ebp+eax*2+0xd1e90884],> + * 1381264f d9b4f3 832dc4aa fstenv (28-byte) ptr ds:[ebx+esi*8+0xaac> + * 13812656 1001 adc byte ptr ds:[ecx],al + * 13812658 06 push es + * 13812659 e9 0e000000 jmp 1381266c + * 1381265e 01ac46 8408e9bb add dword ptr ds:[esi+eax*2+0xbbe90884],> + * 13812665 d9b4f3 90cccccc fstenv (28-byte) ptr ds:[ebx+esi*8+0xccc> + * 1381266c 77 0f ja short 1381267d + * 1381266e c705 a8aa1001 ac>mov dword ptr ds:[0x110aaa8],0x88446ac + * 13812678 -e9 87d9b4f3 jmp 07360004 + * 1381267d 8b35 88a71001 mov esi,dword ptr ds:[0x110a788] + * 13812683 3b35 94a71001 cmp esi,dword ptr ds:[0x110a794] + * 13812689 0f85 16000000 jnz 138126a5 + * 1381268f 832d c4aa1001 02 sub dword ptr ds:[0x110aac4],0x2 + * 13812696 e9 d9000000 jmp 13812774 + * 1381269b 01d8 add eax,ebx + * 1381269d 45 inc ebp + * 1381269e 8408 test byte ptr ds:[eax],cl + * 138126a0 -e9 7ed9b4f3 jmp 07360023 + * 138126a5 832d c4aa1001 02 sub dword ptr ds:[0x110aac4],0x2 + * 138126ac e9 0b000000 jmp 138126bc + * 138126b1 01b446 8408e968 add dword ptr ds:[esi+eax*2+0x68e90884],> + * 138126b8 d9b4f3 90770fc7 fstenv (28-byte) ptr ds:[ebx+esi*8+0xc70> + * 138126bf 05 a8aa1001 add eax,0x110aaa8 + * 138126c4 b4 46 mov ah,0x46 + * 138126c6 8408 test byte ptr ds:[eax],cl + * 138126c8 -e9 37d9b4f3 jmp 07360004 + * 138126cd 8b35 88a71001 mov esi,dword ptr ds:[0x110a788] + * 138126d3 8d76 01 lea esi,dword ptr ds:[esi+0x1] + * 138126d6 813d 84a71001 00>cmp dword ptr ds:[0x110a784],0x0 + * 138126e0 8935 88a71001 mov dword ptr ds:[0x110a788],esi + * 138126e6 0f84 16000000 je 13812702 + * 138126ec 832d c4aa1001 02 sub dword ptr ds:[0x110aac4],0x2 + * 138126f3 e9 24000000 jmp 1381271c + * 138126f8 018c46 8408e921 add dword ptr ds:[esi+eax*2+0x21e90884],> + * 138126ff d9b4f3 832dc4aa fstenv (28-byte) ptr ds:[ebx+esi*8+0xaac> + * 13812706 1001 adc byte ptr ds:[ecx],al + * 13812708 02c7 add al,bh + * 1381270a 05 a8aa1001 add eax,0x110aaa8 + * 1381270f bc 468408e9 mov esp,0xe9088446 + * 13812714 0bd9 or ebx,ecx + * 13812716 b4 f3 mov ah,0xf3 + * 13812718 90 nop + * 13812719 cc int3 + * 1381271a cc int3 + * 1381271b cc int3 + * + * This function is very similar to Imageepoch, and can have duplicate text + * 138d1486 cc int3 + * 138d1487 cc int3 + * 138d1488 77 0f ja short 138d1499 + * 138d148a c705 a8aa1001 2c>mov dword ptr ds:[0x110aaa8],0x884452c + * 138d1494 -e9 6beba8f3 jmp 07360004 + * 138d1499 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + * 138d149f 81e0 ffffff3f and eax,0x3fffffff + * 138d14a5 0fbeb0 00008007 movsx esi,byte ptr ds:[eax+0x7800000] ; jichi: hook here + * 138d14ac 8b3d 7ca71001 mov edi,dword ptr ds:[0x110a77c] + * 138d14b2 8d7f 01 lea edi,dword ptr ds:[edi+0x1] + * 138d14b5 8b05 74a71001 mov eax,dword ptr ds:[0x110a774] + * 138d14bb 81e0 ffffff3f and eax,0x3fffffff + * 138d14c1 8bd6 mov edx,esi + * 138d14c3 8890 00008007 mov byte ptr ds:[eax+0x7800000],dl + * 138d14c9 8b2d 74a71001 mov ebp,dword ptr ds:[0x110a774] + * 138d14cf 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + * 138d14d2 81fe 00000000 cmp esi,0x0 + * 138d14d8 8935 70a71001 mov dword ptr ds:[0x110a770],esi + * 138d14de 892d 74a71001 mov dword ptr ds:[0x110a774],ebp + * 138d14e4 893d 7ca71001 mov dword ptr ds:[0x110a77c],edi + * 138d14ea 0f85 16000000 jnz 138d1506 + * 138d14f0 832d c4aa1001 05 sub dword ptr ds:[0x110aac4],0x5 + * 138d14f7 e9 e8000000 jmp 138d15e4 + * 138d14fc 015445 84 add dword ptr ss:[ebp+eax*2-0x7c],edx + * 138d1500 08e9 or cl,ch + * 138d1502 1d eba8f383 sbb eax,0x83f3a8eb + * 138d1507 2d c4aa1001 sub eax,0x110aac4 + * 138d150c 05 e90e0000 add eax,0xee9 + * 138d1511 0001 add byte ptr ds:[ecx],al + * 138d1513 40 inc eax + * 138d1514 45 inc ebp + * 138d1515 8408 test byte ptr ds:[eax],cl + * 138d1517 -e9 07eba8f3 jmp 07360023 + * 138d151c 90 nop + * 138d151d cc int3 + * 138d151e cc int3 + * 138d151f cc int3 + */ +//static void SpecialPSPHookYeti(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +//{ +// //enum { base = 0x7400000 }; +// DWORD eax = regof(eax, esp_base); +// LPCSTR text = LPCSTR(eax + hp->user_value); +// if (*text) { +// *data = (DWORD)text; +// *len = ::strlen(text); // SHIFT-JIS +// //*split = regof(ecx, esp_base); // ecx is bad that will split text threads +// //*split = FIXED_SPLIT_VALUE; // Similar to 5pb, it only has one thread? +// //*split = regof(ebx, esp_base); // value of ebx is splitting +// *split = FIXED_SPLIT_VALUE << 1; // * 2 to make it unique +// } +//} + +bool InsertYetiPSPHook() +{ + ConsoleOutput("vnreng: Yeti PSP: enter"); + const BYTE bytes[] = { + //0xcc, // 14e49edb cc int3 + 0x77, 0x0f, // 14e49edc 77 0f ja short 14e49eed + 0xc7,0x05, XX8, // 14e49ede c705 a8aa1001 98>mov dword ptr ds:[0x110aaa8],0x885ff98 + 0xe9, XX4, // 14e49ee8 -e9 17619eee jmp 03830004 + 0x8b,0x35, XX4, // 14e49eed 8b35 70a71001 mov esi,dword ptr ds:[0x110a770] + 0xc1,0xee, 0x1f, // 14e49ef3 c1ee 1f shr esi,0x1f + 0x8b,0x05, XX4, // 14e49ef6 8b05 b4a71001 mov eax,dword ptr ds:[0x110a7b4] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 14e49efc 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xb8, XX4, // 14e49f02 8bb8 14deff07 mov edi,dword ptr ds:[eax+0x7ffde14] + 0x03,0x35, XX4, // 14e49f08 0335 70a71001 add esi,dword ptr ds:[0x110a770] + 0xd1,0xfe, // 14e49f0e d1fe sar esi,1 + 0x8b,0x05, XX4, // 14e49f10 8b05 b0a71001 mov eax,dword ptr ds:[0x110a7b0] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 14e49f16 81e0 ffffff3f and eax,0x3fffffff + 0x89,0xb8, XX4, // 14e49f1c 89b8 00000008 mov dword ptr ds:[eax+0x8000000],edi + 0x8b,0x05, XX4, // 14e49f22 8b05 dca71001 mov eax,dword ptr ds:[0x110a7dc] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 14e49f28 81e0 ffffff3f and eax,0x3fffffff + 0x89,0xb0, XX4, // 14e49f2e 89b0 30000008 mov dword ptr ds:[eax+0x8000030],esi + 0x8b,0x05, XX4, // 14e49f34 8b05 b4a71001 mov eax,dword ptr ds:[0x110a7b4] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 14e49f3a 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xa8, XX4, // 14e49f40 8ba8 14deff07 mov ebp,dword ptr ds:[eax+0x7ffde14] + 0x8b,0xc5, // 14e49f46 8bc5 mov eax,ebp + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 14e49f48 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xb0 //, XX4, // 14e49f4e 0fb6b0 00000008 movzx esi,byte ptr ds:[eax+0x8000000] ; jichi: hook here + }; + enum { memory_offset = 3 }; // 14e49f4e 0fb6b0 00000008 movzx esi,byte ptr ds:[eax+0x8000000] + enum { addr_offset = sizeof(bytes) - memory_offset }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: Yeti PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|USING_SPLIT|FIXING_SPLIT|NO_CONTEXT; // Fix the split value to merge all threads + hp.text_fun = SpecialPSPHook; + hp.offset = pusha_eax_off - 4; + ConsoleOutput("vnreng: Yeti PSP: INSERT"); + NewHook(hp, "Yeti PSP"); + } + + ConsoleOutput("vnreng: Yeti PSP: leave"); + return addr; +} + +/** 7/19/2014 jichi kid-game.co.jp PSP engine, 0,9.8, 0.9.9 + * Sample game: Monochrome + * + * Note: sceFontGetCharInfo, sceFontGetCharGlyphImage_Clip also works + * + * Debug method: breakpoint the memory address + * There are two matched memory address to the current text + * + * == Second run == + * 13973a7b 90 nop + * 13973a7c 77 0f ja short 13973a8d + * 13973a7e c705 a8aa1001 90>mov dword ptr ds:[0x110aaa8],0x885c290 + * 13973a88 -e9 77c5ecef jmp 03840004 + * 13973a8d 8b05 90a71001 mov eax,dword ptr ds:[0x110a790] + * 13973a93 81e0 ffffff3f and eax,0x3fffffff + * 13973a99 0fb6b0 00008007 movzx esi,byte ptr ds:[eax+0x7800000] + * 13973aa0 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 13973aa6 81e0 ffffff3f and eax,0x3fffffff + * 13973aac 0fb6b8 00008007 movzx edi,byte ptr ds:[eax+0x7800000] ; jichi: hook here + * 13973ab3 81fe 00000000 cmp esi,0x0 + * 13973ab9 c705 8ca71001 00>mov dword ptr ds:[0x110a78c],0x0 + * 13973ac3 893d 9ca71001 mov dword ptr ds:[0x110a79c],edi + * 13973ac9 8935 a0a71001 mov dword ptr ds:[0x110a7a0],esi + * 13973acf 0f85 16000000 jnz 13973aeb + * 13973ad5 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 13973adc c705 a8aa1001 d0>mov dword ptr ds:[0x110aaa8],0x885c2d0 + * 13973ae6 -e9 38c5ecef jmp 03840023 + * 13973aeb 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 13973af2 e9 0d000000 jmp 13973b04 + * 13973af7 01a0 c28508e9 add dword ptr ds:[eax+0xe90885c2],esp + * 13973afd 22c5 and al,ch + * 13973aff ec in al,dx ; i/o command + * 13973b00 ef out dx,eax ; i/o command + * 13973b01 90 nop + * 13973b02 cc int3 + * 13973b03 cc int3 + * + * == First run == + * 1087394a cc int3 + * 1087394b cc int3 + * 1087394c 77 0f ja short 1087395d + * 1087394e c705 a8aa1001 78>mov dword ptr ds:[0x110aaa8],0x885c278 + * 10873958 -e9 a7c6bff2 jmp 03470004 + * 1087395d 8b35 80d0da12 mov esi,dword ptr ds:[0x12dad080] + * 10873963 8bc6 mov eax,esi + * 10873965 81e0 ffffff3f and eax,0x3fffffff + * 1087396b 8bb8 0000000a mov edi,dword ptr ds:[eax+0xa000000] + * 10873971 81ff 00000000 cmp edi,0x0 + * 10873977 c705 70a71001 00>mov dword ptr ds:[0x110a770],0x8db0000 + * 10873981 c705 74a71001 00>mov dword ptr ds:[0x110a774],0x0 + * 1087398b 893d 90a71001 mov dword ptr ds:[0x110a790],edi + * 10873991 8935 94a71001 mov dword ptr ds:[0x110a794],esi + * 10873997 c705 98a71001 00>mov dword ptr ds:[0x110a798],0x0 + * 108739a1 0f85 16000000 jnz 108739bd + * 108739a7 832d c4aa1001 06 sub dword ptr ds:[0x110aac4],0x6 + * 108739ae e9 75c20100 jmp 1088fc28 + * 108739b3 0148 c3 add dword ptr ds:[eax-0x3d],ecx + * 108739b6 8508 test dword ptr ds:[eax],ecx + * 108739b8 -e9 66c6bff2 jmp 03470023 + * 108739bd 832d c4aa1001 06 sub dword ptr ds:[0x110aac4],0x6 + * 108739c4 e9 0b000000 jmp 108739d4 + * 108739c9 0190 c28508e9 add dword ptr ds:[eax+0xe90885c2],edx + * 108739cf 50 push eax + * 108739d0 c6 ??? ; unknown command + * 108739d1 bf f290770f mov edi,0xf7790f2 + * 108739d6 c705 a8aa1001 90>mov dword ptr ds:[0x110aaa8],0x885c290 + * 108739e0 -e9 1fc6bff2 jmp 03470004 + * 108739e5 8b05 90a71001 mov eax,dword ptr ds:[0x110a790] + * 108739eb 81e0 ffffff3f and eax,0x3fffffff + * 108739f1 0fb6b0 0000000a movzx esi,byte ptr ds:[eax+0xa000000] ; jichi: hook here + * 108739f8 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 108739fe 81e0 ffffff3f and eax,0x3fffffff + * 10873a04 0fb6b8 0000000a movzx edi,byte ptr ds:[eax+0xa000000] ; jichi: hook here + * 10873a0b 81fe 00000000 cmp esi,0x0 + * 10873a11 c705 8ca71001 00>mov dword ptr ds:[0x110a78c],0x0 + * 10873a1b 893d 9ca71001 mov dword ptr ds:[0x110a79c],edi + * 10873a21 8935 a0a71001 mov dword ptr ds:[0x110a7a0],esi + * 10873a27 0f85 16000000 jnz 10873a43 + * 10873a2d 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 10873a34 c705 a8aa1001 d0>mov dword ptr ds:[0x110aaa8],0x885c2d0 + * 10873a3e -e9 e0c5bff2 jmp 03470023 + * 10873a43 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 10873a4a e9 0d000000 jmp 10873a5c + * 10873a4f 01a0 c28508e9 add dword ptr ds:[eax+0xe90885c2],esp + * 10873a55 ca c5bf retf 0xbfc5 ; far return + * 10873a58 f2: prefix repne: ; superfluous prefix + * 10873a59 90 nop + * 10873a5a cc int3 + * 10873a5b cc int3 + */ +static void SpecialPSPHookKid(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD eax = regof(eax, esp_base); + LPCSTR text = LPCSTR(eax + hp->user_value); + static LPCSTR lasttext; // Prevent reading the same address multiple times + if (text != lasttext && *text) { + lasttext = text; + text = _5pbltrim(text); + *data = (DWORD)text; + *len = _5pbstrlen(text); + *split = regof(ecx, esp_base); + } +} + +bool InsertKidPSPHook() +{ + ConsoleOutput("vnreng: KID PSP: enter"); + + const BYTE bytes[] = { + //0x90, // 13973a7b 90 nop + 0x77, 0x0f, // 13973a7c 77 0f ja short 13973a8d + 0xc7,0x05, XX8, // 13973a7e c705 a8aa1001 90>mov dword ptr ds:[0x110aaa8],0x885c290 + 0xe9, XX4, // 13973a88 -e9 77c5ecef jmp 03840004 + 0x8b,0x05, XX4, // 13973a8d 8b05 90a71001 mov eax,dword ptr ds:[0x110a790] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13973a93 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xb0, XX4, // 13973a99 0fb6b0 00008007 movzx esi,byte ptr ds:[eax+0x7800000] + 0x8b,0x05, XX4, // 13973aa0 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13973aa6 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xb8, XX4, // 13973aac 0fb6b8 00008007 movzx edi,byte ptr ds:[eax+0x7800000] ; jichi: hook here + 0x81,0xfe, 0x00,0x00,0x00,0x00 // 13973ab3 81fe 00000000 cmp esi,0x0 + }; + enum { memory_offset = 3 }; // 13973aac 0fb6b8 00008007 movzx edi,byte ptr ds:[eax+0x7800000] + enum { addr_offset = 0x13973aac - 0x13973a7c }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: KID PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.text_fun = SpecialPSPHookKid; + hp.type = USING_STRING|NO_CONTEXT; // no context is needed to get rid of variant retaddr + + //HookParam hp = {}; + //hp.address = addr + addr_offset; + //hp.user_value = *(DWORD *)(hp.address + memory_offset); + //hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; // Fix the split value to merge all threads + //hp.offset = pusha_eax_off - 4; + //hp.split = pusha_ecx_off - 4; + //hp.text_fun = SpecialPSPHook; + + ConsoleOutput("vnreng: KID PSP: INSERT"); + NewHook(hp, "KID PSP"); + } + + ConsoleOutput("vnreng: KID PSP: leave"); + return addr; +} + +/** 7/19/2014 jichi CYBERFRONT PSP engine, 0,9.8, 0.9.9 + * Sample game: 想�かけ�クローストゥ (0.9.9) + * + * Debug method: breakpoint the memory address + * There are two matched memory address to the current text + * + * The second is used. + * The #1 is missing text. + * + * #1 The text is written word by word + * + * 0ed8be86 90 nop + * 0ed8be87 cc int3 + * 0ed8be88 77 0f ja short 0ed8be99 + * 0ed8be8a c705 c84c1301 dc>mov dword ptr ds:[0x1134cc8],0x88151dc + * 0ed8be94 -e9 6b41b4f4 jmp 038d0004 + * 0ed8be99 8b35 cc491301 mov esi,dword ptr ds:[0x11349cc] + * 0ed8be9f 8d76 02 lea esi,dword ptr ds:[esi+0x2] + * 0ed8bea2 8b3d 94491301 mov edi,dword ptr ds:[0x1134994] + * 0ed8bea8 8b05 d0491301 mov eax,dword ptr ds:[0x11349d0] + * 0ed8beae 81e0 ffffff3f and eax,0x3fffffff + * 0ed8beb4 8bd7 mov edx,edi + * 0ed8beb6 8890 00008009 mov byte ptr ds:[eax+0x9800000],dl ; jichi: hook here, write text here + * 0ed8bebc 8b05 c8491301 mov eax,dword ptr ds:[0x11349c8] + * 0ed8bec2 81e0 ffffff3f and eax,0x3fffffff + * 0ed8bec8 0fb6a8 00008009 movzx ebp,byte ptr ds:[eax+0x9800000] + * 0ed8becf 8b05 d0491301 mov eax,dword ptr ds:[0x11349d0] + * 0ed8bed5 81e0 ffffff3f and eax,0x3fffffff + * 0ed8bedb 8bd5 mov edx,ebp + * 0ed8bedd 8890 01008009 mov byte ptr ds:[eax+0x9800001],dl + * 0ed8bee3 8b15 d0491301 mov edx,dword ptr ds:[0x11349d0] + * 0ed8bee9 8d52 02 lea edx,dword ptr ds:[edx+0x2] + * 0ed8beec 892d 90491301 mov dword ptr ds:[0x1134990],ebp + * 0ed8bef2 8935 cc491301 mov dword ptr ds:[0x11349cc],esi + * 0ed8bef8 8915 d0491301 mov dword ptr ds:[0x11349d0],edx + * 0ed8befe 832d e44c1301 06 sub dword ptr ds:[0x1134ce4],0x6 + * 0ed8bf05 e9 0e000000 jmp 0ed8bf18 + * 0ed8bf0a 013451 add dword ptr ds:[ecx+edx*2],esi + * 0ed8bf0d 8108 e90f41b4 or dword ptr ds:[eax],0xb4410fe9 + * 0ed8bf13 f4 hlt ; privileged command + * 0ed8bf14 90 nop + * 0ed8bf15 cc int3 + * + * #2 The text is read + * + * Issue: the text is read multiple times. + * Only esp > 0xfff is kept. + * + * 0ed8cf13 90 nop + * 0ed8cf14 77 0f ja short 0ed8cf25 + * 0ed8cf16 c705 c84c1301 b8>mov dword ptr ds:[0x1134cc8],0x888d1b8 + * 0ed8cf20 -e9 df30b4f4 jmp 038d0004 + * 0ed8cf25 8b05 98491301 mov eax,dword ptr ds:[0x1134998] + * 0ed8cf2b 81e0 ffffff3f and eax,0x3fffffff + * 0ed8cf31 0fb6b0 00008009 movzx esi,byte ptr ds:[eax+0x9800000] ; jichi: hook here + * 0ed8cf38 81fe 00000000 cmp esi,0x0 + * 0ed8cf3e 8935 90491301 mov dword ptr ds:[0x1134990],esi + * 0ed8cf44 0f85 2f000000 jnz 0ed8cf79 + * 0ed8cf4a 8b05 9c491301 mov eax,dword ptr ds:[0x113499c] + * 0ed8cf50 81e0 ffffff3f and eax,0x3fffffff + * 0ed8cf56 0fbeb0 00008009 movsx esi,byte ptr ds:[eax+0x9800000] + * 0ed8cf5d 8935 90491301 mov dword ptr ds:[0x1134990],esi + * 0ed8cf63 832d e44c1301 03 sub dword ptr ds:[0x1134ce4],0x3 + * 0ed8cf6a c705 c84c1301 18>mov dword ptr ds:[0x1134cc8],0x888d218 + * 0ed8cf74 -e9 aa30b4f4 jmp 038d0023 + * 0ed8cf79 832d e44c1301 03 sub dword ptr ds:[0x1134ce4],0x3 + * 0ed8cf80 e9 0b000000 jmp 0ed8cf90 + * 0ed8cf85 01c4 add esp,eax + * 0ed8cf87 d188 08e99430 ror dword ptr ds:[eax+0x3094e908],1 + * 0ed8cf8d b4 f4 mov ah,0xf4 + * 0ed8cf8f 90 nop + */ + +static void SpecialPSPHookCyberfront(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD splitvalue = regof(edi, esp_base); + if (splitvalue < 0x0fff) + return; + DWORD eax = regof(eax, esp_base); + LPCSTR text = LPCSTR(eax + hp->user_value); + if (*text) { + *data = (DWORD)text; + *len = ::strlen(text); + *split = splitvalue; + } +} +bool InsertCyberfrontPSPHook() +{ + ConsoleOutput("vnreng: CYBERFRONT PSP: enter"); + + const BYTE bytes[] = { + // 0ed8cf13 90 nop + 0x77, 0x0f, // 0ed8cf14 77 0f ja short 0ed8cf25 + 0xc7,0x05, XX8, // 0ed8cf16 c705 c84c1301 b8>mov dword ptr ds:[0x1134cc8],0x888d1b8 + 0xe9, XX4, // 0ed8cf20 -e9 df30b4f4 jmp 038d0004 + 0x8b,0x05, XX4, // 0ed8cf25 8b05 98491301 mov eax,dword ptr ds:[0x1134998] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 0ed8cf2b 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xb0, XX4, // 0ed8cf31 0fb6b0 00008009 movzx esi,byte ptr ds:[eax+0x9800000] ; jichi: hook here + 0x81,0xfe, 0x00,0x00,0x00,0x00, // 0ed8cf38 81fe 00000000 cmp esi,0x0 + 0x89,0x35, XX4, // 0ed8cf3e 8935 90491301 mov dword ptr ds:[0x1134990],esi + 0x0f,0x85, 0x2f,0x00,0x00,0x00, // 0ed8cf44 0f85 2f000000 jnz 0ed8cf79 + 0x8b,0x05, XX4, // 0ed8cf4a 8b05 9c491301 mov eax,dword ptr ds:[0x113499c] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 0ed8cf50 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xbe,0xb0, XX4, // 0ed8cf56 0fbeb0 00008009 movsx esi,byte ptr ds:[eax+0x9800000] + 0x89,0x35, XX4, // 0ed8cf5d 8935 90491301 mov dword ptr ds:[0x1134990],esi + 0x83,0x2d, XX4, 0x03, // 0ed8cf63 832d e44c1301 03 sub dword ptr ds:[0x1134ce4],0x3 + 0xc7,0x05 //, XX8 // 0ed8cf6a c705 c84c1301 18>mov dword ptr ds:[0x1134cc8],0x888d218 + }; + enum { memory_offset = 3 }; // 13909a51 8890 00008007 mov byte ptr ds:[eax+0x7800000],dl + enum { addr_offset = 0x0ed8cf31 - 0x0ed8cf14 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + //GROWL_DWORD(addr); + if (!addr) + ConsoleOutput("vnreng: CYBERFRONT PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; + //hp.offset = pusha_eax_off - 4; + //hp.split = pusha_edi_off - 4; + hp.text_fun = SpecialPSPHookCyberfront; + ConsoleOutput("vnreng: CYBERFRONT PSP: INSERT"); + NewHook(hp, "CYBERFRONT PSP"); + } + + ConsoleOutput("vnreng: CYBERFRONT PSP: leave"); + return addr; +} + +/** 7/19/2014 jichi Alternative Yeti PSP engine, 0.9.8, 0.9.9 + * Sample game: Never 7, 0.9.8 & 0.9.9 + * Sample game: ひまわり + * + * Do not work on 0.9.9 Ever17 (7/27/2014) + * + * + * This hook does not work for 12River. + * However, sceFont functions work. + * + * Memory address is FIXED. + * Debug method: breakpoint the memory address + * There are two matched memory address to the current text + * + * There are several functions. The first one is used. + * + * The text also has 5pb-like garbage, but it is difficult to trim. + * + * PPSSPP 0.9.8: + * + * 14289802 cc int3 + * 14289803 cc int3 + * 14289804 77 0f ja short 14289815 + * 14289806 c705 a8aa1001 58>mov dword ptr ds:[0x110aaa8],0x881ab58 + * 14289810 -e9 ef6767ef jmp 03900004 + * 14289815 8b35 74a71001 mov esi,dword ptr ds:[0x110a774] + * 1428981b 0335 78a71001 add esi,dword ptr ds:[0x110a778] + * 14289821 8b05 dca71001 mov eax,dword ptr ds:[0x110a7dc] + * 14289827 81e0 ffffff3f and eax,0x3fffffff + * 1428982d 8bb8 28004007 mov edi,dword ptr ds:[eax+0x7400028] + * 14289833 8bc6 mov eax,esi + * 14289835 81e0 ffffff3f and eax,0x3fffffff + * 1428983b 8bd7 mov edx,edi + * 1428983d 8890 10044007 mov byte ptr ds:[eax+0x7400410],dl + * 14289843 8b05 b0a71001 mov eax,dword ptr ds:[0x110a7b0] + * 14289849 81e0 ffffff3f and eax,0x3fffffff + * 1428984f 8bb8 84004007 mov edi,dword ptr ds:[eax+0x7400084] + * 14289855 8b05 aca71001 mov eax,dword ptr ds:[0x110a7ac] + * 1428985b 81e0 ffffff3f and eax,0x3fffffff + * 14289861 0fb6a8 00004007 movzx ebp,byte ptr ds:[eax+0x7400000] ; jichi: hook here + * 14289868 81ff 00000000 cmp edi,0x0 + * 1428986e 8935 70a71001 mov dword ptr ds:[0x110a770],esi + * 14289874 893d 74a71001 mov dword ptr ds:[0x110a774],edi + * 1428987a 892d 78a71001 mov dword ptr ds:[0x110a778],ebp + * 14289880 0f85 16000000 jnz 1428989c + * 14289886 832d c4aa1001 06 sub dword ptr ds:[0x110aac4],0x6 + * 1428988d c705 a8aa1001 ac>mov dword ptr ds:[0x110aaa8],0x881aeac + * 14289897 -e9 876767ef jmp 03900023 + * 1428989c 832d c4aa1001 06 sub dword ptr ds:[0x110aac4],0x6 + * 142898a3 e9 0c000000 jmp 142898b4 + * 142898a8 0170 ab add dword ptr ds:[eax-0x55],esi + * 142898ab 8108 e9716767 or dword ptr ds:[eax],0x676771e9 + * 142898b1 ef out dx,eax ; i/o command + * 142898b2 90 nop + * + * 142878ed cc int3 + * 142878ee cc int3 + * 142878ef cc int3 + * 142878f0 77 0f ja short 14287901 + * 142878f2 c705 a8aa1001 44>mov dword ptr ds:[0x110aaa8],0x8811e44 + * 142878fc -e9 038767ef jmp 03900004 + * 14287901 8b35 70a71001 mov esi,dword ptr ds:[0x110a770] + * 14287907 8b05 b0a71001 mov eax,dword ptr ds:[0x110a7b0] + * 1428790d 81e0 ffffff3f and eax,0x3fffffff + * 14287913 8bd6 mov edx,esi + * 14287915 8890 00004007 mov byte ptr ds:[eax+0x7400000],dl ; jichi: hook here + * 1428791b 8b05 a8a71001 mov eax,dword ptr ds:[0x110a7a8] + * 14287921 81e0 ffffff3f and eax,0x3fffffff + * 14287927 0fb6b8 00004007 movzx edi,byte ptr ds:[eax+0x7400000] + * 1428792e 8b2d aca71001 mov ebp,dword ptr ds:[0x110a7ac] + * 14287934 81c5 02000000 add ebp,0x2 + * 1428793a 8bd5 mov edx,ebp + * 1428793c 8915 aca71001 mov dword ptr ds:[0x110a7ac],edx + * 14287942 8b05 b0a71001 mov eax,dword ptr ds:[0x110a7b0] + * 14287948 81e0 ffffff3f and eax,0x3fffffff + * 1428794e 8bd7 mov edx,edi + * 14287950 8890 01004007 mov byte ptr ds:[eax+0x7400001],dl + * 14287956 8b15 b0a71001 mov edx,dword ptr ds:[0x110a7b0] + * 1428795c 8d52 02 lea edx,dword ptr ds:[edx+0x2] + * 1428795f 893d 74a71001 mov dword ptr ds:[0x110a774],edi + * 14287965 892d a8a71001 mov dword ptr ds:[0x110a7a8],ebp + * 1428796b 8915 b0a71001 mov dword ptr ds:[0x110a7b0],edx + * 14287971 832d c4aa1001 07 sub dword ptr ds:[0x110aac4],0x7 + * 14287978 e9 0b000000 jmp 14287988 + * 1428797d 01a8 1d8108e9 add dword ptr ds:[eax+0xe908811d],ebp + * 14287983 9c pushfd + * 14287984 8667 ef xchg byte ptr ds:[edi-0x11],ah + * 14287987 90 nop + * + * 14289a2a 90 nop + * 14289a2b cc int3 + * 14289a2c 77 0f ja short 14289a3d + * 14289a2e c705 a8aa1001 b4>mov dword ptr ds:[0x110aaa8],0x881abb4 + * 14289a38 -e9 c76567ef jmp 03900004 + * 14289a3d 8b05 dca71001 mov eax,dword ptr ds:[0x110a7dc] + * 14289a43 81e0 ffffff3f and eax,0x3fffffff + * 14289a49 8bb0 18004007 mov esi,dword ptr ds:[eax+0x7400018] + * 14289a4f 8b05 dca71001 mov eax,dword ptr ds:[0x110a7dc] + * 14289a55 81e0 ffffff3f and eax,0x3fffffff + * 14289a5b 8bb8 24004007 mov edi,dword ptr ds:[eax+0x7400024] + * 14289a61 8b2d 70a71001 mov ebp,dword ptr ds:[0x110a770] + * 14289a67 03ee add ebp,esi + * 14289a69 8b05 dca71001 mov eax,dword ptr ds:[0x110a7dc] + * 14289a6f 81e0 ffffff3f and eax,0x3fffffff + * 14289a75 8bb0 20004007 mov esi,dword ptr ds:[eax+0x7400020] + * 14289a7b 8bc5 mov eax,ebp + * 14289a7d 81e0 ffffff3f and eax,0x3fffffff + * 14289a83 66:89b8 c2034007 mov word ptr ds:[eax+0x74003c2],di + * 14289a8a 8bc5 mov eax,ebp + * 14289a8c 81e0 ffffff3f and eax,0x3fffffff + * 14289a92 66:89b0 c0034007 mov word ptr ds:[eax+0x74003c0],si + * 14289a99 8b05 aca71001 mov eax,dword ptr ds:[0x110a7ac] + * 14289a9f 81e0 ffffff3f and eax,0x3fffffff + * 14289aa5 0fb6b0 00004007 movzx esi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + * 14289aac 81e6 ff000000 and esi,0xff + * 14289ab2 892d 70a71001 mov dword ptr ds:[0x110a770],ebp + * 14289ab8 893d 74a71001 mov dword ptr ds:[0x110a774],edi + * 14289abe 8935 78a71001 mov dword ptr ds:[0x110a778],esi + * 14289ac4 c705 e4a71001 d8>mov dword ptr ds:[0x110a7e4],0x881abd8 + * 14289ace 832d c4aa1001 09 sub dword ptr ds:[0x110aac4],0x9 + * 14289ad5 ^e9 d6c6f8ff jmp 142161b0 + * 14289ada 90 nop + * + * 14289adb cc int3 + * 14289adc 77 0f ja short 14289aed + * 14289ade c705 a8aa1001 d8>mov dword ptr ds:[0x110aaa8],0x881abd8 + * 14289ae8 -e9 176567ef jmp 03900004 + * 14289aed 813d 70a71001 00>cmp dword ptr ds:[0x110a770],0x0 + * 14289af7 0f85 2f000000 jnz 14289b2c + * 14289afd 8b05 aca71001 mov eax,dword ptr ds:[0x110a7ac] + * 14289b03 81e0 ffffff3f and eax,0x3fffffff + * 14289b09 0fb6b0 00004007 movzx esi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + * 14289b10 8935 70a71001 mov dword ptr ds:[0x110a770],esi + * 14289b16 832d c4aa1001 02 sub dword ptr ds:[0x110aac4],0x2 + * 14289b1d e9 22000000 jmp 14289b44 + * 14289b22 0110 add dword ptr ds:[eax],edx + * 14289b24 af scas dword ptr es:[edi] + * 14289b25 8108 e9f76467 or dword ptr ds:[eax],0x6764f7e9 + * 14289b2b ef out dx,eax ; i/o command + * 14289b2c 832d c4aa1001 02 sub dword ptr ds:[0x110aac4],0x2 + * 14289b33 c705 a8aa1001 e0>mov dword ptr ds:[0x110aaa8],0x881abe0 + * 14289b3d -e9 e16467ef jmp 03900023 + * + * PPSSPP 0.9.9 (7/27/2014) + * + * 0ed85942 cc int3 + * 0ed85943 cc int3 + * 0ed85944 77 0f ja short 0ed85955 + * 0ed85946 c705 c84c1301 58>mov dword ptr ds:[0x1134cc8],0x881ab58 + * 0ed85950 -e9 afa6aef4 jmp 03870004 + * 0ed85955 8b35 94491301 mov esi,dword ptr ds:[0x1134994] + * 0ed8595b 0335 98491301 add esi,dword ptr ds:[0x1134998] + * 0ed85961 8b05 fc491301 mov eax,dword ptr ds:[0x11349fc] + * 0ed85967 81e0 ffffff3f and eax,0x3fffffff + * 0ed8596d 8bb8 28008009 mov edi,dword ptr ds:[eax+0x9800028] + * 0ed85973 8bc6 mov eax,esi + * 0ed85975 81e0 ffffff3f and eax,0x3fffffff + * 0ed8597b 8bd7 mov edx,edi + * 0ed8597d 8890 10048009 mov byte ptr ds:[eax+0x9800410],dl + * 0ed85983 8b05 d0491301 mov eax,dword ptr ds:[0x11349d0] + * 0ed85989 81e0 ffffff3f and eax,0x3fffffff + * 0ed8598f 8bb8 84008009 mov edi,dword ptr ds:[eax+0x9800084] + * 0ed85995 8b05 cc491301 mov eax,dword ptr ds:[0x11349cc] + * 0ed8599b 81e0 ffffff3f and eax,0x3fffffff + * 0ed859a1 0fb6a8 00008009 movzx ebp,byte ptr ds:[eax+0x9800000] ; jichi: hook here + * 0ed859a8 81ff 00000000 cmp edi,0x0 + * 0ed859ae 8935 90491301 mov dword ptr ds:[0x1134990],esi + * 0ed859b4 893d 94491301 mov dword ptr ds:[0x1134994],edi + * 0ed859ba 892d 98491301 mov dword ptr ds:[0x1134998],ebp + * 0ed859c0 0f85 16000000 jnz 0ed859dc + * 0ed859c6 832d e44c1301 06 sub dword ptr ds:[0x1134ce4],0x6 + * 0ed859cd c705 c84c1301 ac>mov dword ptr ds:[0x1134cc8],0x881aeac + * 0ed859d7 -e9 47a6aef4 jmp 03870023 + * 0ed859dc 832d e44c1301 06 sub dword ptr ds:[0x1134ce4],0x6 + * 0ed859e3 e9 0c000000 jmp 0ed859f4 + * 0ed859e8 0170 ab add dword ptr ds:[eax-0x55],esi + * 0ed859eb 8108 e931a6ae or dword ptr ds:[eax],0xaea631e9 + * 0ed859f1 f4 hlt ; privileged command + * 0ed859f2 90 nop + */ +// TODO: Is reverse_strlen a better choice? +static void SpecialPSPHookYeti2(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD eax = regof(eax, esp_base); + LPCSTR text = LPCSTR(eax + hp->user_value); + if (BYTE c = *(BYTE *)text) { + *data = (DWORD)text; + //*len = text[1] ? 2 : 1; + *len = ::LeadByteTable[c]; + + *split = regof(edx, esp_base); + //DWORD ecx = regof(ecx, esp_base); + //*split = ecx ? (FIXED_SPLIT_VALUE << 1) : 0; // << 1 to be unique, non-zero ecx is what I want + } +} + +bool InsertYeti2PSPHook() +{ + ConsoleOutput("vnreng: Yeti2 PSP: enter"); + + const BYTE bytes[] = { + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 14289827 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xb8, XX4, // 1428982d 8bb8 28004007 mov edi,dword ptr ds:[eax+0x7400028] + 0x8b,0xc6, // 14289833 8bc6 mov eax,esi + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 14289835 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xd7, // 1428983b 8bd7 mov edx,edi + 0x88,0x90, XX4, // 1428983d 8890 10044007 mov byte ptr ds:[eax+0x7400410],dl + 0x8b,0x05, XX4, // 14289843 8b05 b0a71001 mov eax,dword ptr ds:[0x110a7b0] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 14289849 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xb8, XX4, // 1428984f 8bb8 84004007 mov edi,dword ptr ds:[eax+0x7400084] + 0x8b,0x05, XX4, // 14289855 8b05 aca71001 mov eax,dword ptr ds:[0x110a7ac] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1428985b 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xa8 //, XX4 // 14289861 0fb6a8 00004007 movzx ebp,byte ptr ds:[eax+0x7400000] ; jichi: hook here + // 14289b10 8935 70a71001 mov dword ptr ds:[0x110a770],esi + // 14289b16 832d c4aa1001 02 sub dword ptr ds:[0x110aac4],0x2 + }; + enum { memory_offset = 3 }; + enum { addr_offset = sizeof(bytes) - memory_offset }; + //enum { addr_offset = sizeof(bytes) + 4 }; // point to next statement after ebp is assigned + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: Yeti2 PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|NO_CONTEXT; + //hp.offset = pusha_eax_off - 4; + //hp.split = pusha_ecx_off - 4; // this would split scenario thread + //hp.split = hp.offset; // directly use text address to split + hp.text_fun = SpecialPSPHookYeti2; + ConsoleOutput("vnreng: Yeti2 PSP: INSERT"); + NewHook(hp, "Yeti2 PSP"); + } + + ConsoleOutput("vnreng: Yeti2 PSP: leave"); + return addr; +} + +/** 7/22/2014 jichi BANDAI PSP engine, 0.9.8 only + * Replaced by Otomate PPSSPP on 0.9.9. + * Sample game: School Rumble PSP 姉さん事件で�(SHIFT-JIS) + * See: http://sakuradite.com/topic/333 + * + * Sample game: 寮�のサクリファイス work on 0.9.8, not 0.9.9 + * + * + * Sample game: Shining Hearts (UTF-8) + * See: http://sakuradite.com/topic/346 + * + * The encoding could be either UTF-8 or SHIFT-JIS + * + * Debug method: breakpoint the memory address + * There are two matched memory address to the current text + * + * Only one function is accessing the text address. + * + * Character name: + * + * 1346c122 cc int3 + * 1346c123 cc int3 + * 1346c124 77 0f ja short 1346c135 + * 1346c126 c705 a8aa1001 a4>mov dword ptr ds:[0x110aaa8],0x882f2a4 + * 1346c130 -e9 cf3e2cf0 jmp 03730004 + * 1346c135 8b05 a8a71001 mov eax,dword ptr ds:[0x110a7a8] + * 1346c13b 81e0 ffffff3f and eax,0x3fffffff + * 1346c141 8bb0 14004007 mov esi,dword ptr ds:[eax+0x7400014] + * 1346c147 8b3d 70a71001 mov edi,dword ptr ds:[0x110a770] + * 1346c14d c1e7 02 shl edi,0x2 + * 1346c150 8b05 a8a71001 mov eax,dword ptr ds:[0x110a7a8] + * 1346c156 81e0 ffffff3f and eax,0x3fffffff + * 1346c15c 8ba8 18004007 mov ebp,dword ptr ds:[eax+0x7400018] + * 1346c162 03fe add edi,esi + * 1346c164 8bc7 mov eax,edi + * 1346c166 81e0 ffffff3f and eax,0x3fffffff + * 1346c16c 0fb790 02004007 movzx edx,word ptr ds:[eax+0x7400002] + * 1346c173 8bc2 mov eax,edx + * 1346c175 8bd5 mov edx,ebp + * 1346c177 03d0 add edx,eax + * 1346c179 8bc2 mov eax,edx + * 1346c17b 81e0 ffffff3f and eax,0x3fffffff + * 1346c181 0fb6b8 00004007 movzx edi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + * 1346c188 8bcf mov ecx,edi + * 1346c18a 81e7 ff000000 and edi,0xff + * 1346c190 8935 74a71001 mov dword ptr ds:[0x110a774],esi + * 1346c196 8b35 b8a71001 mov esi,dword ptr ds:[0x110a7b8] + * 1346c19c 81c6 bc82ffff add esi,0xffff82bc + * 1346c1a2 81ff 00000000 cmp edi,0x0 + * 1346c1a8 893d 70a71001 mov dword ptr ds:[0x110a770],edi + * 1346c1ae 8915 78a71001 mov dword ptr ds:[0x110a778],edx + * 1346c1b4 892d 7ca71001 mov dword ptr ds:[0x110a77c],ebp + * 1346c1ba 890d 80a71001 mov dword ptr ds:[0x110a780],ecx + * 1346c1c0 8935 84a71001 mov dword ptr ds:[0x110a784],esi + * 1346c1c6 0f85 16000000 jnz 1346c1e2 + * 1346c1cc 832d c4aa1001 0b sub dword ptr ds:[0x110aac4],0xb + * 1346c1d3 e9 3c050000 jmp 1346c714 + * 1346c1d8 014cf3 82 add dword ptr ds:[ebx+esi*8-0x7e],ecx + * 1346c1dc 08e9 or cl,ch + * 1346c1de 41 inc ecx + * 1346c1df 3e:2c f0 sub al,0xf0 ; superfluous prefix + * 1346c1e2 832d c4aa1001 0b sub dword ptr ds:[0x110aac4],0xb + * 1346c1e9 e9 0e000000 jmp 1346c1fc + * 1346c1ee 01d0 add eax,edx + * 1346c1f0 f2: prefix repne: ; superfluous prefix + * 1346c1f1 8208 e9 or byte ptr ds:[eax],0xffffffe9 + * 1346c1f4 2b3e sub edi,dword ptr ds:[esi] + * 1346c1f6 2c f0 sub al,0xf0 + * 1346c1f8 90 nop + * 1346c1f9 cc int3 + * 1346c1fa cc int3 + * 1346c1fb cc int3 + * + * Scenario: + * + * 1340055d cc int3 + * 1340055e cc int3 + * 1340055f cc int3 + * 13400560 77 0f ja short 13400571 + * 13400562 c705 a8aa1001 cc>mov dword ptr ds:[0x110aaa8],0x883decc + * 1340056c -e9 93fa54f0 jmp 03950004 + * 13400571 8b35 78a71001 mov esi,dword ptr ds:[0x110a778] + * 13400577 81c6 01000000 add esi,0x1 + * 1340057d 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 13400583 81e0 ffffff3f and eax,0x3fffffff + * 13400589 0fb6b8 00004007 movzx edi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + * 13400590 8b2d 78a71001 mov ebp,dword ptr ds:[0x110a778] + * 13400596 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + * 13400599 81ff 00000000 cmp edi,0x0 + * 1340059f 8935 70a71001 mov dword ptr ds:[0x110a770],esi + * 134005a5 893d 74a71001 mov dword ptr ds:[0x110a774],edi + * 134005ab 892d 78a71001 mov dword ptr ds:[0x110a778],ebp + * 134005b1 0f84 16000000 je 134005cd + * 134005b7 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 134005be e9 21000000 jmp 134005e4 + * 134005c3 01d0 add eax,edx + * 134005c5 de83 08e956fa fiadd word ptr ds:[ebx+0xfa56e908] + * 134005cb 54 push esp + * 134005cc f0:832d c4aa1001>lock sub dword ptr ds:[0x110aac4],0x4 ; lock prefix + * 134005d4 e9 7f000000 jmp 13400658 + * 134005d9 01dc add esp,ebx + * 134005db de83 08e940fa fiadd word ptr ds:[ebx+0xfa40e908] + * 134005e1 54 push esp + * 134005e2 f0:90 lock nop ; lock prefix is not allowed + * 134005e4 77 0f ja short 134005f5 + * 134005e6 c705 a8aa1001 d0>mov dword ptr ds:[0x110aaa8],0x883ded0 + * 134005f0 -e9 0ffa54f0 jmp 03950004 + * 134005f5 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 134005fb 81e0 ffffff3f and eax,0x3fffffff + * 13400601 0fb6b0 00004007 movzx esi,byte ptr ds:[eax+0x7400000] + * 13400608 8b3d 78a71001 mov edi,dword ptr ds:[0x110a778] + * 1340060e 8d7f 01 lea edi,dword ptr ds:[edi+0x1] + * 13400611 81fe 00000000 cmp esi,0x0 + * 13400617 8935 74a71001 mov dword ptr ds:[0x110a774],esi + * 1340061d 893d 78a71001 mov dword ptr ds:[0x110a778],edi + * 13400623 0f84 16000000 je 1340063f + * 13400629 832d c4aa1001 03 sub dword ptr ds:[0x110aac4],0x3 + * 13400630 ^e9 afffffff jmp 134005e4 + * 13400635 01d0 add eax,edx + * 13400637 de83 08e9e4f9 fiadd word ptr ds:[ebx+0xf9e4e908] + * 1340063d 54 push esp + * 1340063e f0:832d c4aa1001>lock sub dword ptr ds:[0x110aac4],0x3 ; lock prefix + * 13400646 e9 0d000000 jmp 13400658 + * 1340064b 01dc add esp,ebx + * 1340064d de83 08e9cef9 fiadd word ptr ds:[ebx+0xf9cee908] + * 13400653 54 push esp + * 13400654 f0:90 lock nop ; lock prefix is not allowed + * 13400656 cc int3 + * 13400657 cc int3 + */ +bool InsertBandaiNamePSPHook() +{ + ConsoleOutput("vnreng: BANDAI Name PSP: enter"); + + const BYTE bytes[] = { + //0xcc, // 1346c122 cc int3 + //0xcc, // 1346c123 cc int3 + 0x77, 0x0f, // 1346c124 77 0f ja short 1346c135 + 0xc7,0x05, XX8, // 1346c126 c705 a8aa1001 a4>mov dword ptr ds:[0x110aaa8],0x882f2a4 + 0xe9, XX4, // 1346c130 -e9 cf3e2cf0 jmp 03730004 + 0x8b,0x05, XX4, // 1346c135 8b05 a8a71001 mov eax,dword ptr ds:[0x110a7a8] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1346c13b 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xb0, XX4, // 1346c141 8bb0 14004007 mov esi,dword ptr ds:[eax+0x7400014] + 0x8b,0x3d, XX4, // 1346c147 8b3d 70a71001 mov edi,dword ptr ds:[0x110a770] + 0xc1,0xe7, 0x02, // 1346c14d c1e7 02 shl edi,0x2 + 0x8b,0x05, XX4, // 1346c150 8b05 a8a71001 mov eax,dword ptr ds:[0x110a7a8] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1346c156 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xa8, XX4, // 1346c15c 8ba8 18004007 mov ebp,dword ptr ds:[eax+0x7400018] + 0x03,0xfe, // 1346c162 03fe add edi,esi + 0x8b,0xc7, // 1346c164 8bc7 mov eax,edi + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1346c166 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb7,0x90, XX4, // 1346c16c 0fb790 02004007 movzx edx,word ptr ds:[eax+0x7400002] + 0x8b,0xc2, // 1346c173 8bc2 mov eax,edx + 0x8b,0xd5, // 1346c175 8bd5 mov edx,ebp + 0x03,0xd0, // 1346c177 03d0 add edx,eax + 0x8b,0xc2, // 1346c179 8bc2 mov eax,edx + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1346c17b 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xb8 //, XX4 // 1346c181 0fb6b8 00004007 movzx edi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + }; + enum { memory_offset = 3 }; // 1346c181 0fb6b8 00004007 movzx edi,byte ptr ds:[eax+0x7400000] + enum { addr_offset = sizeof(bytes) - memory_offset }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: BANDAI Name PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; + hp.offset = pusha_eax_off - 4; + hp.split = pusha_ebx_off - 4; + hp.text_fun = SpecialPSPHook; + ConsoleOutput("vnreng: BANDAI Name PSP: INSERT"); + NewHook(hp, "BANDAI Name PSP"); + } + + ConsoleOutput("vnreng: BANDAI Name PSP: leave"); + return addr; +} + +namespace { // unnamed + +inline bool _bandaigarbage_ch(char c) +{ + return c == ' ' || c == '/' || c == '#' || c == '.' || c == ':' + || c >= '0' && c <= '9' + || c >= 'A' && c <= 'z'; // also ignore ASCII 91-96: [ \ ] ^ _ ` +} + +// Remove trailing /L/P or #n garbage +size_t _bandaistrlen(LPCSTR text) +{ + size_t len = ::strlen(text); + size_t ret = len; + while (len && _bandaigarbage_ch(text[len - 1])) { + len--; + if (text[len] == '/' || text[len] == '#') // in case trim UTF-8 trailing bytes + ret = len; + } + return ret; +} + +// Trim leading garbage +LPCSTR _bandailtrim(LPCSTR p) +{ + enum { MAX_LENGTH = VNR_TEXT_CAPACITY }; + if (p) + for (int count = 0; *p && count < MAX_LENGTH; count++, p++) + if (!_bandaigarbage_ch(*p)) + return p; + return nullptr; +} +} // unnamed namespae + +static void SpecialPSPHookBandai(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD eax = regof(eax, esp_base); + LPCSTR text = LPCSTR(eax + hp->user_value); + + if (*text) { + //lasttext = text; + text = _bandailtrim(text); + *data = (DWORD)text; + *len = _bandaistrlen(text); + + // Issue: The split value will create lots of threads for Shining Hearts + //*split = regof(ecx, esp_base); // works for Shool Rumble, but mix character name for Shining Hearts + *split = regof(edi, esp_base); // works for Shining Hearts to split character name + } +} + +// 7/22/2014 jichi: This engine works for multiple game? +// It is also observed in Broccoli game ぁ�の�リンスさまっ. +bool InsertBandaiPSPHook() +{ + ConsoleOutput("vnreng: BANDAI PSP: enter"); + + const BYTE bytes[] = { + 0x77, 0x0f, // 13400560 77 0f ja short 13400571 + 0xc7,0x05, XX8, // 13400562 c705 a8aa1001 cc>mov dword ptr ds:[0x110aaa8],0x883decc + 0xe9, XX4, // 1340056c -e9 93fa54f0 jmp 03950004 + 0x8b,0x35, XX4, // 13400571 8b35 78a71001 mov esi,dword ptr ds:[0x110a778] + 0x81,0xc6, 0x01,0x00,0x00,0x00, // 13400577 81c6 01000000 add esi,0x1 + 0x8b,0x05, XX4, // 1340057d 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13400583 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xb8, XX4, // 13400589 0fb6b8 00004007 movzx edi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + 0x8b,0x2d, XX4, // 13400590 8b2d 78a71001 mov ebp,dword ptr ds:[0x110a778] + 0x8d,0x6d, 0x01, // 13400596 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + 0x81,0xff, 0x00,0x00,0x00,0x00 // 13400599 81ff 00000000 cmp edi,0x0 + }; + enum { memory_offset = 3 }; // 13400589 0fb6b8 00004007 movzx edi,byte ptr ds:[eax+0x7400000] + enum { addr_offset = 0x13400589 - 0x13400560 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: BANDAI PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; + //hp.offset = pusha_eax_off - 4; + //hp.split = pusha_ecx_off - 4; + hp.text_fun = SpecialPSPHookBandai; + ConsoleOutput("vnreng: BANDAI PSP: INSERT"); + NewHook(hp, "BANDAI PSP"); + } + + ConsoleOutput("vnreng: BANDAI PSP: leave"); + return addr; +} + +/** 7/22/2014 jichi: Nippon1 PSP engine, 0.9.8 only + * Sample game: ぁ�の�リンスさまっ♪ (0.9.8 only) + * + * Memory address is FIXED. + * Debug method: breakpoint the precomputed address + * + * The data is in (WORD)bp instead of eax. + * bp contains SHIFT-JIS BIG_ENDIAN data. + * + * There is only one text thread. + * + * 134e0553 cc int3 + * 134e0554 77 0f ja short 134e0565 + * 134e0556 c705 a8aa1001 34>mov dword ptr ds:[0x110aaa8],0x8853a34 + * 134e0560 -e9 9ffa03f0 jmp 03520004 + * 134e0565 8b35 74a71001 mov esi,dword ptr ds:[0x110a774] + * 134e056b d1e6 shl esi,1 + * 134e056d c7c7 987db708 mov edi,0x8b77d98 + * 134e0573 03fe add edi,esi + * 134e0575 8b2d 78a71001 mov ebp,dword ptr ds:[0x110a778] + * 134e057b 8bc7 mov eax,edi + * 134e057d 81e0 ffffff3f and eax,0x3fffffff + * 134e0583 66:89a8 00004007 mov word ptr ds:[eax+0x7400000],bp ; jichi: hook here + * 134e058a 8b2d 8c7df70f mov ebp,dword ptr ds:[0xff77d8c] + * 134e0590 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + * 134e0593 892d 8c7df70f mov dword ptr ds:[0xff77d8c],ebp + * 134e0599 8b05 e4a71001 mov eax,dword ptr ds:[0x110a7e4] + * 134e059f c705 74a71001 00>mov dword ptr ds:[0x110a774],0x8b70000 + * 134e05a9 892d 78a71001 mov dword ptr ds:[0x110a778],ebp + * 134e05af 8935 7ca71001 mov dword ptr ds:[0x110a77c],esi + * 134e05b5 8905 a8aa1001 mov dword ptr ds:[0x110aaa8],eax + * 134e05bb 832d c4aa1001 0c sub dword ptr ds:[0x110aac4],0xc + * 134e05c2 -e9 5cfa03f0 jmp 03520023 + */ +// Read text from bp +// TODO: This should be expressed as general hook without extern fun +static void SpecialPSPHookNippon1(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + //LPCSTR text = LPCSTR(esp_base + pusha_ebp_off - 4); // ebp address + LPCSTR text = LPCSTR(esp_base + hp->offset); // dynamic offset, ebp or esi + if (*text) { + *data = (DWORD)text; + *len = !text[0] ? 0 : !text[1] ? 1 : 2; // bp or si has at most two bytes + //*len = ::LeadByteTable[*(BYTE *)text] // TODO: Test leadbytetable + *split = regof(ecx, esp_base); + } +} + +bool InsertNippon1PSPHook() +{ + ConsoleOutput("vnreng: Nippon1 PSP: enter"); + + const BYTE bytes[] = { + //0xcc, // 134e0553 cc int3 + 0x77, 0x0f, // 134e0554 77 0f ja short 134e0565 + 0xc7,0x05, XX8, // 134e0556 c705 a8aa1001 34>mov dword ptr ds:[0x110aaa8],0x8853a34 + 0xe9, XX4, // 134e0560 -e9 9ffa03f0 jmp 03520004 + 0x8b,0x35, XX4, // 134e0565 8b35 74a71001 mov esi,dword ptr ds:[0x110a774] + 0xd1,0xe6, // 134e056b d1e6 shl esi,1 + 0xc7,0xc7, XX4, // 134e056d c7c7 987db708 mov edi,0x8b77d98 + 0x03,0xfe, // 134e0573 03fe add edi,esi + 0x8b,0x2d, XX4, // 134e0575 8b2d 78a71001 mov ebp,dword ptr ds:[0x110a778] + 0x8b,0xc7, // 134e057b 8bc7 mov eax,edi + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 134e057d 81e0 ffffff3f and eax,0x3fffffff + 0x66,0x89,0xa8, XX4, // 134e0583 66:89a8 00004007 mov word ptr ds:[eax+0x7400000],bp ; jichi: hook here + 0x8b,0x2d, XX4, // 134e058a 8b2d 8c7df70f mov ebp,dword ptr ds:[0xff77d8c] + 0x8d,0x6d, 0x01 // 134e0590 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + }; + enum { memory_offset = 3 }; + enum { addr_offset = 0x134e0583 - 0x134e0554 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: Nippon1 PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.offset = pusha_ebp_off - 4; // ebp + hp.type = USING_STRING|NO_CONTEXT; + hp.text_fun = SpecialPSPHookNippon1; + ConsoleOutput("vnreng: Nippon1 PSP: INSERT"); + NewHook(hp, "Nippon1 PSP"); + } + + ConsoleOutput("vnreng: Nippon1 PSP: leave"); + return addr; +} + +/** 7/26/2014 jichi: Alternative Nippon1 PSP engine, 0.9.8 only + * Sample game: 神�悪戯 (0.9.8 only) + * Issue: character name cannot be extracted + * + * Memory address is FIXED. + * Debug method: breakpoint the precomputed address + * + * This function is the one that write the text into the memory. + * + * 13d13e8b 0f92c0 setb al + * 13d13e8e 8bf8 mov edi,eax + * 13d13e90 81ff 00000000 cmp edi,0x0 + * 13d13e96 893d 78a71001 mov dword ptr ds:[0x110a778],edi + * 13d13e9c 8935 dca71001 mov dword ptr ds:[0x110a7dc],esi + * 13d13ea2 0f85 16000000 jnz 13d13ebe + * 13d13ea8 832d c4aa1001 0a sub dword ptr ds:[0x110aac4],0xa + * 13d13eaf c705 a8aa1001 cc>mov dword ptr ds:[0x110aaa8],0x887c2cc + * 13d13eb9 -e9 65c1a3ef jmp 03750023 + * 13d13ebe 832d c4aa1001 0a sub dword ptr ds:[0x110aac4],0xa + * 13d13ec5 e9 0e000000 jmp 13d13ed8 + * 13d13eca 01a8 c28708e9 add dword ptr ds:[eax+0xe90887c2],ebp + * 13d13ed0 4f dec edi + * 13d13ed1 c1a3 ef90cccc cc shl dword ptr ds:[ebx+0xcccc90ef],0xcc ; shift constant out of range 1..31 + * 13d13ed8 77 0f ja short 13d13ee9 + * 13d13eda c705 a8aa1001 a8>mov dword ptr ds:[0x110aaa8],0x887c2a8 + * 13d13ee4 -e9 1bc1a3ef jmp 03750004 + * 13d13ee9 8b05 dca71001 mov eax,dword ptr ds:[0x110a7dc] + * 13d13eef 81e0 ffffff3f and eax,0x3fffffff + * 13d13ef5 0fb7b0 0000c007 movzx esi,word ptr ds:[eax+0x7c00000] + * 13d13efc 8b3d fccd5a10 mov edi,dword ptr ds:[0x105acdfc] + * 13d13f02 8bef mov ebp,edi + * 13d13f04 d1e5 shl ebp,1 + * 13d13f06 81c5 e8cd9a08 add ebp,0x89acde8 + * 13d13f0c 8bc5 mov eax,ebp + * 13d13f0e 81e0 ffffff3f and eax,0x3fffffff + * 13d13f14 66:89b0 2000c007 mov word ptr ds:[eax+0x7c00020],si ; jichi: hook here + * 13d13f1b 8d7f 01 lea edi,dword ptr ds:[edi+0x1] + * 13d13f1e 893d fccd5a10 mov dword ptr ds:[0x105acdfc],edi + * 13d13f24 8b15 dca71001 mov edx,dword ptr ds:[0x110a7dc] + * 13d13f2a 8d52 10 lea edx,dword ptr ds:[edx+0x10] + * 13d13f2d 8b05 e4a71001 mov eax,dword ptr ds:[0x110a7e4] + * 13d13f33 893d 78a71001 mov dword ptr ds:[0x110a778],edi + * 13d13f39 c705 7ca71001 e8>mov dword ptr ds:[0x110a77c],0x89acde8 + * 13d13f43 8935 80a71001 mov dword ptr ds:[0x110a780],esi + * 13d13f49 892d 84a71001 mov dword ptr ds:[0x110a784],ebp + * 13d13f4f 8915 dca71001 mov dword ptr ds:[0x110a7dc],edx + * 13d13f55 8905 a8aa1001 mov dword ptr ds:[0x110aaa8],eax + * 13d13f5b 832d c4aa1001 0b sub dword ptr ds:[0x110aac4],0xb + * 13d13f62 -e9 bcc0a3ef jmp 03750023 + * 13d13f67 90 nop + */ +//static void SpecialPSPHookNippon2(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +//{ +// LPCSTR text = LPCSTR(esp_base + pusha_esi_off - 4); // esi address +// if (*text) { +// *data = (DWORD)text; +// *len = !text[0] ? 0 : !text[1] ? 1 : 2; // bp has at most two bytes +// //*len = ::LeadByteTable[*(BYTE *)text] // TODO: Test leadbytetable +// *split = regof(ecx, esp_base); +// } +//} + +// 8/13/2014: 5pb might crash on 0.9.9. +bool InsertNippon2PSPHook() +{ + ConsoleOutput("vnreng: Nippon2 PSP: enter"); + + const BYTE bytes[] = { + 0xe9, XX4, // 13d13ee4 -e9 1bc1a3ef jmp 03750004 + 0x8b,0x05, XX4, // 13d13ee9 8b05 dca71001 mov eax,dword ptr ds:[0x110a7dc] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13d13eef 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb7,0xb0, XX4, // 13d13ef5 0fb7b0 0000c007 movzx esi,word ptr ds:[eax+0x7c00000] + 0x8b,0x3d, XX4, // 13d13efc 8b3d fccd5a10 mov edi,dword ptr ds:[0x105acdfc] + 0x8b,0xef, // 13d13f02 8bef mov ebp,edi + 0xd1,0xe5, // 13d13f04 d1e5 shl ebp,1 + 0x81,0xc5, XX4, // 13d13f06 81c5 e8cd9a08 add ebp,0x89acde8 + 0x8b,0xc5, // 13d13f0c 8bc5 mov eax,ebp + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13d13f0e 81e0 ffffff3f and eax,0x3fffffff + 0x66,0x89,0xb0 //, XX4 // 13d13f14 66:89b0 2000c007 mov word ptr ds:[eax+0x7c00020],si ; jichi: hook here + }; + enum { memory_offset = 3 }; + enum { addr_offset = sizeof(bytes) - memory_offset }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: Nippon2 PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.offset = pusha_esi_off - 4; // esi + hp.type = USING_STRING|NO_CONTEXT; + hp.text_fun = SpecialPSPHookNippon1; + ConsoleOutput("vnreng: Nippon2 PSP: INSERT"); + NewHook(hp, "Nippon2 PSP"); + } + + ConsoleOutput("vnreng: Nippon2 PSP: leave"); + return addr; +} + +/** 7/26/2014 jichi Broccoli PSP engine, 0.9.8, 0.9.9 + * Sample game: 明治東亰恋伽 (works on both 0.9.8, 0.9.9) + * + * Memory address is FIXED. + * Debug method: breakpoint the memory address + * + * The data is in (WORD)dl in bytes. + * + * There are two text threads. + * Only one is correct. + * + * 13d26cab cc int3 + * 13d26cac 77 0f ja short 13d26cbd + * 13d26cae c705 a8aa1001 24>mov dword ptr ds:[0x110aaa8],0x886a724 + * 13d26cb8 -e9 4793ccef jmp 039f0004 + * 13d26cbd 8b35 dca71001 mov esi,dword ptr ds:[0x110a7dc] + * 13d26cc3 8db6 60feffff lea esi,dword ptr ds:[esi-0x1a0] + * 13d26cc9 8b3d e4a71001 mov edi,dword ptr ds:[0x110a7e4] + * 13d26ccf 8bc6 mov eax,esi + * 13d26cd1 81e0 ffffff3f and eax,0x3fffffff + * 13d26cd7 89b8 9001c007 mov dword ptr ds:[eax+0x7c00190],edi + * 13d26cdd 8b2d 80a71001 mov ebp,dword ptr ds:[0x110a780] + * 13d26ce3 0fbfed movsx ebp,bp + * 13d26ce6 8bd6 mov edx,esi + * 13d26ce8 8bce mov ecx,esi + * 13d26cea 03cd add ecx,ebp + * 13d26cec 8935 dca71001 mov dword ptr ds:[0x110a7dc],esi + * 13d26cf2 33c0 xor eax,eax + * 13d26cf4 3bd1 cmp edx,ecx + * 13d26cf6 0f92c0 setb al + * 13d26cf9 8bf0 mov esi,eax + * 13d26cfb 81fe 00000000 cmp esi,0x0 + * 13d26d01 8935 70a71001 mov dword ptr ds:[0x110a770],esi + * 13d26d07 890d 74a71001 mov dword ptr ds:[0x110a774],ecx + * 13d26d0d 892d 80a71001 mov dword ptr ds:[0x110a780],ebp + * 13d26d13 8915 8ca71001 mov dword ptr ds:[0x110a78c],edx + * 13d26d19 0f85 16000000 jnz 13d26d35 + * 13d26d1f 832d c4aa1001 08 sub dword ptr ds:[0x110aac4],0x8 + * 13d26d26 e9 b9000000 jmp 13d26de4 + * 13d26d2b 0158 a7 add dword ptr ds:[eax-0x59],ebx + * 13d26d2e 8608 xchg byte ptr ds:[eax],cl + * 13d26d30 -e9 ee92ccef jmp 039f0023 + * 13d26d35 832d c4aa1001 08 sub dword ptr ds:[0x110aac4],0x8 + * 13d26d3c e9 0b000000 jmp 13d26d4c + * 13d26d41 0144a7 86 add dword ptr ds:[edi-0x7a],eax + * 13d26d45 08e9 or cl,ch + * 13d26d47 d892 ccef9077 fcom dword ptr ds:[edx+0x7790efcc] + * 13d26d4d 0fc7 ??? ; unknown command + * 13d26d4f 05 a8aa1001 add eax,0x110aaa8 + * 13d26d54 44 inc esp + * 13d26d55 a7 cmps dword ptr ds:[esi],dword ptr es:[ed> + * 13d26d56 8608 xchg byte ptr ds:[eax],cl + * 13d26d58 -e9 a792ccef jmp 039f0004 + * 13d26d5d 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + * 13d26d63 81e0 ffffff3f and eax,0x3fffffff + * 13d26d69 0fb6b0 0000c007 movzx esi,byte ptr ds:[eax+0x7c00000] + * 13d26d70 8b3d 7ca71001 mov edi,dword ptr ds:[0x110a77c] + * 13d26d76 8d7f 01 lea edi,dword ptr ds:[edi+0x1] + * 13d26d79 8b05 8ca71001 mov eax,dword ptr ds:[0x110a78c] + * 13d26d7f 81e0 ffffff3f and eax,0x3fffffff + * 13d26d85 8bd6 mov edx,esi + * 13d26d87 8890 0000c007 mov byte ptr ds:[eax+0x7c00000],dl ; jichi: hook here, get byte from dl + * 13d26d8d 8b2d 8ca71001 mov ebp,dword ptr ds:[0x110a78c] + * 13d26d93 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + * 13d26d96 81fe 00000000 cmp esi,0x0 + * 13d26d9c 893d 7ca71001 mov dword ptr ds:[0x110a77c],edi + * 13d26da2 8935 88a71001 mov dword ptr ds:[0x110a788],esi + * 13d26da8 892d 8ca71001 mov dword ptr ds:[0x110a78c],ebp + * 13d26dae 0f84 16000000 je 13d26dca + * 13d26db4 832d c4aa1001 05 sub dword ptr ds:[0x110aac4],0x5 + * 13d26dbb e9 f48b0100 jmp 13d3f9b4 + * 13d26dc0 0138 add dword ptr ds:[eax],edi + * 13d26dc2 a7 cmps dword ptr ds:[esi],dword ptr es:[ed> + * 13d26dc3 8608 xchg byte ptr ds:[eax],cl + * 13d26dc5 -e9 5992ccef jmp 039f0023 + * 13d26dca 832d c4aa1001 05 sub dword ptr ds:[0x110aac4],0x5 + * 13d26dd1 e9 0e000000 jmp 13d26de4 + * 13d26dd6 0158 a7 add dword ptr ds:[eax-0x59],ebx + * 13d26dd9 8608 xchg byte ptr ds:[eax],cl + * 13d26ddb -e9 4392ccef jmp 039f0023 + * 13d26de0 90 nop + * 13d26de1 cc int3 + */ + +// New line character for Broccoli games is '^' +static inline bool _broccoligarbage_ch(char c) { return c == '^'; } + +// Read text from dl +static void SpecialPSPHookBroccoli(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD text = esp_base + pusha_edx_off - 4; // edx address + char c = *(LPCSTR)text; + if (c && !_broccoligarbage_ch(c)) { + *data = text; + *len = 1; + *split = regof(ecx, esp_base); + } +} + +bool InsertBroccoliPSPHook() +{ + ConsoleOutput("vnreng: Broccoli PSP: enter"); + + const BYTE bytes[] = { + 0x0f,0xc7, // 13d26d4d 0fc7 ??? ; unknown command + 0x05, XX4, // 13d26d4f 05 a8aa1001 add eax,0x110aaa8 + 0x44, // 13d26d54 44 inc esp + 0xa7, // 13d26d55 a7 cmps dword ptr ds:[esi],dword ptr es:[ed> + 0x86,0x08, // 13d26d56 8608 xchg byte ptr ds:[eax],cl + 0xe9, XX4, // 13d26d58 -e9 a792ccef jmp 039f0004 + 0x8b,0x05, XX4, // 13d26d5d 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + // Following pattern is not sufficient + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13d26d63 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xb0, XX4, // 13d26d69 0fb6b0 0000c007 movzx esi,byte ptr ds:[eax+0x7c00000] + 0x8b,0x3d, XX4, // 13d26d70 8b3d 7ca71001 mov edi,dword ptr ds:[0x110a77c] + 0x8d,0x7f, 0x01, // 13d26d76 8d7f 01 lea edi,dword ptr ds:[edi+0x1] + 0x8b,0x05, XX4, // 13d26d79 8b05 8ca71001 mov eax,dword ptr ds:[0x110a78c] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13d26d7f 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xd6, // 13d26d85 8bd6 mov edx,esi + 0x88,0x90, XX4, // 13d26d87 8890 0000c007 mov byte ptr ds:[eax+0x7c00000],dl ; jichi: hook here, get byte from dl + 0x8b,0x2d, XX4, // 13d26d8d 8b2d 8ca71001 mov ebp,dword ptr ds:[0x110a78c] + 0x8d,0x6d, 0x01, // 13d26d93 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + 0x81,0xfe, 0x00,0x00,0x00,0x00 // 13d26d96 81fe 00000000 cmp esi,0x0 + }; + enum { addr_offset = 0x13d26d87 - 0x13d26d4d }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: Broccoli PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; + hp.text_fun = SpecialPSPHookBroccoli; + //GROWL_DWORD(hp.address); + ConsoleOutput("vnreng: Broccoli PSP: INSERT"); + NewHook(hp, "Broccoli PSP"); + } + + ConsoleOutput("vnreng: Broccoli PSP: leave"); + return addr; +} + +/** 7/26/2014 jichi Otomate PSP engine, 0.9.8 only, 0.9.9 not work + * Replaced by Otomate PPSSPP on 0.9.9. + * + * Sample game: クロノスタシア + * Sample game: フォトカ�(repetition) + * + * Not work on 0.9.9: Amnesia Crowd + * + * The instruction pattern also exist in 0.9.9. But the function is not called. + * + * Memory address is FIXED. + * Debug method: breakpoint the memory address + * + * The memory access of the function below is weird that the accessed value is 2 bytes after the real text. + * + * PPSSPP 0.9.8, クロノスタシア + * 13c00fe1 cc int3 + * 13c00fe2 cc int3 + * 13c00fe3 cc int3 + * 13c00fe4 77 0f ja short 13c00ff5 + * 13c00fe6 c705 a8aa1001 30>mov dword ptr ds:[0x110aaa8],0x884b330 + * 13c00ff0 -e9 0ff0edf2 jmp 06ae0004 + * 13c00ff5 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 13c00ffb 81e0 ffffff3f and eax,0x3fffffff + * 13c01001 0fbeb0 0000c007 movsx esi,byte ptr ds:[eax+0x7c00000] ; jichi: hook here + * 13c01008 81fe 00000000 cmp esi,0x0 ; jichi: hook here, get the esi value + * 13c0100e 8935 80a71001 mov dword ptr ds:[0x110a780],esi + * 13c01014 0f84 25000000 je 13c0103f + * 13c0101a 8b35 78a71001 mov esi,dword ptr ds:[0x110a778] + * 13c01020 8d76 01 lea esi,dword ptr ds:[esi+0x1] + * 13c01023 8935 78a71001 mov dword ptr ds:[0x110a778],esi + * 13c01029 832d c4aa1001 03 sub dword ptr ds:[0x110aac4],0x3 + * 13c01030 ^e9 afffffff jmp 13c00fe4 + * 13c01035 0130 add dword ptr ds:[eax],esi + * 13c01037 b3 84 mov bl,0x84 + * 13c01039 08e9 or cl,ch + * 13c0103b e4 ef in al,0xef ; i/o command + * 13c0103d ed in eax,dx ; i/o command + * 13c0103e f2: prefix repne: ; superfluous prefix + * 13c0103f 832d c4aa1001 03 sub dword ptr ds:[0x110aac4],0x3 + * 13c01046 e9 0d000000 jmp 13c01058 + * 13c0104b 013cb3 add dword ptr ds:[ebx+esi*4],edi + * 13c0104e 8408 test byte ptr ds:[eax],cl + * 13c01050 -e9 ceefedf2 jmp 06ae0023 + * 13c01055 90 nop + * 13c01056 cc int3 + * 13c01057 cc int3 + */ +// TODO: is reverse_strlen a better choice? +// Read text from esi +static void SpecialPSPHookOtomate(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + //static uniquemap uniq; + DWORD eax = regof(eax, esp_base); + LPCSTR text = LPCSTR(eax + hp->user_value - 2); // -2 to read 1 word more from previous location + if (*text) { + *split = regof(ecx, esp_base); // this would cause lots of texts, but it works for all games + //*split = regof(ecx, esp_base) & 0xff00; // only use higher bits + *data = (DWORD)text; + size_t sz = ::strlen(text); + *len = sz == 3 ? 3 : 1; // handling the last two bytes + } +} + +bool InsertOtomatePSPHook() +{ + ConsoleOutput("vnreng: Otomate PSP: enter"); + const BYTE bytes[] = { + 0x77, 0x0f, // 13c00fe4 77 0f ja short 13c00ff5 + 0xc7,0x05, XX8, // 13c00fe6 c705 a8aa1001 30>mov dword ptr ds:[0x110aaa8],0x884b330 + 0xe9, XX4, // 13c00ff0 -e9 0ff0edf2 jmp 06ae0004 + 0x8b,0x05, XX4, // 13c00ff5 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13c00ffb 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xbe,0xb0, XX4, // 13c01001 0fbeb0 0000c007 movsx esi,byte ptr ds:[eax+0x7c00000] ; jichi: hook here + 0x81,0xfe, 0x00,0x00,0x00,0x00, // 13c01008 81fe 00000000 cmp esi,0x0 + 0x89,0x35, XX4, // 13c0100e 8935 80a71001 mov dword ptr ds:[0x110a780],esi + 0x0f,0x84, 0x25,0x00,0x00,0x00, // 13c01014 0f84 25000000 je 13c0103f + 0x8b,0x35, XX4, // 13c0101a 8b35 78a71001 mov esi,dword ptr ds:[0x110a778] + 0x8d,0x76, 0x01, // 13c01020 8d76 01 lea esi,dword ptr ds:[esi+0x1] + 0x89,0x35, XX4, // 13c01023 8935 78a71001 mov dword ptr ds:[0x110a778],esi + 0x83,0x2d, XX4, 0x03 // 13c01029 832d c4aa1001 03 sub dword ptr ds:[0x110aac4],0x3 + }; + enum { memory_offset = 3 }; + //enum { addr_offset = 0x13c01008 - 0x13c00fe4 }; + enum { addr_offset = 0x13c01001- 0x13c00fe4 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + //GROWL_DWORD(addr); + if (!addr) + ConsoleOutput("vnreng: Otomate PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|NO_CONTEXT; + hp.text_fun = SpecialPSPHookOtomate; + ConsoleOutput("vnreng: Otomate PSP: INSERT"); + NewHook(hp, "Otomate PSP"); + } + + ConsoleOutput("vnreng: Otomate PSP: leave"); + return addr; +} + +/** 7/29/2014 jichi Otomate PPSSPP 0.9.9 + * Sample game: Amnesia Crowd + * Sample game: Amnesia Later + * + * 006db4af cc int3 + * 006db4b0 8b15 b8ebaf00 mov edx,dword ptr ds:[0xafebb8] ; ppssppwi.01134988 + * 006db4b6 56 push esi + * 006db4b7 8b42 10 mov eax,dword ptr ds:[edx+0x10] + * 006db4ba 25 ffffff3f and eax,0x3fffffff + * 006db4bf 0305 94411301 add eax,dword ptr ds:[0x1134194] + * 006db4c5 8d70 01 lea esi,dword ptr ds:[eax+0x1] + * 006db4c8 8a08 mov cl,byte ptr ds:[eax] ; jichi: hook here, get text in [eax] + * 006db4ca 40 inc eax + * 006db4cb 84c9 test cl,cl + * 006db4cd ^75 f9 jnz short ppssppwi.006db4c8 + * 006db4cf 2bc6 sub eax,esi + * 006db4d1 8942 08 mov dword ptr ds:[edx+0x8],eax + * 006db4d4 5e pop esi + * 006db4d5 8d0485 07000000 lea eax,dword ptr ds:[eax*4+0x7] + * 006db4dc c3 retn + * 006db4dd cc int3 + */ +static void SpecialPPSSPPHookOtomate(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + // 006db4b7 8b42 10 mov eax,dword ptr ds:[edx+0x10] ; jichi: hook here + // 006db4ba 25 ffffff3f and eax,0x3fffffff + // 006db4bf 0305 94411301 add eax,dword ptr ds:[0x1134194]; jichi: ds offset + // 006db4c5 8d70 01 lea esi,dword ptr ds:[eax+0x1] + DWORD edx = regof(edx, esp_base); + DWORD eax = *(DWORD *)(edx + 0x10); + eax &= 0x3fffffff; + eax += *(DWORD *)hp->user_value; + + //DWORD eax = regof(eax, esp_base); + LPCSTR text = LPCSTR(eax); + if (*text) { + text = _bandailtrim(text); // the same as bandai PSP + *data = (DWORD)text; + *len = _bandaistrlen(text); + + *split = regof(ecx, esp_base); // the same as Otomate PSP hook + //DWORD ecx = regof(ecx, esp_base); // the same as Otomate PSP hook + //*split = ecx ? ecx : (FIXED_SPLIT_VALUE << 2); + //*split = ecx & 0xffffff00; // skip cl which is used + } +} +bool InsertOtomatePPSSPPHook() +{ + ULONG startAddress, stopAddress; + if (!NtInspect::getProcessMemoryRange(&startAddress, &stopAddress)) { // need accurate stopAddress + ConsoleOutput("vnreng: Otomate PPSSPP: failed to get memory range"); + return false; + } + ConsoleOutput("vnreng: Otomate PPSSPP: enter"); + const BYTE bytes[] = { + 0x8b,0x15, XX4, // 006db4b0 8b15 b8ebaf00 mov edx,dword ptr ds:[0xafebb8] ; ppssppwi.01134988 + 0x56, // 006db4b6 56 push esi + 0x8b,0x42, 0x10, // 006db4b7 8b42 10 mov eax,dword ptr ds:[edx+0x10] ; jichi: hook here + 0x25, 0xff,0xff,0xff,0x3f, // 006db4ba 25 ffffff3f and eax,0x3fffffff + 0x03,0x05, XX4, // 006db4bf 0305 94411301 add eax,dword ptr ds:[0x1134194]; jichi: ds offset + 0x8d,0x70, 0x01, // 006db4c5 8d70 01 lea esi,dword ptr ds:[eax+0x1] + 0x8a,0x08, // 006db4c8 8a08 mov cl,byte ptr ds:[eax] ; jichi: hook here + 0x40, // 006db4ca 40 inc eax + 0x84,0xc9, // 006db4cb 84c9 test cl,cl + 0x75, 0xf9, // 006db4cd ^75 f9 jnz short ppssppwi.006db4c8 + 0x2b,0xc6, // 006db4cf 2bc6 sub eax,esi + 0x89,0x42, 0x08, // 006db4d1 8942 08 mov dword ptr ds:[edx+0x8],eax + 0x5e, // 006db4d4 5e pop esi + 0x8d,0x04,0x85, 0x07,0x00,0x00,0x00 // 006db4d5 8d0485 07000000 lea eax,dword ptr ds:[eax*4+0x7] + }; + //enum { addr_offset = 0x006db4c8 - 0x006db4b0 }; + enum { addr_offset = 0x006db4b7 - 0x006db4b0 }; + enum { ds_offset = 0x006db4bf - 0x006db4b0 + 2 }; + + DWORD addr = SafeMatchBytes(bytes, sizeof(bytes), startAddress, stopAddress); + //GROWL_DWORD(addr); + if (!addr) + ConsoleOutput("vnreng: Otomate PPSSPP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(addr + ds_offset); // this is the address after ds:[] + hp.type = USING_STRING|NO_CONTEXT; + hp.text_fun = SpecialPPSSPPHookOtomate; + ConsoleOutput("vnreng: Otomate PPSSPP: INSERT"); + NewHook(hp, "Otomate PPSSPP"); + } + + ConsoleOutput("vnreng: Otomate PPSSPP: leave"); + return addr; +} + +#if 0 // FIXME: I am not able to find stable pattern in PSP 0.9.9.1 + +/** 9/21/2014 jichi Otomate PPSSPP 0.9.9.1 + * Sample game: Amnesia Later + * + * There are four fixed memory addresses. + * The two out of four can be used. + * (The other twos have loops or cannot be debugged). + * + * This function is the same as PPSSPP 0.9.9.1 (?). + * + * 14039126 cc int3 + * 14039127 cc int3 + * 14039128 77 0f ja short 14039139 + * 1403912a c705 988e1301 3c>mov dword ptr ds:[0x1138e98],0x8922c3c + * 14039134 -e9 cb6e83ef jmp 03870004 + * 14039139 8b05 688b1301 mov eax,dword ptr ds:[0x1138b68] + * 1403913f 81e0 ffffff3f and eax,0x3fffffff + * 14039145 0fbeb0 00000008 movsx esi,byte ptr ds:[eax+0x8000000] ; jichi: text accessed, but looped + * 1403914c 8b05 6c8b1301 mov eax,dword ptr ds:[0x1138b6c] + * 14039152 81e0 ffffff3f and eax,0x3fffffff + * 14039158 0fbeb8 00000008 movsx edi,byte ptr ds:[eax+0x8000000] + * 1403915f 3bf7 cmp esi,edi + * 14039161 8935 748b1301 mov dword ptr ds:[0x1138b74],esi + * 14039167 893d 7c8b1301 mov dword ptr ds:[0x1138b7c],edi + * 1403916d 0f84 2f000000 je 140391a2 + * 14039173 8b05 688b1301 mov eax,dword ptr ds:[0x1138b68] + * 14039179 81e0 ffffff3f and eax,0x3fffffff + * 1403917f 0fb6b0 00000008 movzx esi,byte ptr ds:[eax+0x8000000] ; jichi: hook here + * 14039186 8935 608b1301 mov dword ptr ds:[0x1138b60],esi + * 1403918c 832d b48e1301 04 sub dword ptr ds:[0x1138eb4],0x4 + * 14039193 e9 24000000 jmp 140391bc + * 14039198 0170 2c add dword ptr ds:[eax+0x2c],esi + * 1403919b 92 xchg eax,edx + * 1403919c 08e9 or cl,ch + * 1403919e 816e 83 ef832db4 sub dword ptr ds:[esi-0x7d],0xb42d83ef + * 140391a5 8e13 mov ss,word ptr ds:[ebx] ; modification of segment register + * 140391a7 0104e9 add dword ptr ds:[ecx+ebp*8],eax + * 140391aa b2 59 mov dl,0x59 + * 140391ac 0000 add byte ptr ds:[eax],al + * 140391ae 014c2c 92 add dword ptr ss:[esp+ebp-0x6e],ecx + * 140391b2 08e9 or cl,ch + * 140391b4 6b6e 83 ef imul ebp,dword ptr ds:[esi-0x7d],-0x11 + * 140391b8 90 nop + * 140391b9 cc int3 + * 140391ba cc int3 + */ +// Get bytes in esi +static void SpecialPSPHookOtomate2(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + //static uniquemap uniq; + DWORD text = esp_base + pusha_esi_off - 4; + if (*(LPCSTR *)text) { + *split = regof(ecx, esp_base); // this would cause lots of texts, but it works for all games + *data = text; + *len = 1; + } +} + +bool InsertOtomate2PSPHook() +{ + ConsoleOutput("vnreng: Otomate2 PSP: enter"); + const BYTE bytes[] = { + 0x77, 0x0f, // 14039128 77 0f ja short 14039139 + 0xc7,0x05, XX8, // 1403912a c705 988e1301 3c>mov dword ptr ds:[0x1138e98],0x8922c3c + 0xe9, XX4, // 14039134 -e9 cb6e83ef jmp 03870004 + 0x8b,0x05, XX4, // 14039139 8b05 688b1301 mov eax,dword ptr ds:[0x1138b68] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1403913f 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xbe,0xb0, XX4, // 14039145 0fbeb0 00000008 movsx esi,byte ptr ds:[eax+0x8000000] ; jichi: text accessed, but looped + 0x8b,0x05, XX4, // 1403914c 8b05 6c8b1301 mov eax,dword ptr ds:[0x1138b6c] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 14039152 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xbe,0xb8, XX4, // 14039158 0fbeb8 00000008 movsx edi,byte ptr ds:[eax+0x8000000] + 0x3b,0xf7, // 1403915f 3bf7 cmp esi,edi + 0x89,0x35, XX4, // 14039161 8935 748b1301 mov dword ptr ds:[0x1138b74],esi + 0x89,0x3d, XX4, // 14039167 893d 7c8b1301 mov dword ptr ds:[0x1138b7c],edi + 0x0f,0x84, 0x2f,0x00,0x00,0x00, // 1403916d 0f84 2f000000 je 140391a2 + + //0x8b,0x05, XX4, // 14039173 8b05 688b1301 mov eax,dword ptr ds:[0x1138b68] + //0x81,0xe0, 0xff,0xff,0xff,0x3f, // 14039179 81e0 ffffff3f and eax,0x3fffffff + //0x0f,0xb6,0xb0, XX4, // 1403917f 0fb6b0 00000008 movzx esi,byte ptr ds:[eax+0x8000000] ; jichi: text accessed + //0x89,0x35, XX4, // 14039186 8935 608b1301 mov dword ptr ds:[0x1138b60],esi ; jichi: hook here, get lower bytes in esi + //0x83,0x2d, XX4, 0x04 // 1403918c 832d b48e1301 04 sub dword ptr ds:[0x1138eb4],0x4 + }; + enum { addr_offset = 0x14039186 - 0x14039128 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) { + ConsoleOutput("vnreng: Otomate2 PSP: leave: first pattern not found"); + return false; + } + addr += addr_offset; + + //0x89,0x35, XX4, // 14039186 8935 608b1301 mov dword ptr ds:[0x1138b60],esi ; jichi: hook here, get lower bytes in esi + enum : WORD { mov_esi = 0x3589 }; + if (*(WORD *)addr != mov_esi) { + ConsoleOutput("vnreng: Otomate2 PSP: leave: second pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = addr; + hp.type = USING_STRING|NO_CONTEXT; + hp.text_fun = SpecialPSPHookOtomate2; + ConsoleOutput("vnreng: Otomate2 PSP: INSERT"); + NewHook(hp, "Otomate PSP"); + + ConsoleOutput("vnreng: Otomate2 PSP: leave"); + return addr; +} + +#endif // 0 + +/** 7/27/2014 jichi Intense.jp PSP engine, 0.9.8, 0.9.9, + * Though Otomate can work, it cannot work line by line. + * + * Sample game: 寮�のサクリファイス work on 0.9.8 & 0.9.9 + * This hook is only for intro graphic painting + * + * Memory address is FIXED. + * Debug method: predict and breakpoint the memory address + * + * There are two matches in the memory, and only one function accessing them. + * The memory is accessed by words. + * + * The memory and hooked function is as follows. + * + * 09dfee77 88 c3 82 a2 95 a3 82 cc 89 9c 92 ea 82 c5 81 41 暗い淵の奥底で� * 09dfee87 92 e1 82 ad 81 41 8f ac 82 b3 82 ad 81 41 8b bf 低く、小さく〟� + * 09dfee97 82 ad 81 42 2a 70 0a 82 b1 82 ea 82 cd 81 41 8c く�p.これは、� + * 09dfeea7 db 93 ae 81 63 81 48 2a 70 0a 82 c6 82 e0 82 b7 �動…p.ともす + * 09dfeeb7 82 ea 82 ce 95 b7 82 ab 93 a6 82 b5 82 c4 82 b5 れ�聞き送�て� * 09dfeec7 82 dc 82 a2 82 bb 82 a4 82 c8 81 41 2a 70 0a 8f まぁ�ぁ��p.・ + * 09dfeed7 ac 82 b3 82 ad 81 41 8e e3 81 58 82 b5 82 ad 81 �さく、弱、�く� + * 09dfeee7 41 95 73 8a 6d 82 a9 82 c8 89 b9 81 42 00 00 00 a不確かな音�.. + * 09dfeef7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + * 09dfee07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + * + * 13472227 90 nop + * 13472228 77 0f ja short 13472239 + * 1347222a c705 a8aa1001 20>mov dword ptr ds:[0x110aaa8],0x884ce20 + * 13472234 -e9 cbdd16f0 jmp 035e0004 + * 13472239 8b05 a8a71001 mov eax,dword ptr ds:[0x110a7a8] + * 1347223f 81e0 ffffff3f and eax,0x3fffffff + * 13472245 8bb0 30004007 mov esi,dword ptr ds:[eax+0x7400030] + * 1347224b 8b3d 84a71001 mov edi,dword ptr ds:[0x110a784] + * 13472251 81c7 01000000 add edi,0x1 + * 13472257 8bee mov ebp,esi + * 13472259 032d 84a71001 add ebp,dword ptr ds:[0x110a784] + * 1347225f 8bc5 mov eax,ebp + * 13472261 81e0 ffffff3f and eax,0x3fffffff + * 13472267 0fbe90 00004007 movsx edx,byte ptr ds:[eax+0x7400000] ; jichi: hook here + * 1347226e 8b05 a8a71001 mov eax,dword ptr ds:[0x110a7a8] + * 13472274 81e0 ffffff3f and eax,0x3fffffff + * 1347227a 89b8 38004007 mov dword ptr ds:[eax+0x7400038],edi + * 13472280 8bea mov ebp,edx + * 13472282 81e5 ff000000 and ebp,0xff + * 13472288 81fa 0a000000 cmp edx,0xa + * 1347228e c705 70a71001 0a>mov dword ptr ds:[0x110a770],0xa + * 13472298 8915 74a71001 mov dword ptr ds:[0x110a774],edx + * 1347229e 893d 78a71001 mov dword ptr ds:[0x110a778],edi + * 134722a4 892d 7ca71001 mov dword ptr ds:[0x110a77c],ebp + * 134722aa 8935 80a71001 mov dword ptr ds:[0x110a780],esi + * 134722b0 0f85 16000000 jnz 134722cc + * 134722b6 832d c4aa1001 08 sub dword ptr ds:[0x110aac4],0x8 + * 134722bd e9 02680000 jmp 13478ac4 + * 134722c2 01ec add esp,ebp + * 134722c4 ce into + * 134722c5 8408 test byte ptr ds:[eax],cl + * 134722c7 -e9 57dd16f0 jmp 035e0023 + * 134722cc 832d c4aa1001 08 sub dword ptr ds:[0x110aac4],0x8 + * 134722d3 e9 0c000000 jmp 134722e4 + * 134722d8 0140 ce add dword ptr ds:[eax-0x32],eax + * 134722db 8408 test byte ptr ds:[eax],cl + * 134722dd -e9 41dd16f0 jmp 035e0023 + * 134722e2 90 nop + * 134722e3 cc int3 + */ +// Read text from esi +static void SpecialPSPHookIntense(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD eax = regof(eax, esp_base); + DWORD text = eax + hp->user_value; + if (BYTE c = *(BYTE *)text) { // unsigned char + *data = text; + *len = ::LeadByteTable[c]; // 1 or 2 + //*split = regof(ecx, esp_base); // cause scenario text to split + //*split = regof(edx, esp_base); // cause scenario text to split + + //*split = regof(ebx, esp_base); // works, but floating value + *split = FIXED_SPLIT_VALUE * 3; + } +} +bool InsertIntensePSPHook() +{ + ConsoleOutput("vnreng: Intense PSP: enter"); + const BYTE bytes[] = { + 0x77, 0x0f, // 13472228 77 0f ja short 13472239 + 0xc7,0x05, XX8, // 1347222a c705 a8aa1001 20>mov dword ptr ds:[0x110aaa8],0x884ce20 + 0xe9, XX4, // 13472234 -e9 cbdd16f0 jmp 035e0004 + 0x8b,0x05, XX4, // 13472239 8b05 a8a71001 mov eax,dword ptr ds:[0x110a7a8] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1347223f 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xb0, XX4, // 13472245 8bb0 30004007 mov esi,dword ptr ds:[eax+0x7400030] + 0x8b,0x3d, XX4, // 1347224b 8b3d 84a71001 mov edi,dword ptr ds:[0x110a784] + 0x81,0xc7, 0x01,0x00,0x00,0x00, // 13472251 81c7 01000000 add edi,0x1 + 0x8b,0xee, // 13472257 8bee mov ebp,esi + 0x03,0x2d, XX4, // 13472259 032d 84a71001 add ebp,dword ptr ds:[0x110a784] + 0x8b,0xc5, // 1347225f 8bc5 mov eax,ebp + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13472261 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xbe,0x90, XX4, // 13472267 0fbe90 00004007 movsx edx,byte ptr ds:[eax+0x7400000] ; jichi: hook here + 0x8b,0x05, XX4, // 1347226e 8b05 a8a71001 mov eax,dword ptr ds:[0x110a7a8] + 0x81,0xe0, 0xff,0xff,0xff,0x3f // 13472274 81e0 ffffff3f and eax,0x3fffffff + }; + enum { memory_offset = 3 }; + enum { addr_offset = 0x13472267 - 0x13472228 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: Intense PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|NO_CONTEXT; + hp.text_fun = SpecialPSPHookIntense; + ConsoleOutput("vnreng: Intense PSP: INSERT"); + NewHook(hp, "Intense PSP"); + } + + ConsoleOutput("vnreng: Intense PSP: leave"); + return addr; +} + +/** 8/12/2014 jichi Konami.jp PSP engine, 0.9.8, 0.9.9, + * Though Alchemist/Otomate can work, it has bad split that creates too many threads. + * + * Sample game: 幻想水滸�紡がれし百年の�on 0.9.8, 0.9.9 + * + * Memory address is FIXED. + * But hardware accesses are looped. + * Debug method: predict and breakpoint the memory address + * + * There are two matches in the memory. + * Three looped functions are as follows. + * I randomply picked the first one. + * + * It cannot extract character names. + * + * 14178f73 cc int3 + * 14178f74 77 0f ja short 14178f85 + * 14178f76 c705 c84c1301 a4>mov dword ptr ds:[0x1134cc8],0x88129a4 + * 14178f80 -e9 7f7071ef jmp 03890004 + * 14178f85 8b05 c8491301 mov eax,dword ptr ds:[0x11349c8] + * 14178f8b 81e0 ffffff3f and eax,0x3fffffff + * 14178f91 0fbeb0 00000008 movsx esi,byte ptr ds:[eax+0x8000000] ; jichi: hook here, loop + * 14178f98 81fe 40000000 cmp esi,0x40 + * 14178f9e 8935 98491301 mov dword ptr ds:[0x1134998],esi + * 14178fa4 c705 9c491301 40>mov dword ptr ds:[0x113499c],0x40 + * 14178fae 0f85 2f000000 jnz 14178fe3 + * 14178fb4 8b05 c8491301 mov eax,dword ptr ds:[0x11349c8] + * 14178fba 81e0 ffffff3f and eax,0x3fffffff + * 14178fc0 0fbeb0 01000008 movsx esi,byte ptr ds:[eax+0x8000001] + * 14178fc7 8935 98491301 mov dword ptr ds:[0x1134998],esi + * 14178fcd 832d e44c1301 04 sub dword ptr ds:[0x1134ce4],0x4 + * 14178fd4 c705 c84c1301 d0>mov dword ptr ds:[0x1134cc8],0x88129d0 + * 14178fde -e9 407071ef jmp 03890023 + * 14178fe3 832d e44c1301 04 sub dword ptr ds:[0x1134ce4],0x4 + * 14178fea e9 0d000000 jmp 14178ffc + * 14178fef 01b429 8108e92a add dword ptr ds:[ecx+ebp+0x2ae90881],es> + * 14178ff6 70 71 jo short 14179069 + * 14178ff8 ef out dx,eax ; i/o command + * 14178ff9 90 nop + * 14178ffa cc int3 + * + * 1417a18c 77 0f ja short 1417a19d + * 1417a18e c705 c84c1301 78>mov dword ptr ds:[0x1134cc8],0x8818378 + * 1417a198 -e9 675e71ef jmp 03890004 + * 1417a19d 8b05 c8491301 mov eax,dword ptr ds:[0x11349c8] + * 1417a1a3 81e0 ffffff3f and eax,0x3fffffff + * 1417a1a9 0fbeb0 00000008 movsx esi,byte ptr ds:[eax+0x8000000] ; jichi: hook here, loop + * 1417a1b0 81fe 0a000000 cmp esi,0xa + * 1417a1b6 8935 98491301 mov dword ptr ds:[0x1134998],esi + * 1417a1bc c705 9c491301 0a>mov dword ptr ds:[0x113499c],0xa + * 1417a1c6 0f84 2e000000 je 1417a1fa + * 1417a1cc 8b05 fc491301 mov eax,dword ptr ds:[0x11349fc] + * 1417a1d2 81e0 ffffff3f and eax,0x3fffffff + * 1417a1d8 8bb0 18000008 mov esi,dword ptr ds:[eax+0x8000018] + * 1417a1de 8935 98491301 mov dword ptr ds:[0x1134998],esi + * 1417a1e4 832d e44c1301 04 sub dword ptr ds:[0x1134ce4],0x4 + * 1417a1eb e9 24000000 jmp 1417a214 + * 1417a1f0 01b0 838108e9 add dword ptr ds:[eax+0xe9088183],esi + * 1417a1f6 295e 71 sub dword ptr ds:[esi+0x71],ebx + * 1417a1f9 ef out dx,eax ; i/o command + * 1417a1fa 832d e44c1301 04 sub dword ptr ds:[0x1134ce4],0x4 + * 1417a201 e9 1e660000 jmp 14180824 + * 1417a206 0188 838108e9 add dword ptr ds:[eax+0xe9088183],ecx + * 1417a20c 135e 71 adc ebx,dword ptr ds:[esi+0x71] + * 1417a20f ef out dx,eax ; i/o command + * 1417a210 90 nop + * 1417a211 cc int3 + * 1417a212 cc int3 + * + * 1417a303 90 nop + * 1417a304 77 0f ja short 1417a315 + * 1417a306 c705 c84c1301 48>mov dword ptr ds:[0x1134cc8],0x8818448 + * 1417a310 -e9 ef5c71ef jmp 03890004 + * 1417a315 8b35 dc491301 mov esi,dword ptr ds:[0x11349dc] + * 1417a31b 8b3d 98491301 mov edi,dword ptr ds:[0x1134998] + * 1417a321 33c0 xor eax,eax + * 1417a323 3bf7 cmp esi,edi + * 1417a325 0f9cc0 setl al + * 1417a328 8bf8 mov edi,eax + * 1417a32a 81ff 00000000 cmp edi,0x0 + * 1417a330 893d 98491301 mov dword ptr ds:[0x1134998],edi + * 1417a336 0f84 2f000000 je 1417a36b + * 1417a33c 8b05 c8491301 mov eax,dword ptr ds:[0x11349c8] + * 1417a342 81e0 ffffff3f and eax,0x3fffffff + * 1417a348 0fbeb0 00000008 movsx esi,byte ptr ds:[eax+0x8000000] ; jichi: hook here, loop + * 1417a34f 8935 98491301 mov dword ptr ds:[0x1134998],esi + * 1417a355 832d e44c1301 03 sub dword ptr ds:[0x1134ce4],0x3 + * 1417a35c e9 23000000 jmp 1417a384 + * 1417a361 018484 8108e9b8 add dword ptr ss:[esp+eax*4+0xb8e90881],> + * 1417a368 5c pop esp + * 1417a369 ^71 ef jno short 1417a35a + * 1417a36b 832d e44c1301 03 sub dword ptr ds:[0x1134ce4],0x3 + * 1417a372 c705 c84c1301 54>mov dword ptr ds:[0x1134cc8],0x8818454 + * 1417a37c -e9 a25c71ef jmp 03890023 + * 1417a381 90 nop + * 1417a382 cc int3 + */ +// Read text from looped address word by word +// Use reverse search to avoid looping issue assume the text is at fixed address. +static void SpecialPSPHookKonami(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + //static LPCSTR lasttext; // this value should be the same for the same game + static size_t lastsize; + + DWORD eax = regof(eax, esp_base); + LPCSTR cur = LPCSTR(eax + hp->user_value); + if (!*cur) + return; + + LPCSTR text = reverse_search_begin(cur); + if (!text) + return; + //if (lasttext != text) { + // lasttext = text; + // lastsize = 0; // reset last size + //} + + size_t size = ::strlen(text); + if (size == lastsize) + return; + + *len = lastsize = size; + *data = (DWORD)text; + + *split = regof(ebx, esp_base); // ecx changes for each character, ebx is an address, edx is stable, but very large +} +bool InsertKonamiPSPHook() +{ + ConsoleOutput("vnreng: KONAMI PSP: enter"); + const BYTE bytes[] = { + // 14178f73 cc int3 + 0x77, 0x0f, // 14178f74 77 0f ja short 14178f85 + 0xc7,0x05, XX8, // 14178f76 c705 c84c1301 a4>mov dword ptr ds:[0x1134cc8],0x88129a4 + 0xe9, XX4, // 14178f80 -e9 7f7071ef jmp 03890004 + 0x8b,0x05, XX4, // 14178f85 8b05 c8491301 mov eax,dword ptr ds:[0x11349c8] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 14178f8b 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xbe,0xb0, XX4, // 14178f91 0fbeb0 00000008 movsx esi,byte ptr ds:[eax+0x8000000] ; jichi: hook here, loop + 0x81,0xfe, 0x40,0x00,0x00,0x00, // 14178f98 81fe 40000000 cmp esi,0x40 + 0x89,0x35 //, XX4, // 14178f9e 8935 98491301 mov dword ptr ds:[0x1134998],esi + //0xc7,0x05, XX4, 0x40,0x00,0x00,0x00, // 14178fa4 c705 9c491301 40>mov dword ptr ds:[0x113499c],0x40 + //0x0f,0x85, 0x2f,0x00,0x00,0x00,0x00, // 14178fae 0f85 2f000000 jnz 14178fe3 + //0x8b,0x05, XX4 // 14178fb4 8b05 c8491301 mov eax,dword ptr ds:[0x11349c8] + }; + enum { memory_offset = 3 }; + enum { addr_offset = 0x14178f91 - 0x14178f74 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: KONAMI PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|NO_CONTEXT; + hp.text_fun = SpecialPSPHookKonami; + ConsoleOutput("vnreng: KONAMI PSP: INSERT"); + NewHook(hp, "KONAMI PSP"); + } + + ConsoleOutput("vnreng: KONAMI PSP: leave"); + return addr; +} + +/** 8/9/2014 jichi Kadokawa.co.jp PSP engine, 0.9.8, ?, + * + * Sample game: 未来日�work on 0.9.8, not tested on 0.9.9 + * + * FIXME: Currently, only the character name works + * + * Memory address is FIXED. + * Debug method: predict and breakpoint the memory address + * + * There are two matches in the memory, and only one function accessing them. + * + * Character name function is as follows. + * The scenario is the text after the name. + * + * 1348d79f cc int3 + * 1348d7a0 77 0f ja short 1348d7b1 + * 1348d7a2 c705 a8aa1001 fc>mov dword ptr ds:[0x110aaa8],0x884c6fc + * 1348d7ac -e9 532844f0 jmp 038d0004 + * 1348d7b1 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 1348d7b7 81e0 ffffff3f and eax,0x3fffffff + * 1348d7bd 0fb6b0 00004007 movzx esi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + * 1348d7c4 81fe 00000000 cmp esi,0x0 + * 1348d7ca 8935 70a71001 mov dword ptr ds:[0x110a770],esi + * 1348d7d0 0f85 2f000000 jnz 1348d805 + * 1348d7d6 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + * 1348d7dc 81e0 ffffff3f and eax,0x3fffffff + * 1348d7e2 0fbeb0 00004007 movsx esi,byte ptr ds:[eax+0x7400000] + * 1348d7e9 8935 70a71001 mov dword ptr ds:[0x110a770],esi + * 1348d7ef 832d c4aa1001 03 sub dword ptr ds:[0x110aac4],0x3 + * 1348d7f6 c705 a8aa1001 5c>mov dword ptr ds:[0x110aaa8],0x884c75c + * 1348d800 -e9 1e2844f0 jmp 038d0023 + * 1348d805 832d c4aa1001 03 sub dword ptr ds:[0x110aac4],0x3 + * 1348d80c e9 0b000000 jmp 1348d81c + * 1348d811 0108 add dword ptr ds:[eax],ecx + * 1348d813 c78408 e9082844 >mov dword ptr ds:[eax+ecx+0x442808e9],0x> + * 1348d81e c705 a8aa1001 08>mov dword ptr ds:[0x110aaa8],0x884c708 + * 1348d828 -e9 d72744f0 jmp 038d0004 + * 1348d82d 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + * 1348d833 81e0 ffffff3f and eax,0x3fffffff + * 1348d839 0fbeb0 00004007 movsx esi,byte ptr ds:[eax+0x7400000] + * 1348d840 81fe 00000000 cmp esi,0x0 + * 1348d846 8935 88a71001 mov dword ptr ds:[0x110a788],esi + * 1348d84c 0f85 16000000 jnz 1348d868 + * 1348d852 832d c4aa1001 03 sub dword ptr ds:[0x110aac4],0x3 + * 1348d859 e9 aa030000 jmp 1348dc08 + * 1348d85e 0154c7 84 add dword ptr ds:[edi+eax*8-0x7c],edx + * 1348d862 08e9 or cl,ch + * 1348d864 bb 2744f083 mov ebx,0x83f04427 + * 1348d869 2d c4aa1001 sub eax,0x110aac4 + * 1348d86e 03e9 add ebp,ecx + * 1348d870 0c 00 or al,0x0 + * 1348d872 0000 add byte ptr ds:[eax],al + * 1348d874 0114c7 add dword ptr ds:[edi+eax*8],edx + * 1348d877 8408 test byte ptr ds:[eax],cl + * 1348d879 -e9 a52744f0 jmp 038d0023 + * 1348d87e 90 nop + * 1348d87f cc int3 + * + * Scenario function is as follows. + * But I am not able to find it at runtime. + * + * 13484483 90 nop + * 13484484 77 0f ja short 13484495 + * 13484486 c705 a8aa1001 30>mov dword ptr ds:[0x110aaa8],0x884b030 + * 13484490 -e9 6fbb59f3 jmp 06a20004 + * 13484495 8b35 74a71001 mov esi,dword ptr ds:[0x110a774] + * 1348449b 81fe 00000000 cmp esi,0x0 + * 134844a1 9c pushfd + * 134844a2 8bc6 mov eax,esi + * 134844a4 8b35 84a71001 mov esi,dword ptr ds:[0x110a784] + * 134844aa 03f0 add esi,eax + * 134844ac 8935 74a71001 mov dword ptr ds:[0x110a774],esi + * 134844b2 9d popfd + * 134844b3 0f8f 0c000000 jg 134844c5 + * 134844b9 832d c4aa1001 02 sub dword ptr ds:[0x110aac4],0x2 + * 134844c0 ^e9 23b0f9ff jmp 1341f4e8 + * 134844c5 832d c4aa1001 02 sub dword ptr ds:[0x110aac4],0x2 + * 134844cc e9 0b000000 jmp 134844dc + * 134844d1 0138 add dword ptr ds:[eax],edi + * 134844d3 b0 84 mov al,0x84 + * 134844d5 08e9 or cl,ch + * 134844d7 48 dec eax + * 134844d8 bb 59f39077 mov ebx,0x7790f359 + * 134844dd 0fc7 ??? ; unknown command + * 134844df 05 a8aa1001 add eax,0x110aaa8 + * 134844e4 38b0 8408e917 cmp byte ptr ds:[eax+0x17e90884],dh + * 134844ea bb 59f38b05 mov ebx,0x58bf359 + * 134844ef ^7c a7 jl short 13484498 + * 134844f1 1001 adc byte ptr ds:[ecx],al + * 134844f3 81e0 ffffff3f and eax,0x3fffffff + * 134844f9 0fb6b0 00004007 movzx esi,byte ptr ds:[eax+0x7400000] ; jichi: hook here, byte by byte + * 13484500 8b05 84a71001 mov eax,dword ptr ds:[0x110a784] + * 13484506 81e0 ffffff3f and eax,0x3fffffff + * 1348450c 8bd6 mov edx,esi + * 1348450e 8890 00004007 mov byte ptr ds:[eax+0x7400000],dl + * 13484514 8b3d 84a71001 mov edi,dword ptr ds:[0x110a784] + * 1348451a 8d7f 01 lea edi,dword ptr ds:[edi+0x1] + * 1348451d 8b2d 7ca71001 mov ebp,dword ptr ds:[0x110a77c] + * 13484523 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + * 13484526 3b3d 74a71001 cmp edi,dword ptr ds:[0x110a774] + * 1348452c 8935 70a71001 mov dword ptr ds:[0x110a770],esi + * 13484532 892d 7ca71001 mov dword ptr ds:[0x110a77c],ebp + * 13484538 893d 84a71001 mov dword ptr ds:[0x110a784],edi + * 1348453e 0f84 16000000 je 1348455a + * 13484544 832d c4aa1001 05 sub dword ptr ds:[0x110aac4],0x5 + * 1348454b ^e9 8cffffff jmp 134844dc + * 13484550 0138 add dword ptr ds:[eax],edi + * 13484552 b0 84 mov al,0x84 + * 13484554 08e9 or cl,ch + * 13484556 c9 leave + * 13484557 ba 59f3832d mov edx,0x2d83f359 + * 1348455c c4aa 100105e9 les ebp,fword ptr ds:[edx+0xe9050110] ; modification of segment register + * 13484562 0e push cs + * 13484563 0000 add byte ptr ds:[eax],al + * 13484565 0001 add byte ptr ds:[ecx],al + * 13484567 4c dec esp + * 13484568 b0 84 mov al,0x84 + * 1348456a 08e9 or cl,ch + * 1348456c b3 ba mov bl,0xba + * 1348456e 59 pop ecx + * 1348456f f3: prefix rep: ; superfluous prefix + * 13484570 90 nop + * 13484571 cc int3 + * 13484572 cc int3 + * 13484573 cc int3 + */ +bool InsertKadokawaNamePSPHook() +{ + ConsoleOutput("vnreng: Kadokawa Name PSP: enter"); + const BYTE bytes[] = { + 0x77, 0x0f, // 1348d7a0 77 0f ja short 1348d7b1 + 0xc7,0x05, XX8, // 1348d7a2 c705 a8aa1001 fc>mov dword ptr ds:[0x110aaa8],0x884c6fc + 0xe9, XX4, // 1348d7ac -e9 532844f0 jmp 038d0004 + 0x8b,0x05, XX4, // 1348d7b1 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1348d7b7 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xb0, XX4, // 1348d7bd 0fb6b0 00004007 movzx esi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + 0x81,0xfe, 0x00,0x00,0x00,0x00, // 1348d7c4 81fe 00000000 cmp esi,0x0 + 0x89,0x35, XX4, // 1348d7ca 8935 70a71001 mov dword ptr ds:[0x110a770],esi + 0x0f,0x85, 0x2f,0x00,0x00,0x00, // 1348d7d0 0f85 2f000000 jnz 1348d805 + 0x8b,0x05, XX4, // 1348d7d6 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1348d7dc 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xbe,0xb0, XX4, // 1348d7e2 0fbeb0 00004007 movsx esi,byte ptr ds:[eax+0x7400000] + 0x89,0x35 //, XX4, // 1348d7e9 8935 70a71001 mov dword ptr ds:[0x110a770],esi + }; + enum { memory_offset = 3 }; + enum { addr_offset = 0x1348d7bd - 0x1348d7a0 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: Kadokawa Name PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; + hp.offset = pusha_eax_off - 4; + hp.split = pusha_edx_off - 4; // use edx to split repetition + hp.text_fun = SpecialPSPHook; + + //GROWL_DWORD2(hp.address, hp.user_value); + ConsoleOutput("vnreng: Kadokawa Name PSP: INSERT"); + NewHook(hp, "Kadokawa Name PSP"); + } + + ConsoleOutput("vnreng: Kadokawa Name PSP: leave"); + return addr; +} + +/** 9/5/2014 jichi felistella.co.jp PSP engine, 0.9.8, 0.9.9 + * Sample game: Summon Night 5 0.9.8/0.9.9 + * + * Encoding: utf8 + * Fixed memory addresses: two matches + * + * Debug method: predict the text and add break-points. + * + * There are two good functions + * The second is used as it contains fewer garbage + * + * // Not used + * 14081173 cc int3 + * 14081174 77 0f ja short 14081185 + * 14081176 c705 c84c1301 40>mov dword ptr ds:[0x1134cc8],0x8989540 + * 14081180 -e9 7feef5f3 jmp 07fe0004 + * 14081185 8b35 9c491301 mov esi,dword ptr ds:[0x113499c] + * 1408118b 8bc6 mov eax,esi + * 1408118d 81e0 ffffff3f and eax,0x3fffffff + * 14081193 0fb6b8 00000008 movzx edi,byte ptr ds:[eax+0x8000000] ; jichi: hook here + * 1408119a 8bef mov ebp,edi + * 1408119c 81e5 80000000 and ebp,0x80 + * 140811a2 8d76 01 lea esi,dword ptr ds:[esi+0x1] + * 140811a5 81fd 00000000 cmp ebp,0x0 + * 140811ab c705 90491301 00>mov dword ptr ds:[0x1134990],0x0 + * 140811b5 893d 9c491301 mov dword ptr ds:[0x113499c],edi + * 140811bb 8935 a0491301 mov dword ptr ds:[0x11349a0],esi + * 140811c1 892d a4491301 mov dword ptr ds:[0x11349a4],ebp + * 140811c7 0f85 16000000 jnz 140811e3 + * 140811cd 832d e44c1301 06 sub dword ptr ds:[0x1134ce4],0x6 + * 140811d4 e9 fbf71200 jmp 141b09d4 + * 140811d9 01dc add esp,ebx + * 140811db 95 xchg eax,ebp + * 140811dc 98 cwde + * 140811dd 08e9 or cl,ch + * 140811df 40 inc eax + * + * // Used + * 141be92f cc int3 + * 141be930 77 0f ja short 141be941 + * 141be932 c705 c84c1301 0c>mov dword ptr ds:[0x1134cc8],0x8988f0c + * 141be93c -e9 c316e2f3 jmp 07fe0004 + * 141be941 8b35 98491301 mov esi,dword ptr ds:[0x1134998] + * 141be947 8bc6 mov eax,esi + * 141be949 81e0 ffffff3f and eax,0x3fffffff + * 141be94f 0fb6b8 00000008 movzx edi,byte ptr ds:[eax+0x8000000] ; jichi: hook here + * 141be956 81ff 00000000 cmp edi,0x0 + * 141be95c c705 90491301 00>mov dword ptr ds:[0x1134990],0x0 + * 141be966 893d 98491301 mov dword ptr ds:[0x1134998],edi + * 141be96c 8935 9c491301 mov dword ptr ds:[0x113499c],esi + * 141be972 0f85 16000000 jnz 141be98e + * 141be978 832d e44c1301 04 sub dword ptr ds:[0x1134ce4],0x4 + * 141be97f e9 e4020000 jmp 141bec68 + * 141be984 01748f 98 add dword ptr ds:[edi+ecx*4-0x68],esi + * 141be988 08e9 or cl,ch + * 141be98a 95 xchg eax,ebp + * 141be98b 16 push ss + * 141be98c ^e2 f3 loopd short 141be981 + * 141be98e 832d e44c1301 04 sub dword ptr ds:[0x1134ce4],0x4 + * 141be995 e9 0e000000 jmp 141be9a8 + * 141be99a 011c8f add dword ptr ds:[edi+ecx*4],ebx + * 141be99d 98 cwde + * 141be99e 08e9 or cl,ch + * 141be9a0 7f 16 jg short 141be9b8 + * 141be9a2 ^e2 f3 loopd short 141be997 + * 141be9a4 90 nop + * 141be9a5 cc int3 + */ +// Only split text when edi is eax +// The value of edi is either eax or 0 +static void SpecialPSPHookFelistella(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD eax = regof(eax, esp_base); + LPCSTR text = LPCSTR(eax + hp->user_value); + if (text) { + *len = ::strlen(text); // utf8 + *data = (DWORD)text; + + DWORD edi = regof(edi, esp_base); + *split = FIXED_SPLIT_VALUE * (edi == eax ? 4 : 5); + } +} +bool InsertFelistellaPSPHook() +{ + ConsoleOutput("vnreng: FELISTELLA PSP: enter"); + const BYTE bytes[] = { + //0xcc, // 141be92f cc int3 + 0x77, 0x0f, // 141be930 77 0f ja short 141be941 + 0xc7,0x05, XX8, // 141be932 c705 c84c1301 0c>mov dword ptr ds:[0x1134cc8],0x8988f0c + 0xe9, XX4, // 141be93c -e9 c316e2f3 jmp 07fe0004 + 0x8b,0x35, XX4, // 141be941 8b35 98491301 mov esi,dword ptr ds:[0x1134998] + 0x8b,0xc6, // 141be947 8bc6 mov eax,esi + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 141be949 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xb8, XX4, // 141be94f 0fb6b8 00000008 movzx edi,byte ptr ds:[eax+0x8000000] ; jichi: hook here + 0x81,0xff, 0x00,0x00,0x00,0x00, // 141be956 81ff 00000000 cmp edi,0x0 + 0xc7,0x05, XX4, 0x00,0x00,0x00,0x00, // 141be95c c705 90491301 00>mov dword ptr ds:[0x1134990],0x0 + 0x89,0x3d, XX4, // 141be966 893d 98491301 mov dword ptr ds:[0x1134998],edi + 0x89,0x35, XX4, // 141be96c 8935 9c491301 mov dword ptr ds:[0x113499c],esi + 0x0f,0x85, XX4, // 141be972 0f85 16000000 jnz 141be98e + 0x83,0x2d, XX4, 0x04, // 141be978 832d e44c1301 04 sub dword ptr ds:[0x1134ce4],0x4 + // Above is not sufficient + 0xe9, XX4, // 141be97f e9 e4020000 jmp 141bec68 + 0x01,0x74,0x8f, 0x98 // 141be984 01748f 98 add dword ptr ds:[edi+ecx*4-0x68],esi + //0x08,0xe9, // 141be988 08e9 or cl,ch + // Below could be changed for different run + //0x95, // 141be98a 95 xchg eax,ebp + //0x16 // 141be98b 16 push ss + }; + enum { memory_offset = 3 }; + enum { addr_offset = 0x141be94f - 0x141be930 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + //GROWL_DWORD(addr); + if (!addr) + ConsoleOutput("vnreng: FELISTELLA PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|USING_UTF8|USING_SPLIT|NO_CONTEXT; // Fix the split value to merge all threads + //hp.text_fun = SpecialPSPHook; + hp.text_fun = SpecialPSPHookFelistella; + hp.offset = pusha_eax_off - 4; + //hp.split = pusha_ecx_off - 4; // cause main thread to split + //hp.split = pusha_edx_off - 4; // cause main thread to split for different lines + ConsoleOutput("vnreng: FELISTELLA PSP: INSERT"); + NewHook(hp, "FELISTELLA PSP"); + } + + ConsoleOutput("vnreng: FELISTELLA PSP: leave"); + return addr; +} + + +#if 0 // 8/9/2014 jichi: does not work + +bool InsertKadokawaPSPHook() +{ + ConsoleOutput("vnreng: Kadokawa PSP: enter"); + const BYTE bytes[] = { + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 134844f3 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xb0, XX4, // 134844f9 0fb6b0 00004007 movzx esi,byte ptr ds:[eax+0x7400000] ; jichi: hook here, byte by byte + 0x8b,0x05, XX4, // 13484500 8b05 84a71001 mov eax,dword ptr ds:[0x110a784] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13484506 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xd6, // 1348450c 8bd6 mov edx,esi + 0x88,0x90, XX4, // 1348450e 8890 00004007 mov byte ptr ds:[eax+0x7400000],dl + 0x8b,0x3d, XX4, // 13484514 8b3d 84a71001 mov edi,dword ptr ds:[0x110a784] + 0x8d,0x7f, 0x01, // 1348451a 8d7f 01 lea edi,dword ptr ds:[edi+0x1] + 0x8b,0x2d, XX4, // 1348451d 8b2d 7ca71001 mov ebp,dword ptr ds:[0x110a77c] + 0x8d,0x6d, 0x01, // 13484523 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + 0x3b,0x3d, XX4, // 13484526 3b3d 74a71001 cmp edi,dword ptr ds:[0x110a774] + 0x89,0x35, XX4, // 1348452c 8935 70a71001 mov dword ptr ds:[0x110a770],esi + 0x89,0x2d, XX4, // 13484532 892d 7ca71001 mov dword ptr ds:[0x110a77c],ebp + 0x89,0x3d, XX4, // 13484538 893d 84a71001 mov dword ptr ds:[0x110a784],edi + // Above is not sufficient + //0x0f,0x84, XX4, // 1348453e 0f84 16000000 je 1348455a + //0x83,0x2d, XX4, 0x05, // 13484544 832d c4aa1001 05 sub dword ptr ds:[0x110aac4],0x5 + //0xe9, XX4, // 1348454b ^e9 8cffffff jmp 134844dc + //0x01,0x38, // 13484550 0138 add dword ptr ds:[eax],edi + //0xb0, 0x84, // 13484552 b0 84 mov al,0x84 + //0x08,0xe9 // 13484554 08e9 or cl,ch + // Below will change at runtime + }; + enum { memory_offset = 3 }; + enum { addr_offset = 0x134844f9 - 0x134844f3 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) { + ConsoleOutput("vnreng: Kadokawa PSP: pattern not found"); + return false; + } + addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes), addr); + addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes), addr); + + if (!addr) + ConsoleOutput("vnreng: Kadokawa PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; + hp.offset = pusha_eax_off - 4; + hp.split = pusha_ecx_off - 4; // use edx to split repetition + hp.length_offset = 1; // byte by byte + hp.text_fun = SpecialPSPHook; + + //GROWL_DWORD2(hp.address, hp.user_value); + ConsoleOutput("vnreng: Kadokawa PSP: INSERT"); + NewHook(hp, "Kadokawa PSP"); + } + + ConsoleOutput("vnreng: Kadokawa PSP: leave"); + return addr; +} +#endif // 0 + +#if 0 // 8/9/2014 jichi: cannot find a good function + +/** 8/9/2014 jichi Typemoon.com PSP engine, 0.9.8, 0.9.9, + * + * Sample game: Fate CCC + * This game is made by both TYPE-MOON and Imageepoch + * But the encoding is SHIFT-JIS than UTF-8 like other Imageepoch games. + * Otomate hook will produce significant amount of garbage. + * + * Memory address is FIXED. + * There are two matches in the memory. + * + * Debug method: breakpoint the memory address + * The hooked functions were looping which made it difficult to debug. + * + * Two looped functions are as follows. The first one is used + * The second function is tested as bad. + * + * Registers: (all of them are fixed except eax) + * EAX 08C91373 + * ECX 00000016 + * EDX 00000012 + * EBX 0027A580 + * ESP 0353E6D0 + * EBP 0000000B + * ESI 0000001E + * EDI 00000001 + * EIP 1351E14D + * + * 1351e12d f0:90 lock nop ; lock prefix is not allowed + * 1351e12f cc int3 + * 1351e130 77 0f ja short 1351e141 + * 1351e132 c705 a8aa1001 b8>mov dword ptr ds:[0x110aaa8],0x88ed7b8 + * 1351e13c -e9 c31e27f0 jmp 03790004 + * 1351e141 8b05 aca71001 mov eax,dword ptr ds:[0x110a7ac] + * 1351e147 81e0 ffffff3f and eax,0x3fffffff + * 1351e14d 0fbeb0 01004007 movsx esi,byte ptr ds:[eax+0x7400001] ; or jichi: hook here + * 1351e154 8b05 dca71001 mov eax,dword ptr ds:[0x110a7dc] + * 1351e15a 81e0 ffffff3f and eax,0x3fffffff + * 1351e160 8bb8 50004007 mov edi,dword ptr ds:[eax+0x7400050] + * 1351e166 81e6 ff000000 and esi,0xff + * 1351e16c 8bc6 mov eax,esi + * 1351e16e 8b35 a8a71001 mov esi,dword ptr ds:[0x110a7a8] + * 1351e174 0bf0 or esi,eax + * 1351e176 c1e6 10 shl esi,0x10 + * 1351e179 c1fe 10 sar esi,0x10 + * 1351e17c 893d 78a71001 mov dword ptr ds:[0x110a778],edi + * 1351e182 8935 7ca71001 mov dword ptr ds:[0x110a77c],esi + * 1351e188 c705 e4a71001 d4>mov dword ptr ds:[0x110a7e4],0x88ed7d4 + * 1351e192 832d c4aa1001 07 sub dword ptr ds:[0x110aac4],0x7 + * 1351e199 e9 0e000000 jmp 1351e1ac + * 1351e19e 01ac3e 8e08e97b add dword ptr ds:[esi+edi+0x7be9088e],eb> + * 1351e1a5 1e push ds + * 1351e1a6 27 daa + * 1351e1a7 f0:90 lock nop ; lock prefix is not allowed + * 1351e1a9 cc int3 + * + * 13513f23 cc int3 + * 13513f24 77 0f ja short 13513f35 + * 13513f26 c705 a8aa1001 d4>mov dword ptr ds:[0x110aaa8],0x88e7bd4 + * 13513f30 -e9 cfc027f0 jmp 03790004 + * 13513f35 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + * 13513f3b 81e0 ffffff3f and eax,0x3fffffff + * 13513f41 0fbeb0 00004007 movsx esi,byte ptr ds:[eax+0x7400000] + * 13513f48 8b3d 84a71001 mov edi,dword ptr ds:[0x110a784] + * 13513f4e 8d7f 01 lea edi,dword ptr ds:[edi+0x1] + * 13513f51 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 13513f57 81e0 ffffff3f and eax,0x3fffffff + * 13513f5d 8bd6 mov edx,esi + * 13513f5f 8890 00004007 mov byte ptr ds:[eax+0x7400000],dl ; jichi: bad hook + * 13513f65 8b2d 78a71001 mov ebp,dword ptr ds:[0x110a778] + * 13513f6b 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + * 13513f6e 33c0 xor eax,eax + * 13513f70 3b3d 80a71001 cmp edi,dword ptr ds:[0x110a780] + * 13513f76 0f9cc0 setl al + * 13513f79 8bf0 mov esi,eax + * 13513f7b 8b15 7ca71001 mov edx,dword ptr ds:[0x110a77c] + * 13513f81 8d52 01 lea edx,dword ptr ds:[edx+0x1] + * 13513f84 81fe 00000000 cmp esi,0x0 + * 13513f8a 892d 78a71001 mov dword ptr ds:[0x110a778],ebp + * 13513f90 8915 7ca71001 mov dword ptr ds:[0x110a77c],edx + * 13513f96 893d 84a71001 mov dword ptr ds:[0x110a784],edi + * 13513f9c 8935 88a71001 mov dword ptr ds:[0x110a788],esi + * 13513fa2 0f84 16000000 je 13513fbe + * 13513fa8 832d c4aa1001 07 sub dword ptr ds:[0x110aac4],0x7 + * 13513faf ^e9 70ffffff jmp 13513f24 + * 13513fb4 01d4 add esp,edx + * 13513fb6 7b 8e jpo short 13513f46 + * 13513fb8 08e9 or cl,ch + * 13513fba 65:c027 f0 shl byte ptr gs:[edi],0xf0 ; shift constant out of range 1..31 + * 13513fbe 832d c4aa1001 07 sub dword ptr ds:[0x110aac4],0x7 + * 13513fc5 e9 0e000000 jmp 13513fd8 + * 13513fca 01f0 add eax,esi + * 13513fcc 7b 8e jpo short 13513f5c + * 13513fce 08e9 or cl,ch + * 13513fd0 4f dec edi + * 13513fd1 c027 f0 shl byte ptr ds:[edi],0xf0 ; shift constant out of range 1..31 + * 13513fd4 90 nop + * 13513fd5 cc int3 + * 13513fd6 cc int3 + */ +// Read text from dl +static void SpecialPSPHookTypeMoon(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD eax = regof(eax, esp_base); + DWORD text = eax + hp->user_value - 1; // the text is in the previous byte + if (BYTE c = *(BYTE *)text) { // unsigned char + *data = text; + *len = ::LeadByteTable[c]; // 1 or 2 + //*split = regof(ecx, esp_base); + //*split = regof(edx, esp_base); + *split = regof(ebx, esp_base); + } +} +bool InsertTypeMoonPSPHook() +{ + ConsoleOutput("vnreng: TypeMoon PSP: enter"); + const BYTE bytes[] = { + 0x77, 0x0f, // 1351e130 77 0f ja short 1351e141 + 0xc7,0x05, XX8, // 1351e132 c705 a8aa1001 b8>mov dword ptr ds:[0x110aaa8],0x88ed7b8 + 0xe9, XX4, // 1351e13c -e9 c31e27f0 jmp 03790004 + 0x8b,0x05, XX4, // 1351e141 8b05 aca71001 mov eax,dword ptr ds:[0x110a7ac] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1351e147 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xbe,0xb0, XX4, // 1351e14d 0fbeb0 01004007 movsx esi,byte ptr ds:[eax+0x7400001] ; jichi: hook here + 0x8b,0x05, XX4, // 1351e154 8b05 dca71001 mov eax,dword ptr ds:[0x110a7dc] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1351e15a 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xb8, XX4, // 1351e160 8bb8 50004007 mov edi,dword ptr ds:[eax+0x7400050] + 0x81,0xe6, 0xff,0x00,0x00,0x00, // 1351e166 81e6 ff000000 and esi,0xff + 0x8b,0xc6, // 1351e16c 8bc6 mov eax,esi + 0x8b,0x35, XX4, // 1351e16e 8b35 a8a71001 mov esi,dword ptr ds:[0x110a7a8] + 0x0b,0xf0, // 1351e174 0bf0 or esi,eax + 0xc1,0xe6, 0x10, // 1351e176 c1e6 10 shl esi,0x10 + 0xc1,0xfe, 0x10 // 1351e179 c1fe 10 sar esi,0x10 + }; + enum { memory_offset = 3 }; + enum { addr_offset = 0x1351e14d - 0x1351e130 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: TypeMoon PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|NO_CONTEXT; + hp.text_fun = SpecialPSPHookTypeMoon; + ConsoleOutput("vnreng: TypeMoon PSP: INSERT"); + NewHook(hp, "TypeMoon PSP"); + } + + ConsoleOutput("vnreng: TypeMoon PSP: leave"); + return addr; +} + +#endif // 0 + +#if 0 // 7/25/2014: This function is not invoked? Why? +/** 7/22/2014 jichi: KOEI TECMO PSP, 0.9.8 + * Sample game: 金色のコルダ3 + * + * 134598e2 cc int3 + * 134598e3 cc int3 + * 134598e4 77 0f ja short 134598f5 + * 134598e6 c705 a8aa1001 8c>mov dword ptr ds:[0x110aaa8],0x880f08c + * 134598f0 -e9 0f67fbef jmp 03410004 + * 134598f5 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + * 134598fb 81e0 ffffff3f and eax,0x3fffffff + * 13459901 8bb0 00004007 mov esi,dword ptr ds:[eax+0x7400000] ; jichi: hook here + * 13459907 8b3d 7ca71001 mov edi,dword ptr ds:[0x110a77c] + * 1345990d 8d7f 04 lea edi,dword ptr ds:[edi+0x4] + * 13459910 8b05 84a71001 mov eax,dword ptr ds:[0x110a784] + * 13459916 81e0 ffffff3f and eax,0x3fffffff + * 1345991c 89b0 00004007 mov dword ptr ds:[eax+0x7400000],esi + * 13459922 8b2d 84a71001 mov ebp,dword ptr ds:[0x110a784] + * 13459928 8d6d 04 lea ebp,dword ptr ss:[ebp+0x4] + * 1345992b 8b15 78a71001 mov edx,dword ptr ds:[0x110a778] + * 13459931 81fa 01000000 cmp edx,0x1 + * 13459937 8935 70a71001 mov dword ptr ds:[0x110a770],esi + * 1345993d 893d 7ca71001 mov dword ptr ds:[0x110a77c],edi + * 13459943 892d 84a71001 mov dword ptr ds:[0x110a784],ebp + * 13459949 c705 88a71001 01>mov dword ptr ds:[0x110a788],0x1 + * 13459953 0f84 16000000 je 1345996f + * 13459959 832d c4aa1001 09 sub dword ptr ds:[0x110aac4],0x9 + * 13459960 e9 17000000 jmp 1345997c + * 13459965 0190 f08008e9 add dword ptr ds:[eax+0xe90880f0],edx + * 1345996b b4 66 mov ah,0x66 + * 1345996d fb sti + * 1345996e ef out dx,eax ; i/o command + * 1345996f 832d c4aa1001 09 sub dword ptr ds:[0x110aac4],0x9 + * 13459976 ^e9 ddc1ffff jmp 13455b58 + * 1345997b 90 nop + */ +bool InsertTecmoPSPHook() +{ + ConsoleOutput("vnreng: Tecmo PSP: enter"); + + const BYTE bytes[] = { + 0x77, 0x0f, // 134598e4 77 0f ja short 134598f5 + 0xc7,0x05, XX8, // 134598e6 c705 a8aa1001 8c>mov dword ptr ds:[0x110aaa8],0x880f08c + 0xe9, XX4, // 134598f0 -e9 0f67fbef jmp 03410004 + 0x8b,0x05, XX4, // 134598f5 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 134598fb 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xb0, XX4, // 13459901 8bb0 00004007 mov esi,dword ptr ds:[eax+0x7400000] ; jichi: hook here + 0x8b,0x3d, XX4, // 13459907 8b3d 7ca71001 mov edi,dword ptr ds:[0x110a77c] + 0x8d,0x7f, 0x04, // 1345990d 8d7f 04 lea edi,dword ptr ds:[edi+0x4] + 0x8b,0x05, XX4, // 13459910 8b05 84a71001 mov eax,dword ptr ds:[0x110a784] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13459916 81e0 ffffff3f and eax,0x3fffffff + 0x89,0xb0 //, XX4, // 1345991c 89b0 00004007 mov dword ptr ds:[eax+0x7400000],esi + //0x8b,0x2d, XX4, // 13459922 8b2d 84a71001 mov ebp,dword ptr ds:[0x110a784] + //0x8d,0x6d, 0x04, // 13459928 8d6d 04 lea ebp,dword ptr ss:[ebp+0x4] + //0x8b,0x15, XX4, // 1345992b 8b15 78a71001 mov edx,dword ptr ds:[0x110a778] + //0x81,0xfa, 0x01,0x00,0x00,0x00 // 13459931 81fa 01000000 cmp edx,0x1 + }; + enum { memory_offset = 2 }; + enum { addr_offset = 0x13459901 - 0x134598e4 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: Tecmo PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.type = USING_STRING|USING_SPLIT|NO_CONTEXT; + hp.offset = pusha_eax_off - 4; + hp.split = pusha_ecx_off - 4; + hp.text_fun = SpecialPSPHook; + ConsoleOutput("vnreng: Tecmo PSP: INSERT"); + NewHook(hp, "Tecmo PSP"); + } + + ConsoleOutput("vnreng: Tecmo PSP: leave"); + return addr; +} +#endif // 0 + +/** jichi 7/19/2014 PCSX2 + * Tested wit pcsx2-v1.2.1-328-gef0e3fe-windows-x86, built at http://buildbot.orphis.net/pcsx2 + */ +bool InsertPCSX2Hooks() +{ + // TODO: Add generic hooks + return InsertTypeMoonPS2Hook() + || InsertMarvelousPS2Hook() + || InsertMarvelous2PS2Hook(); +} + +/** 7/19/2014 jichi + * Tested game: Fate/stay night [Realta Nua] + * + * Fixed memory address. + * Text is incrementally increased. + * + * Debug method: Debug next text location at \0. + * There are three locations that are OK to hook. + * The first one is used. + * + * Runtime stack: + * 0dc1f7e0 055be7c0 + * 0dc1f7e4 023105b0 pcsx2.023105b0 + * 0dc1f7e8 0dc1f804 + * 0dc1f7ec 023a406b pcsx2.023a406b + * 0dc1f7f0 00000000 + * 0dc1f7f4 000027e5 + * + * 305a5424 2b05 809e9500 sub eax,dword ptr ds:[0x959e80] + * 305a542a 0f88 05000000 js 305a5435 + * 305a5430 -e9 cbebdfd1 jmp pcsx2.023a4000 + * 305a5435 8b0d 20ac9600 mov ecx,dword ptr ds:[0x96ac20] + * 305a543b 89c8 mov eax,ecx + * 305a543d c1e8 0c shr eax,0xc + * 305a5440 8b0485 30009e12 mov eax,dword ptr ds:[eax*4+0x129e0030] + * 305a5447 bb 57545a30 mov ebx,0x305a5457 + * 305a544c 01c1 add ecx,eax + * 305a544e -0f88 ecbcd7d1 js pcsx2.02321140 + * 305a5454 0fbe01 movsx eax,byte ptr ds:[ecx] ; jichi: hook here + * 305a5457 99 cdq + * 305a5458 a3 f0ab9600 mov dword ptr ds:[0x96abf0],eax + * 305a545d 8915 f4ab9600 mov dword ptr ds:[0x96abf4],edx + * 305a5463 a1 40ac9600 mov eax,dword ptr ds:[0x96ac40] + * 305a5468 3b05 f0ab9600 cmp eax,dword ptr ds:[0x96abf0] + * 305a546e 75 11 jnz short 305a5481 + * 305a5470 a1 44ac9600 mov eax,dword ptr ds:[0x96ac44] + * 305a5475 3b05 f4ab9600 cmp eax,dword ptr ds:[0x96abf4] + * 305a547b 0f84 3a000000 je 305a54bb + * 305a5481 8305 00ac9600 24 add dword ptr ds:[0x96ac00],0x24 + * 305a5488 9f lahf + * 305a5489 66:c1f8 0f sar ax,0xf + * 305a548d 98 cwde + * 305a548e a3 04ac9600 mov dword ptr ds:[0x96ac04],eax + * 305a5493 c705 a8ad9600 6c>mov dword ptr ds:[0x96ada8],0x10e26c + * 305a549d a1 c0ae9600 mov eax,dword ptr ds:[0x96aec0] + * 305a54a2 83c0 04 add eax,0x4 + * + * 3038c78e -0f88 ac4af9d1 js pcsx2.02321240 + * 3038c794 8911 mov dword ptr ds:[ecx],edx + * 3038c796 8b0d 60ab9600 mov ecx,dword ptr ds:[0x96ab60] + * 3038c79c 89c8 mov eax,ecx + * 3038c79e c1e8 0c shr eax,0xc + * 3038c7a1 8b0485 30009e12 mov eax,dword ptr ds:[eax*4+0x129e0030] + * 3038c7a8 bb b8c73830 mov ebx,0x3038c7b8 + * 3038c7ad 01c1 add ecx,eax + * 3038c7af -0f88 8b49f9d1 js pcsx2.02321140 + * 3038c7b5 0fbe01 movsx eax,byte ptr ds:[ecx] ; jichi: or hook here + * 3038c7b8 99 cdq + * 3038c7b9 a3 e0ab9600 mov dword ptr ds:[0x96abe0],eax + * 3038c7be 8915 e4ab9600 mov dword ptr ds:[0x96abe4],edx + * 3038c7c4 c705 20ab9600 00>mov dword ptr ds:[0x96ab20],0x0 + * 3038c7ce c705 24ab9600 00>mov dword ptr ds:[0x96ab24],0x0 + * 3038c7d8 c705 f0ab9600 25>mov dword ptr ds:[0x96abf0],0x25 + * 3038c7e2 c705 f4ab9600 00>mov dword ptr ds:[0x96abf4],0x0 + * 3038c7ec 833d e0ab9600 25 cmp dword ptr ds:[0x96abe0],0x25 + * 3038c7f3 75 0d jnz short 3038c802 + * 3038c7f5 833d e4ab9600 00 cmp dword ptr ds:[0x96abe4],0x0 + * 3038c7fc 0f84 34000000 je 3038c836 + * 3038c802 31c0 xor eax,eax + * + * 304e1a0a 8b0d 40ab9600 mov ecx,dword ptr ds:[0x96ab40] + * 304e1a10 89c8 mov eax,ecx + * 304e1a12 c1e8 0c shr eax,0xc + * 304e1a15 8b0485 30009e12 mov eax,dword ptr ds:[eax*4+0x129e0030] + * 304e1a1c bb 2c1a4e30 mov ebx,0x304e1a2c + * 304e1a21 01c1 add ecx,eax + * 304e1a23 -0f88 17f7e3d1 js pcsx2.02321140 + * 304e1a29 0fbe01 movsx eax,byte ptr ds:[ecx] ; jichi: or hook here + * 304e1a2c 99 cdq + * 304e1a2d a3 f0ab9600 mov dword ptr ds:[0x96abf0],eax + * 304e1a32 8915 f4ab9600 mov dword ptr ds:[0x96abf4],edx + * 304e1a38 a1 f0ab9600 mov eax,dword ptr ds:[0x96abf0] + * 304e1a3d 3b05 d0ab9600 cmp eax,dword ptr ds:[0x96abd0] + * 304e1a43 75 11 jnz short 304e1a56 + * 304e1a45 a1 f4ab9600 mov eax,dword ptr ds:[0x96abf4] + * 304e1a4a 3b05 d4ab9600 cmp eax,dword ptr ds:[0x96abd4] + * 304e1a50 0f84 3c000000 je 304e1a92 + * 304e1a56 a1 f0ab9600 mov eax,dword ptr ds:[0x96abf0] + * 304e1a5b 83c0 d0 add eax,-0x30 + * 304e1a5e 99 cdq + */ +namespace { // unnamed +bool _typemoongarbage_ch(char c) +{ + return c == '%' || c == '.' || c == ' ' || c == ',' + || c >= '0' && c <= '9' + || c >= 'A' && c <= 'z'; // also ignore ASCII 91-96: [ \ ] ^ _ ` +} + +// Trim leading garbage +LPCSTR _typemoonltrim(LPCSTR p) +{ + enum { MAX_LENGTH = VNR_TEXT_CAPACITY }; + if (p && p[0] == '%') + for (int count = 0; *p && count < MAX_LENGTH; count++, p++) + if (!_typemoongarbage_ch(*p)) + return p; + return nullptr; +} + +// Remove trailing garbage such as %n +size_t _typemoonstrlen(LPCSTR text) +{ + size_t len = ::strlen(text); + size_t ret = len; + while (len && _typemoongarbage_ch(text[len - 1])) { + len--; + if (text[len] == '%') + ret = len; + } + return ret; +} + +} // unnamed namespace + +// Use last text size to determine +static void SpecialPS2HookTypeMoon(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + static LPCSTR lasttext; // this value should be the same for the same game + static size_t lastsize; + + LPCSTR cur = LPCSTR(regof(ecx, esp_base)); + if (!*cur) + return; + + LPCSTR text = reverse_search_begin(cur); + if (!text) + return; + //text = _typemoonltrim(text); + if (lasttext != text) { + lasttext = text; + lastsize = 0; // reset last size + } + + size_t size = ::strlen(text); + if (size == lastsize) + return; + if (size > lastsize) // incremental + text += lastsize; + lastsize = size; + + text = _typemoonltrim(text); + size = _typemoonstrlen(text); + //size = ::strlen(text); + + *data = (DWORD)text; + *len = size; + + *split = FIXED_SPLIT_VALUE << 2; // merge all threads + //*split = *(DWORD *)(esp_base + 4); // use [esp+4] as split + //*split = regof(eax, esp_base); + //*split = regof(esi, esp_base); +} + +bool InsertTypeMoonPS2Hook() +{ + ConsoleOutput("vnreng: TypeMoon PS2: enter"); + const BYTE bytes[] = { + 0x2b,0x05, XX4, // 305a5424 2b05 809e9500 sub eax,dword ptr ds:[0x959e80] + 0x0f,0x88, 0x05,0x00,0x00,0x00, // 305a542a 0f88 05000000 js 305a5435 + 0xe9, XX4, // 305a5430 -e9 cbebdfd1 jmp pcsx2.023a4000 + 0x8b,0x0d, XX4, // 305a5435 8b0d 20ac9600 mov ecx,dword ptr ds:[0x96ac20] + 0x89,0xc8, // 305a543b 89c8 mov eax,ecx + 0xc1,0xe8, 0x0c, // 305a543d c1e8 0c shr eax,0xc + 0x8b,0x04,0x85, XX4, // 305a5440 8b0485 30009e12 mov eax,dword ptr ds:[eax*4+0x129e0030] + 0xbb, XX4, // 305a5447 bb 57545a30 mov ebx,0x305a5457 + 0x01,0xc1, // 305a544c 01c1 add ecx,eax + // Following pattern is not sufficient + 0x0f,0x88, XX4, // 305a544e -0f88 ecbcd7d1 js pcsx2.02321140 + 0x0f,0xbe,0x01, // 305a5454 0fbe01 movsx eax,byte ptr ds:[ecx] ; jichi: hook here + 0x99, // 305a5457 99 cdq + 0xa3, XX4, // 305a5458 a3 f0ab9600 mov dword ptr ds:[0x96abf0],eax + 0x89,0x15, XX4, // 305a545d 8915 f4ab9600 mov dword ptr ds:[0x96abf4],edx + 0xa1, XX4, // 305a5463 a1 40ac9600 mov eax,dword ptr ds:[0x96ac40] + 0x3b,0x05, XX4, // 305a5468 3b05 f0ab9600 cmp eax,dword ptr ds:[0x96abf0] + 0x75, 0x11, // 305a546e 75 11 jnz short 305a5481 + 0xa1, XX4, // 305a5470 a1 44ac9600 mov eax,dword ptr ds:[0x96ac44] + 0x3b,0x05, XX4, // 305a5475 3b05 f4ab9600 cmp eax,dword ptr ds:[0x96abf4] + 0x0f,0x84, XX4, // 305a547b 0f84 3a000000 je 305a54bb + 0x83,0x05, XX4, 0x24, // 305a5481 8305 00ac9600 24 add dword ptr ds:[0x96ac00],0x24 + 0x9f, // 305a5488 9f lahf + 0x66,0xc1,0xf8, 0x0f, // 305a5489 66:c1f8 0f sar ax,0xf + 0x98 // 305a548d 98 cwde + }; + enum { addr_offset = 0x305a5454 - 0x305a5424 }; + + DWORD addr = SafeMatchBytesInPS2Memory(bytes, sizeof(bytes)); + //addr = 0x30403967; + if (!addr) + ConsoleOutput("vnreng: TypeMoon PS2: pattern not found"); + else { + //GROWL_DWORD(addr + addr_offset); + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.type = USING_STRING|NO_CONTEXT; // no context to get rid of return address + hp.text_fun = SpecialPS2HookTypeMoon; + //hp.offset = pusha_ecx_off - 4; // ecx, get text in ds:[ecx] + //hp.length_offset = 1; + ConsoleOutput("vnreng: TypeMoon PS2: INSERT"); + //GROWL_DWORD(hp.address); + NewHook(hp, "TypeMoon PS2"); + } + + ConsoleOutput("vnreng: TypeMoon PS2: leave"); + return addr; +} + +/** 8/3/2014 jichi + * Tested game: School Rumble ねる娘�育つ + * + * Fixed memory address. + * There is only one matched address. + * + * Debug method: Predict text location. + * There are a couple of locations that are OK to hook. + * The last one is used. + * + * Issue: the order of chara and scenario is reversed: 「scenario」chara + * + * eax 20000000 + * ecx 202d5ab3 + * edx 00000000 + * ebx 3026e299 + * esp 0c14f910 + * ebp 0c14f918 + * esi 0014f470 + * edi 00000000 + * eip 3026e296 + * + * 3026e1d5 -0f88 a530d7d2 js pcsx2.02fe1280 + * 3026e1db 0f1202 movlps xmm0,qword ptr ds:[edx] + * 3026e1de 0f1301 movlps qword ptr ds:[ecx],xmm0 + * 3026e1e1 ba 10ac6201 mov edx,0x162ac10 + * 3026e1e6 8b0d d0ac6201 mov ecx,dword ptr ds:[0x162acd0] ; pcsx2.01ffed00 + * 3026e1ec 83c1 10 add ecx,0x10 + * 3026e1ef 83e1 f0 and ecx,0xfffffff0 + * 3026e1f2 89c8 mov eax,ecx + * 3026e1f4 c1e8 0c shr eax,0xc + * 3026e1f7 8b0485 30006d0d mov eax,dword ptr ds:[eax*4+0xd6d0030] + * 3026e1fe bb 11e22630 mov ebx,0x3026e211 + * 3026e203 01c1 add ecx,eax + * 3026e205 -0f88 b530d7d2 js pcsx2.02fe12c0 + * 3026e20b 0f280a movaps xmm1,dqword ptr ds:[edx] + * 3026e20e 0f2909 movaps dqword ptr ds:[ecx],xmm1 + * 3026e211 ba 00ac6201 mov edx,0x162ac00 + * 3026e216 8b0d d0ac6201 mov ecx,dword ptr ds:[0x162acd0] ; pcsx2.01ffed00 + * 3026e21c 83e1 f0 and ecx,0xfffffff0 + * 3026e21f 89c8 mov eax,ecx + * 3026e221 c1e8 0c shr eax,0xc + * 3026e224 8b0485 30006d0d mov eax,dword ptr ds:[eax*4+0xd6d0030] + * 3026e22b bb 3ee22630 mov ebx,0x3026e23e + * 3026e230 01c1 add ecx,eax + * 3026e232 -0f88 8830d7d2 js pcsx2.02fe12c0 + * 3026e238 0f2812 movaps xmm2,dqword ptr ds:[edx] + * 3026e23b 0f2911 movaps dqword ptr ds:[ecx],xmm2 + * 3026e23e 31c0 xor eax,eax + * 3026e240 a3 f4ac6201 mov dword ptr ds:[0x162acf4],eax + * 3026e245 c705 f0ac6201 d4>mov dword ptr ds:[0x162acf0],0x1498d4 + * 3026e24f c705 a8ad6201 c0>mov dword ptr ds:[0x162ada8],0x1281c0 + * 3026e259 a1 c0ae6201 mov eax,dword ptr ds:[0x162aec0] + * 3026e25e 83c0 07 add eax,0x7 + * 3026e261 a3 c0ae6201 mov dword ptr ds:[0x162aec0],eax + * 3026e266 2b05 809e6101 sub eax,dword ptr ds:[0x1619e80] + * 3026e26c 0f88 05000000 js 3026e277 + * 3026e272 -e9 895ddfd2 jmp pcsx2.03064000 + * 3026e277 8b0d 40ab6201 mov ecx,dword ptr ds:[0x162ab40] + * 3026e27d 89c8 mov eax,ecx + * 3026e27f c1e8 0c shr eax,0xc + * 3026e282 8b0485 30006d0d mov eax,dword ptr ds:[eax*4+0xd6d0030] + * 3026e289 bb 99e22630 mov ebx,0x3026e299 + * 3026e28e 01c1 add ecx,eax + * 3026e290 -0f88 6a2dd7d2 js pcsx2.02fe1000 + * 3026e296 0fb601 movzx eax,byte ptr ds:[ecx] ; jichi: hook here + * 3026e299 a3 60ab6201 mov dword ptr ds:[0x162ab60],eax + * 3026e29e c705 64ab6201 00>mov dword ptr ds:[0x162ab64],0x0 + * 3026e2a8 a1 60ab6201 mov eax,dword ptr ds:[0x162ab60] + * 3026e2ad 05 7fffffff add eax,-0x81 + * 3026e2b2 99 cdq + * 3026e2b3 a3 70ab6201 mov dword ptr ds:[0x162ab70],eax + * 3026e2b8 8915 74ab6201 mov dword ptr ds:[0x162ab74],edx + * 3026e2be b8 01000000 mov eax,0x1 + * 3026e2c3 833d 74ab6201 00 cmp dword ptr ds:[0x162ab74],0x0 + * 3026e2ca 72 0d jb short 3026e2d9 + * 3026e2cc 77 09 ja short 3026e2d7 + * 3026e2ce 833d 70ab6201 18 cmp dword ptr ds:[0x162ab70],0x18 + * 3026e2d5 72 02 jb short 3026e2d9 + * 3026e2d7 31c0 xor eax,eax + * 3026e2d9 a3 10ab6201 mov dword ptr ds:[0x162ab10],eax + * 3026e2de c705 14ab6201 00>mov dword ptr ds:[0x162ab14],0x0 + * 3026e2e8 c705 20ab6201 00>mov dword ptr ds:[0x162ab20],0x0 + * 3026e2f2 c705 24ab6201 00>mov dword ptr ds:[0x162ab24],0x0 + * 3026e2fc c705 30ab6201 00>mov dword ptr ds:[0x162ab30],0x0 + * 3026e306 c705 34ab6201 00>mov dword ptr ds:[0x162ab34],0x0 + * 3026e310 833d 10ab6201 00 cmp dword ptr ds:[0x162ab10],0x0 + * 3026e317 0f85 41000000 jnz 3026e35e + * 3026e31d 833d 14ab6201 00 cmp dword ptr ds:[0x162ab14],0x0 + * 3026e324 0f85 34000000 jnz 3026e35e + * 3026e32a 31c0 xor eax,eax + * 3026e32c a3 50ab6201 mov dword ptr ds:[0x162ab50],eax + * 3026e331 a3 54ab6201 mov dword ptr ds:[0x162ab54],eax + * 3026e336 c705 a8ad6201 c0>mov dword ptr ds:[0x162ada8],0x1285c0 + * 3026e340 a1 c0ae6201 mov eax,dword ptr ds:[0x162aec0] + * 3026e345 83c0 08 add eax,0x8 + * 3026e348 a3 c0ae6201 mov dword ptr ds:[0x162aec0],eax + * 3026e34d 2b05 809e6101 sub eax,dword ptr ds:[0x1619e80] + * 3026e353 0f88 96280000 js 30270bef + * 3026e359 -e9 a25cdfd2 jmp pcsx2.03064000 + * 3026e35e 31c0 xor eax,eax + * 3026e360 a3 50ab6201 mov dword ptr ds:[0x162ab50],eax + * 3026e365 a3 54ab6201 mov dword ptr ds:[0x162ab54],eax + * 3026e36a c705 a8ad6201 dc>mov dword ptr ds:[0x162ada8],0x1281dc + * 3026e374 a1 c0ae6201 mov eax,dword ptr ds:[0x162aec0] + * 3026e379 83c0 08 add eax,0x8 + * 3026e37c a3 c0ae6201 mov dword ptr ds:[0x162aec0],eax + * 3026e381 2b05 809e6101 sub eax,dword ptr ds:[0x1619e80] + * 3026e387 0f88 a61f0000 js 30270333 + * 3026e38d -e9 6e5cdfd2 jmp pcsx2.03064000 + * 3026e392 b8 01000000 mov eax,0x1 + * 3026e397 833d 64ab6201 00 cmp dword ptr ds:[0x162ab64],0x0 + * 3026e39e 7c 10 jl short 3026e3b0 + * 3026e3a0 7f 0c jg short 3026e3ae + * 3026e3a2 813d 60ab6201 80>cmp dword ptr ds:[0x162ab60],0x80 + * 3026e3ac 72 02 jb short 3026e3b0 + * 3026e3ae 31c0 xor eax,eax + * 3026e3b0 a3 10ab6201 mov dword ptr ds:[0x162ab10],eax + * 3026e3b5 c705 14ab6201 00>mov dword ptr ds:[0x162ab14],0x0 + * 3026e3bf 31c0 xor eax,eax + * 3026e3c1 a3 54ab6201 mov dword ptr ds:[0x162ab54],eax + * 3026e3c6 c705 50ab6201 01>mov dword ptr ds:[0x162ab50],0x1 + * 3026e3d0 c705 a8ad6201 e8>mov dword ptr ds:[0x162ada8],0x1285e8 + * 3026e3da a1 c0ae6201 mov eax,dword ptr ds:[0x162aec0] + * 3026e3df 83c0 03 add eax,0x3 + * 3026e3e2 a3 c0ae6201 mov dword ptr ds:[0x162aec0],eax + * 3026e3e7 2b05 809e6101 sub eax,dword ptr ds:[0x1619e80] + * 3026e3ed 0f88 05000000 js 3026e3f8 + * 3026e3f3 -e9 085cdfd2 jmp pcsx2.03064000 + * 3026e3f8 833d 10ab6201 00 cmp dword ptr ds:[0x162ab10],0x0 + * 3026e3ff 0f85 49000000 jnz 3026e44e + * 3026e405 833d 14ab6201 00 cmp dword ptr ds:[0x162ab14],0x0 + * 3026e40c 0f85 3c000000 jnz 3026e44e + * 3026e412 a1 60ab6201 mov eax,dword ptr ds:[0x162ab60] + * 3026e417 c1e0 03 shl eax,0x3 + * 3026e41a 99 cdq + * 3026e41b a3 30ab6201 mov dword ptr ds:[0x162ab30],eax + * 3026e420 8915 34ab6201 mov dword ptr ds:[0x162ab34],edx + * 3026e426 c705 a8ad6201 04>mov dword ptr ds:[0x162ada8],0x128604 + * 3026e430 a1 c0ae6201 mov eax,dword ptr ds:[0x162aec0] + * 3026e435 83c0 02 add eax,0x2 + * 3026e438 a3 c0ae6201 mov dword ptr ds:[0x162aec0],eax + * 3026e43d 2b05 809e6101 sub eax,dword ptr ds:[0x1619e80] + * 3026e443 0f88 93220000 js 302706dc + * 3026e449 -e9 b25bdfd2 jmp pcsx2.03064000 + * 3026e44e a1 60ab6201 mov eax,dword ptr ds:[0x162ab60] + * 3026e453 c1e0 03 shl eax,0x3 + * 3026e456 99 cdq + * 3026e457 a3 30ab6201 mov dword ptr ds:[0x162ab30],eax + * 3026e45c 8915 34ab6201 mov dword ptr ds:[0x162ab34],edx + * 3026e462 c705 a8ad6201 f0>mov dword ptr ds:[0x162ada8],0x1285f0 + * 3026e46c a1 c0ae6201 mov eax,dword ptr ds:[0x162aec0] + * 3026e471 83c0 02 add eax,0x2 + * 3026e474 a3 c0ae6201 mov dword ptr ds:[0x162aec0],eax + * 3026e479 2b05 809e6101 sub eax,dword ptr ds:[0x1619e80] + * 3026e47f 0f88 91270000 js 30270c16 + * 3026e485 -e9 765bdfd2 jmp pcsx2.03064000 + * 3026e48a a1 30ab6201 mov eax,dword ptr ds:[0x162ab30] + * 3026e48f 0305 60ab6201 add eax,dword ptr ds:[0x162ab60] + * 3026e495 99 cdq + * 3026e496 a3 30ab6201 mov dword ptr ds:[0x162ab30],eax + * 3026e49b 8915 34ab6201 mov dword ptr ds:[0x162ab34],edx + * 3026e4a1 a1 30ab6201 mov eax,dword ptr ds:[0x162ab30] + * 3026e4a6 c1e0 05 shl eax,0x5 + * 3026e4a9 99 cdq + * 3026e4aa a3 30ab6201 mov dword ptr ds:[0x162ab30],eax + * 3026e4af 8915 34ab6201 mov dword ptr ds:[0x162ab34],edx + * 3026e4b5 a1 30ab6201 mov eax,dword ptr ds:[0x162ab30] + * 3026e4ba 05 e01f2b00 add eax,0x2b1fe0 + * 3026e4bf 99 cdq + * 3026e4c0 a3 20ab6201 mov dword ptr ds:[0x162ab20],eax + * 3026e4c5 8915 24ab6201 mov dword ptr ds:[0x162ab24],edx + * 3026e4cb 8b35 f0ac6201 mov esi,dword ptr ds:[0x162acf0] + * 3026e4d1 8935 a8ad6201 mov dword ptr ds:[0x162ada8],esi + * 3026e4d7 a1 c0ae6201 mov eax,dword ptr ds:[0x162aec0] + * 3026e4dc 83c0 07 add eax,0x7 + * 3026e4df a3 c0ae6201 mov dword ptr ds:[0x162aec0],eax + * 3026e4e4 2b05 809e6101 sub eax,dword ptr ds:[0x1619e80] + * 3026e4ea -0f88 155bdfd2 js pcsx2.03064005 + * 3026e4f0 -e9 0b5bdfd2 jmp pcsx2.03064000 + * 3026e4f5 a1 20ab6201 mov eax,dword ptr ds:[0x162ab20] + * 3026e4fa 8b15 24ab6201 mov edx,dword ptr ds:[0x162ab24] + * 3026e500 a3 00ac6201 mov dword ptr ds:[0x162ac00],eax + * 3026e505 8915 04ac6201 mov dword ptr ds:[0x162ac04],edx + * 3026e50b 833d 00ac6201 00 cmp dword ptr ds:[0x162ac00],0x0 + * 3026e512 75 0d jnz short 3026e521 + * 3026e514 833d 04ac6201 00 cmp dword ptr ds:[0x162ac04],0x0 + * 3026e51b 0f84 39000000 je 3026e55a + * 3026e521 31c0 xor eax,eax + */ +// Use fixed split for this hook +static void SpecialPS2HookMarvelous(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD text = regof(ecx, esp_base); + if (BYTE c = *(BYTE *)text) { // BYTE is unsigned + *data = text; + *len = ::LeadByteTable[c]; + *split = FIXED_SPLIT_VALUE * 3; // merge all threads + //*split = regof(esi, esp_base); + //*split = *(DWORD *)(esp_base + 4*5); // esp[5] + } +} + +bool InsertMarvelousPS2Hook() +{ + ConsoleOutput("vnreng: Marvelous PS2: enter"); + const BYTE bytes[] = { + 0x2b,0x05, XX4, // 3026e266 2b05 809e6101 sub eax,dword ptr ds:[0x1619e80] + 0x0f,0x88, 0x05,0x00,0x00,0x00, // 3026e26c 0f88 05000000 js 3026e277 + 0xe9, XX4, // 3026e272 -e9 895ddfd2 jmp pcsx2.03064000 + 0x8b,0x0d, XX4, // 3026e277 8b0d 40ab6201 mov ecx,dword ptr ds:[0x162ab40] + 0x89,0xc8, // 3026e27d 89c8 mov eax,ecx + 0xc1,0xe8, 0x0c, // 3026e27f c1e8 0c shr eax,0xc + 0x8b,0x04,0x85, XX4, // 3026e282 8b0485 30006d0d mov eax,dword ptr ds:[eax*4+0xd6d0030] + 0xbb, XX4, // 3026e289 bb 99e22630 mov ebx,0x3026e299 + 0x01,0xc1, // 3026e28e 01c1 add ecx,eax + 0x0f,0x88, XX4, // 3026e290 -0f88 6a2dd7d2 js pcsx2.02fe1000 + 0x0f,0xb6,0x01, // 3026e296 0fb601 movzx eax,byte ptr ds:[ecx] ; jichi: hook here + 0xa3, XX4, // 3026e299 a3 60ab6201 mov dword ptr ds:[0x162ab60],eax + 0xc7,0x05, XX4, 0x00,0x00,0x00,0x00,// 3026e29e c705 64ab6201 00>mov dword ptr ds:[0x162ab64],0x0 + 0xa1, XX4, // 3026e2a8 a1 60ab6201 mov eax,dword ptr ds:[0x162ab60] + 0x05, 0x7f,0xff,0xff,0xff, // 3026e2ad 05 7fffffff add eax,-0x81 + 0x99, // 3026e2b2 99 cdq + 0xa3 //70ab6201 // 3026e2b3 a3 70ab6201 mov dword ptr ds:[0x162ab70],eax + }; + enum { addr_offset = 0x3026e296 - 0x3026e266 }; + + DWORD addr = SafeMatchBytesInPS2Memory(bytes, sizeof(bytes)); + //addr = 0x30403967; + if (!addr) + ConsoleOutput("vnreng: Marvelous PS2: pattern not found"); + else { + //GROWL_DWORD(addr + addr_offset); + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.type = USING_STRING|NO_CONTEXT; // no context to get rid of return address + hp.text_fun = SpecialPS2HookMarvelous; + //hp.offset = pusha_ecx_off - 4; // ecx, get text in ds:[ecx] + //hp.length_offset = 1; + ConsoleOutput("vnreng: Marvelous PS2: INSERT"); + //GROWL_DWORD(hp.address); + NewHook(hp, "Marvelous PS2"); + } + + ConsoleOutput("vnreng: Marvelous PS2: leave"); + return addr; +} + +/** 8/3/2014 jichi + * Tested game: School Rumble 二学� * + * Fixed memory address. + * There is only one matched address. + * + * Debug method: Breakpoint the memory address. + * + * Issue: It cannot extract character name. + * + * 302072bd a3 c0ae9e01 mov dword ptr ds:[0x19eaec0],eax + * 302072c2 2b05 809e9d01 sub eax,dword ptr ds:[0x19d9e80] ; cdvdgiga.5976f736 + * 302072c8 ^0f88 f3cafcff js 301d3dc1 + * 302072ce -e9 2dcd21d3 jmp pcsx2.03424000 + * 302072d3 8b0d 50ab9e01 mov ecx,dword ptr ds:[0x19eab50] + * 302072d9 89c8 mov eax,ecx + * 302072db c1e8 0c shr eax,0xc + * 302072de 8b0485 3000e511 mov eax,dword ptr ds:[eax*4+0x11e50030] + * 302072e5 bb f5722030 mov ebx,0x302072f5 + * 302072ea 01c1 add ecx,eax + * 302072ec -0f88 0e9d19d3 js pcsx2.033a1000 + * 302072f2 0fb601 movzx eax,byte ptr ds:[ecx] + * 302072f5 a3 20ab9e01 mov dword ptr ds:[0x19eab20],eax + * 302072fa c705 24ab9e01 00>mov dword ptr ds:[0x19eab24],0x0 + * 30207304 8305 60ab9e01 ff add dword ptr ds:[0x19eab60],-0x1 + * 3020730b 9f lahf + * 3020730c 66:c1f8 0f sar ax,0xf + * 30207310 98 cwde + * 30207311 a3 64ab9e01 mov dword ptr ds:[0x19eab64],eax + * 30207316 8305 50ab9e01 01 add dword ptr ds:[0x19eab50],0x1 + * 3020731d 9f lahf + * 3020731e 66:c1f8 0f sar ax,0xf + * 30207322 98 cwde + * 30207323 a3 54ab9e01 mov dword ptr ds:[0x19eab54],eax + * 30207328 8b15 20ab9e01 mov edx,dword ptr ds:[0x19eab20] + * 3020732e 8b0d 30ab9e01 mov ecx,dword ptr ds:[0x19eab30] + * 30207334 89c8 mov eax,ecx + * 30207336 c1e8 0c shr eax,0xc + * 30207339 8b0485 3000e511 mov eax,dword ptr ds:[eax*4+0x11e50030] + * 30207340 bb 4f732030 mov ebx,0x3020734f + * 30207345 01c1 add ecx,eax + * 30207347 -0f88 739e19d3 js pcsx2.033a11c0 + * 3020734d 8811 mov byte ptr ds:[ecx],dl ; jichi: hook here, text in dl + * 3020734f 8305 30ab9e01 01 add dword ptr ds:[0x19eab30],0x1 + * 30207356 9f lahf + * 30207357 66:c1f8 0f sar ax,0xf + * 3020735b 98 cwde + * 3020735c a3 34ab9e01 mov dword ptr ds:[0x19eab34],eax + * 30207361 a1 60ab9e01 mov eax,dword ptr ds:[0x19eab60] + * 30207366 3b05 40ab9e01 cmp eax,dword ptr ds:[0x19eab40] + * 3020736c 75 11 jnz short 3020737f + * 3020736e a1 64ab9e01 mov eax,dword ptr ds:[0x19eab64] + * 30207373 3b05 44ab9e01 cmp eax,dword ptr ds:[0x19eab44] + * 30207379 0f84 28000000 je 302073a7 + * 3020737f c705 a8ad9e01 34>mov dword ptr ds:[0x19eada8],0x17eb34 + * 30207389 a1 c0ae9e01 mov eax,dword ptr ds:[0x19eaec0] + * 3020738e 83c0 09 add eax,0x9 + * 30207391 a3 c0ae9e01 mov dword ptr ds:[0x19eaec0],eax + * 30207396 2b05 809e9d01 sub eax,dword ptr ds:[0x19d9e80] ; cdvdgiga.5976f736 + * 3020739c ^0f88 31ffffff js 302072d3 + * 302073a2 -e9 59cc21d3 jmp pcsx2.03424000 + * 302073a7 c705 a8ad9e01 50>mov dword ptr ds:[0x19eada8],0x17eb50 + * 302073b1 a1 c0ae9e01 mov eax,dword ptr ds:[0x19eaec0] + * 302073b6 83c0 09 add eax,0x9 + * 302073b9 a3 c0ae9e01 mov dword ptr ds:[0x19eaec0],eax + * 302073be 2b05 809e9d01 sub eax,dword ptr ds:[0x19d9e80] ; cdvdgiga.5976f736 + * 302073c4 ^0f88 75cbfcff js 301d3f3f + * 302073ca -e9 31cc21d3 jmp pcsx2.03424000 + * 302073cf 8b15 10ac9e01 mov edx,dword ptr ds:[0x19eac10] + * 302073d5 8b0d 20ac9e01 mov ecx,dword ptr ds:[0x19eac20] + * 302073db 83c1 04 add ecx,0x4 + * 302073de 89c8 mov eax,ecx + * 302073e0 c1e8 0c shr eax,0xc + * 302073e3 8b0485 3000e511 mov eax,dword ptr ds:[eax*4+0x11e50030] + * 302073ea bb f9732030 mov ebx,0x302073f9 + * 302073ef 01c1 add ecx,eax + * 302073f1 -0f88 499e19d3 js pcsx2.033a1240 + * 302073f7 8911 mov dword ptr ds:[ecx],edx + * 302073f9 c705 a8ad9e01 5c>mov dword ptr ds:[0x19eada8],0x18d25c + * 30207403 a1 c0ae9e01 mov eax,dword ptr ds:[0x19eaec0] + * 30207408 83c0 03 add eax,0x3 + * 3020740b a3 c0ae9e01 mov dword ptr ds:[0x19eaec0],eax + * 30207410 2b05 809e9d01 sub eax,dword ptr ds:[0x19d9e80] ; cdvdgiga.5976f736 + * 30207416 0f88 05000000 js 30207421 + * 3020741c -e9 dfcb21d3 jmp pcsx2.03424000 + * 30207421 a1 50ac9e01 mov eax,dword ptr ds:[0x19eac50] + * 30207426 05 00a2ffff add eax,0xffffa200 + * 3020742b 99 cdq + * 3020742c a3 00ac9e01 mov dword ptr ds:[0x19eac00],eax + * 30207431 8915 04ac9e01 mov dword ptr ds:[0x19eac04],edx + * 30207437 31d2 xor edx,edx + * 30207439 8b0d d0ac9e01 mov ecx,dword ptr ds:[0x19eacd0] + * 3020743f 89c8 mov eax,ecx + * 30207441 c1e8 0c shr eax,0xc + * 30207444 8b0485 3000e511 mov eax,dword ptr ds:[eax*4+0x11e50030] + * 3020744b bb 5a742030 mov ebx,0x3020745a + * 30207450 01c1 add ecx,eax + * 30207452 -0f88 e89d19d3 js pcsx2.033a1240 + * 30207458 8911 mov dword ptr ds:[ecx],edx + * 3020745a a1 00ac9e01 mov eax,dword ptr ds:[0x19eac00] + * 3020745f 8b15 04ac9e01 mov edx,dword ptr ds:[0x19eac04] + */ +// Use fixed split for this hook +static void SpecialPS2HookMarvelous2(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD text = esp_base + pusha_edx_off - 4; // get text in dl: 3020734d 8811 mov byte ptr ds:[ecx],dl + if (BYTE c = *(BYTE *)text) { // BYTE is unsigned + *data = text; + *len = 1; + //*split = FIXED_SPLIT_VALUE * 4; // merge all threads + *split = regof(esi, esp_base); + //*split = *(DWORD *)(esp_base + 4*5); // esp[5] + } +} + +bool InsertMarvelous2PS2Hook() +{ + ConsoleOutput("vnreng: Marvelous2 PS2: enter"); + const BYTE bytes[] = { + // The following pattern is not sufficient + 0x89,0xc8, // 30207334 89c8 mov eax,ecx + 0xc1,0xe8, 0x0c, // 30207336 c1e8 0c shr eax,0xc + 0x8b,0x04,0x85, XX4, // 30207339 8b0485 3000e511 mov eax,dword ptr ds:[eax*4+0x11e50030] + 0xbb, XX4, // 30207340 bb 4f732030 mov ebx,0x3020734f + 0x01,0xc1, // 30207345 01c1 add ecx,eax + 0x0f,0x88, XX4, // 30207347 -0f88 739e19d3 js pcsx2.033a11c0 + 0x88,0x11, // 3020734d 8811 mov byte ptr ds:[ecx],dl ; jichi: hook here, text in dl + 0x83,0x05, XX4, 0x01, // 3020734f 8305 30ab9e01 01 add dword ptr ds:[0x19eab30],0x1 + 0x9f, // 30207356 9f lahf + 0x66,0xc1,0xf8, 0x0f, // 30207357 66:c1f8 0f sar ax,0xf + 0x98, // 3020735b 98 cwde + // The above pattern is not sufficient + 0xa3, XX4, // 3020735c a3 34ab9e01 mov dword ptr ds:[0x19eab34],eax + 0xa1, XX4, // 30207361 a1 60ab9e01 mov eax,dword ptr ds:[0x19eab60] + 0x3b,0x05, XX4, // 30207366 3b05 40ab9e01 cmp eax,dword ptr ds:[0x19eab40] + 0x75, 0x11, // 3020736c 75 11 jnz short 3020737f + 0xa1, XX4, // 3020736e a1 64ab9e01 mov eax,dword ptr ds:[0x19eab64] + 0x3b,0x05, XX4, // 30207373 3b05 44ab9e01 cmp eax,dword ptr ds:[0x19eab44] + 0x0f,0x84, XX4, // 30207379 0f84 28000000 je 302073a7 + 0xc7,0x05, XX8, // 3020737f c705 a8ad9e01 34>mov dword ptr ds:[0x19eada8],0x17eb34 + // The above pattern is not sufficient + 0xa1, XX4, // 30207389 a1 c0ae9e01 mov eax,dword ptr ds:[0x19eaec0] + 0x83,0xc0, 0x09, // 3020738e 83c0 09 add eax,0x9 + 0xa3, XX4, // 30207391 a3 c0ae9e01 mov dword ptr ds:[0x19eaec0],eax + 0x2b,0x05, XX4, // 30207396 2b05 809e9d01 sub eax,dword ptr ds:[0x19d9e80] ; cdvdgiga.5976f736 + 0x0f,0x88, XX4, // 3020739c ^0f88 31ffffff js 302072d3 + 0xe9, XX4, // 302073a2 -e9 59cc21d3 jmp pcsx2.03424000 + 0xc7,0x05, XX8, // 302073a7 c705 a8ad9e01 50>mov dword ptr ds:[0x19eada8],0x17eb50 + 0xa1, XX4, // 302073b1 a1 c0ae9e01 mov eax,dword ptr ds:[0x19eaec0] + 0x83,0xc0, 0x09, // 302073b6 83c0 09 add eax,0x9 + 0xa3, XX4, // 302073b9 a3 c0ae9e01 mov dword ptr ds:[0x19eaec0],eax + 0x2b,0x05, XX4, // 302073be 2b05 809e9d01 sub eax,dword ptr ds:[0x19d9e80] ; cdvdgiga.5976f736 + 0x0f,0x88, XX4, // 302073c4 ^0f88 75cbfcff js 301d3f3f + 0xe9, XX4, // 302073ca -e9 31cc21d3 jmp pcsx2.03424000 + 0x8b,0x15, XX4, // 302073cf 8b15 10ac9e01 mov edx,dword ptr ds:[0x19eac10] + 0x8b,0x0d, XX4, // 302073d5 8b0d 20ac9e01 mov ecx,dword ptr ds:[0x19eac20] + 0x83,0xc1, 0x04, // 302073db 83c1 04 add ecx,0x4 + 0x89,0xc8, // 302073de 89c8 mov eax,ecx + 0xc1,0xe8, 0x0c, // 302073e0 c1e8 0c shr eax,0xc + 0x8b,0x04,0x85, XX4, // 302073e3 8b0485 3000e511 mov eax,dword ptr ds:[eax*4+0x11e50030] + 0xbb, XX4, // 302073ea bb f9732030 mov ebx,0x302073f9 + 0x01,0xc1 // 302073ef 01c1 add ecx,eax + }; + enum { addr_offset = 0x3020734d - 0x30207334 }; + + DWORD addr = SafeMatchBytesInPS2Memory(bytes, sizeof(bytes)); + //addr = 0x30403967; + if (!addr) + ConsoleOutput("vnreng: Marvelous2 PS2: pattern not found"); + else { + //GROWL_DWORD(addr + addr_offset); + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.type = USING_STRING|NO_CONTEXT; // no context to get rid of return address + hp.text_fun = SpecialPS2HookMarvelous2; + //hp.offset = pusha_ecx_off - 4; // ecx, get text in ds:[ecx] + //hp.length_offset = 1; + ConsoleOutput("vnreng: Marvelous2 PS2: INSERT"); + //GROWL_DWORD(hp.address); + NewHook(hp, "Marvelous2 PS2"); + } + + ConsoleOutput("vnreng: Marvelous2 PS2: leave"); + return addr; +} + +#if 0 // jichi 7/19/2014: duplication text + +/** 7/19/2014 jichi + * Tested game: .hack//G.U. Vol.1 + */ +bool InsertNamcoPS2Hook() +{ + ConsoleOutput("vnreng: Namco PS2: enter"); + const BYTE bytes[1] = { + }; + enum { addr_offset = 0 }; + + //DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + //DWORD addr = 0x303baf26; + DWORD addr = 0x303C4B72; + if (!addr) + ConsoleOutput("vnreng: Namco PS2: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.type = USING_STRING|USING_SPLIT; // no context to get rid of return address + hp.offset = pusha_ecx_off - 4; // ecx + hp.split = hp.offset; // use ecx address to split + ConsoleOutput("vnreng: Namco PS2: INSERT"); + //GROWL_DWORD(hp.address); + NewHook(hp, "Namco PS2"); + } + + ConsoleOutput("vnreng: Namco PS2: leave"); + return addr; +} +#endif // 0 + +} // namespace Engine + +// EOF + +#if 0 // SEGA: loop text. BANDAI and Imageepoch should be sufficient +/** 7/25/2014 jichi sega.jp PSP engine + * Sample game: Shining Hearts + * Encoding: UTF-8 + * + * Debug method: simply add hardware break points to the matched memory + * All texts are in the memory. + * There are two memory addresses, but only one function addresses them. + * + * This function seems to be the same as Tecmo? + * + * 13513476 f0:90 lock nop ; lock prefix is not allowed + * 13513478 77 0f ja short 13513489 + * 1351347a c705 a8aa1001 38>mov dword ptr ds:[0x110aaa8],0x89cae38 + * 13513484 -e9 7bcb4ff0 jmp 03a10004 + * 13513489 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + * 1351348f 81e0 ffffff3f and eax,0x3fffffff + * 13513495 8bb0 00004007 mov esi,dword ptr ds:[eax+0x7400000] ; jichi: there are too many garbage here + * 1351349b 8b3d 7ca71001 mov edi,dword ptr ds:[0x110a77c] + * 135134a1 8d7f 04 lea edi,dword ptr ds:[edi+0x4] + * 135134a4 8b05 84a71001 mov eax,dword ptr ds:[0x110a784] + * 135134aa 81e0 ffffff3f and eax,0x3fffffff + * 135134b0 89b0 00004007 mov dword ptr ds:[eax+0x7400000],esi ; extract from esi + * 135134b6 8b2d 84a71001 mov ebp,dword ptr ds:[0x110a784] + * 135134bc 8d6d 04 lea ebp,dword ptr ss:[ebp+0x4] + * 135134bf 8b15 78a71001 mov edx,dword ptr ds:[0x110a778] + * 135134c5 81fa 01000000 cmp edx,0x1 + * 135134cb 8935 70a71001 mov dword ptr ds:[0x110a770],esi + * 135134d1 893d 7ca71001 mov dword ptr ds:[0x110a77c],edi + * 135134d7 892d 84a71001 mov dword ptr ds:[0x110a784],ebp + * 135134dd c705 88a71001 01>mov dword ptr ds:[0x110a788],0x1 + * 135134e7 0f84 16000000 je 13513503 + * 135134ed 832d c4aa1001 09 sub dword ptr ds:[0x110aac4],0x9 + * 135134f4 e9 23000000 jmp 1351351c + * 135134f9 013cae add dword ptr ds:[esi+ebp*4],edi + * 135134fc 9c pushfd + * 135134fd 08e9 or cl,ch + * 135134ff 20cb and bl,cl + * 13513501 4f dec edi + * 13513502 f0:832d c4aa1001>lock sub dword ptr ds:[0x110aac4],0x9 ; lock prefix + * 1351350a e9 b1000000 jmp 135135c0 + * 1351350f 015cae 9c add dword ptr ds:[esi+ebp*4-0x64],ebx + * 13513513 08e9 or cl,ch + * 13513515 0acb or cl,bl + * 13513517 4f dec edi + * 13513518 f0:90 lock nop ; lock prefix is not allowed + * 1351351a cc int3 + * 1351351b cc int3 + */ +// Read text from esi +static void SpecialPSPHookSega(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + LPCSTR text = LPCSTR(esp_base + pusha_esi_off - 4); // esi address + if (*text) { + *data = (DWORD)text; + *len = !text[0] ? 0 : !text[1] ? 1 : text[2] ? 2 : text[3] ? 3 : 4; + *split = regof(ebx, esp_base); + } +} + +bool InsertSegaPSPHook() +{ + ConsoleOutput("vnreng: SEGA PSP: enter"); + const BYTE bytes[] = { + 0x77, 0x0f, // 13513478 77 0f ja short 13513489 + 0xc7,0x05, XX8, // 1351347a c705 a8aa1001 38>mov dword ptr ds:[0x110aaa8],0x89cae38 + 0xe9, XX4, // 13513484 -e9 7bcb4ff0 jmp 03a10004 + 0x8b,0x05, XX4, // 13513489 8b05 7ca71001 mov eax,dword ptr ds:[0x110a77c] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 1351348f 81e0 ffffff3f and eax,0x3fffffff + 0x8b,0xb0, XX4, // 13513495 8bb0 00004007 mov esi,dword ptr ds:[eax+0x7400000] ; jichi: here are too many garbage + 0x8b,0x3d, XX4, // 1351349b 8b3d 7ca71001 mov edi,dword ptr ds:[0x110a77c] + 0x8d,0x7f, 0x04, // 135134a1 8d7f 04 lea edi,dword ptr ds:[edi+0x4] + 0x8b,0x05, XX4, // 135134a4 8b05 84a71001 mov eax,dword ptr ds:[0x110a784] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 135134aa 81e0 ffffff3f and eax,0x3fffffff + 0x89,0xb0 //, XX4, // 135134b0 89b0 00004007 mov dword ptr ds:[eax+0x7400000],esi ; jichi: hook here, get text in esi + }; + enum { memory_offset = 2 }; + enum { addr_offset = sizeof(bytes) - memory_offset }; + //enum { addr_offset = 0x13513495 - 0x13513478 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: SEGA PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.type = USING_STRING|NO_CONTEXT; // UTF-8 + hp.text_fun = SpecialPSPHookSega; + ConsoleOutput("vnreng: SEGA PSP: INSERT"); + NewHook(hp, "SEGA PSP"); + } + + ConsoleOutput("vnreng: SEGA PSP: leave"); + return addr; +} +#endif // 0 + + +#if 0 // jichi 7/14/2014: TODO there is text duplication issue? + +/** 7/13/2014 jichi SHADE.co.jp PSP engine + * Sample game: とある科学の趛�磁� (b-railgun.iso) + * + * CheatEngine/Ollydbg shew there are 4 memory hits to full text in SHIFT-JIS. + * CheatEngine is not able to trace JIT instructions. + * Ollydbg can track the latter two memory accesses > 0x1ffffffff + * + * The third access is 12ab3d64. There is one write access and 3 read accesses. + * But all the accesses are in a loop. + * So, the extracted text would suffer from infinite loop problem. + * + * Memory range: 0x0400000 - 139f000 + * + * 13400e10 90 nop + * 13400e11 cc int3 + * 13400e12 cc int3 + * 13400e13 cc int3 + * 13400e14 77 0f ja short 13400e25 + * 13400e16 c705 a8aa1001 08>mov dword ptr ds:[0x110aaa8],0x88c1308 + * 13400e20 -e9 dff161f3 jmp 06a20004 + * 13400e25 8b35 78a71001 mov esi,dword ptr ds:[0x110a778] + * 13400e2b 81c6 01000000 add esi,0x1 + * 13400e31 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 13400e37 81e0 ffffff3f and eax,0x3fffffff + * 13400e3d 0fb6b8 00004007 movzx edi,byte ptr ds:[eax+0x7400000] ; jichi: the data is in [eax+0x7400000] + * 13400e44 8b2d 78a71001 mov ebp,dword ptr ds:[0x110a778] + * 13400e4a 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + * 13400e4d 81ff 00000000 cmp edi,0x0 + * 13400e53 8935 70a71001 mov dword ptr ds:[0x110a770],esi + * 13400e59 893d 74a71001 mov dword ptr ds:[0x110a774],edi + * 13400e5f 892d 78a71001 mov dword ptr ds:[0x110a778],ebp + * 13400e65 0f84 16000000 je 13400e81 + * 13400e6b 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 13400e72 e9 21000000 jmp 13400e98 + * 13400e77 010c13 add dword ptr ds:[ebx+edx],ecx + * 13400e7a 8c08 mov word ptr ds:[eax],cs + * 13400e7c -e9 a2f161f3 jmp 06a20023 + * 13400e81 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 13400e88 e9 7f000000 jmp 13400f0c + * 13400e8d 0118 add dword ptr ds:[eax],ebx + * 13400e8f 138c08 e98cf161 adc ecx,dword ptr ds:[eax+ecx+0x61f18ce9> + * 13400e96 f3: prefix rep: ; superfluous prefix + * 13400e97 90 nop + * 13400e98 77 0f ja short 13400ea9 + * 13400e9a c705 a8aa1001 0c>mov dword ptr ds:[0x110aaa8],0x88c130c + * 13400ea4 -e9 5bf161f3 jmp 06a20004 + * 13400ea9 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 13400eaf 81e0 ffffff3f and eax,0x3fffffff + * 13400eb5 0fb6b0 00004007 movzx esi,byte ptr ds:[eax+0x7400000] + * 13400ebc 8b3d 78a71001 mov edi,dword ptr ds:[0x110a778] + * 13400ec2 8d7f 01 lea edi,dword ptr ds:[edi+0x1] + * 13400ec5 81fe 00000000 cmp esi,0x0 + * 13400ecb 8935 74a71001 mov dword ptr ds:[0x110a774],esi + * 13400ed1 893d 78a71001 mov dword ptr ds:[0x110a778],edi + * 13400ed7 0f84 16000000 je 13400ef3 + * 13400edd 832d c4aa1001 03 sub dword ptr ds:[0x110aac4],0x3 + * 13400ee4 ^e9 afffffff jmp 13400e98 + * 13400ee9 010c13 add dword ptr ds:[ebx+edx],ecx + * 13400eec 8c08 mov word ptr ds:[eax],cs + * 13400eee -e9 30f161f3 jmp 06a20023 + * 13400ef3 832d c4aa1001 03 sub dword ptr ds:[0x110aac4],0x3 + * 13400efa e9 0d000000 jmp 13400f0c + * 13400eff 0118 add dword ptr ds:[eax],ebx + * 13400f01 138c08 e91af161 adc ecx,dword ptr ds:[eax+ecx+0x61f11ae9> + * 13400f08 f3: prefix rep: ; superfluous prefix + * 13400f09 90 nop + * 13400f0a cc int3 + * 13400f0b cc int3 + */ +static void SpecialPSPHookShade(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD eax = regof(eax, esp_base); + LPCSTR text = LPCSTR(eax + hp->user_value); + if (*text) { + *data = (DWORD)text; + *len = ::strlen(text); + } +} + +bool InsertShadePSPHook() +{ + ConsoleOutput("vnreng: Shade PSP: enter"); + // TODO: Query MEM_Mapped at runtime + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa366902%28v=vs.85%29.aspx + enum : DWORD { StartAddress = 0x13390000, StopAddress = 0x13490000 }; + + const BYTE bytes[] = { + 0xcc, // 13400e12 cc int3 + 0xcc, // 13400e13 cc int3 + 0x77, 0x0f, // 13400e14 77 0f ja short 13400e25 + 0xc7,0x05, XX8, // 13400e16 c705 a8aa1001 08>mov dword ptr ds:[0x110aaa8],0x88c1308 + 0xe9, XX4, // 13400e20 -e9 dff161f3 jmp 06a20004 + 0x8b,0x35, XX4, // 13400e25 8b35 78a71001 mov esi,dword ptr ds:[0x110a778] + 0x81,0xc6, 0x01,0x00,0x00,0x00, // 13400e2b 81c6 01000000 add esi,0x1 + 0x8b,0x05, XX4, // 13400e31 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13400e37 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xb6,0xb8, XX4, // 13400e3d 0fb6b8 00004007 movzx edi,byte ptr ds:[eax+0x7400000] ; jichi: the data is in [eax+0x7400000] + 0x8b,0x2d, XX4, // 13400e44 8b2d 78a71001 mov ebp,dword ptr ds:[0x110a778] + 0x8d,0x6d, 0x01, // 13400e4a 8d6d 01 lea ebp,dword ptr ss:[ebp+0x1] + 0x81,0xff, 0x00,0x00,0x00,0x00 // 13400e4d 81ff 00000000 cmp edi,0x0 + }; + enum{ memory_offset = 3 }; + enum { addr_offset = 0x13400e3d - 0x13400e12 }; + + ULONG addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: Shade PSP: failed"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); + hp.text_fun = SpecialPSPHookShade; + hp.type = USING_STRING; + ConsoleOutput("vnreng: Shade PSP: INSERT"); + + // CHECKPOINT 7/14/2014: This would crash vnrcli + // I do not have permission to modify the JIT code region? + NewHook(hp, "Shade PSP"); + } + + //DWORD peek = 0x13400e14; + //GROWL_DWORD(*(BYTE *)peek); // supposed to be 0x77 ja + ConsoleOutput("vnreng: Shade PSP: leave"); + return addr; +} + +#endif // 0 + +#if 0 // jichi 7/17/2014: Disabled as there are so many text threads +/** jichi 7/17/2014 alternative Alchemist hook + * + * Sample game: your diary+ (moe-ydp.iso) + * The debugging method is the same as Alchemist1. + * + * It seems that hooks found in Alchemist games + * also exist in other games. + * + * This function is executed in a looped. + * + * 13400e12 cc int3 + * 13400e13 cc int3 + * 13400e14 77 0f ja short 13400e25 + * 13400e16 c705 a8aa1001 84>mov dword ptr ds:[0x110aaa8],0x8931084 + * 13400e20 -e9 dff148f0 jmp 03890004 + * 13400e25 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + * 13400e2b 81e0 ffffff3f and eax,0x3fffffff + * 13400e31 0fbeb0 00004007 movsx esi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + * 13400e38 8b3d 78a71001 mov edi,dword ptr ds:[0x110a778] + * 13400e3e 81fe 00000000 cmp esi,0x0 + * 13400e44 893d 7ca71001 mov dword ptr ds:[0x110a77c],edi + * 13400e4a 8935 80a71001 mov dword ptr ds:[0x110a780],esi + * 13400e50 0f85 16000000 jnz 13400e6c + * 13400e56 832d c4aa1001 03 sub dword ptr ds:[0x110aac4],0x3 + * 13400e5d e9 16010000 jmp 13400f78 + * 13400e62 01a0 109308e9 add dword ptr ds:[eax+0xe9089310],esp + * 13400e68 b7 f1 mov bh,0xf1 + * 13400e6a 48 dec eax + * 13400e6b f0:832d c4aa1001>lock sub dword ptr ds:[0x110aac4],0x3 ; lock prefix + * 13400e73 e9 0c000000 jmp 13400e84 + * 13400e78 0190 109308e9 add dword ptr ds:[eax+0xe9089310],edx + * 13400e7e a1 f148f090 mov eax,dword ptr ds:[0x90f048f1] + * 13400e83 cc int3 + * 13400e84 77 0f ja short 13400e95 + * 13400e86 c705 a8aa1001 90>mov dword ptr ds:[0x110aaa8],0x8931090 + * 13400e90 -e9 6ff148f0 jmp 03890004 + * 13400e95 8b35 78a71001 mov esi,dword ptr ds:[0x110a778] + * 13400e9b 8d76 01 lea esi,dword ptr ds:[esi+0x1] + * 13400e9e 8bc6 mov eax,esi + * 13400ea0 81e0 ffffff3f and eax,0x3fffffff + * 13400ea6 0fbeb8 00004007 movsx edi,byte ptr ds:[eax+0x7400000] + * 13400ead 81ff 00000000 cmp edi,0x0 + * 13400eb3 8935 78a71001 mov dword ptr ds:[0x110a778],esi + * 13400eb9 893d 80a71001 mov dword ptr ds:[0x110a780],edi + * 13400ebf 0f84 25000000 je 13400eea + * 13400ec5 8b35 78a71001 mov esi,dword ptr ds:[0x110a778] + * 13400ecb 8d76 01 lea esi,dword ptr ds:[esi+0x1] + * 13400ece 8935 78a71001 mov dword ptr ds:[0x110a778],esi + * 13400ed4 832d c4aa1001 04 sub dword ptr ds:[0x110aac4],0x4 + * 13400edb e9 24000000 jmp 13400f04 + * 13400ee0 019410 9308e939 add dword ptr ds:[eax+edx+0x39e90893],ed> + * 13400ee7 f1 int1 + * 13400ee8 48 dec eax + * 13400ee9 f0:832d c4aa1001>lock sub dword ptr ds:[0x110aac4],0x4 ; lock prefix + * 13400ef1 e9 82000000 jmp 13400f78 + * 13400ef6 01a0 109308e9 add dword ptr ds:[eax+0xe9089310],esp + * 13400efc 23f1 and esi,ecx + * 13400efe 48 dec eax + * 13400eff f0:90 lock nop ; lock prefix is not allowed + * 13400f01 cc int3 + * 13400f02 cc int3 + */ +// jichi 7/17/2014: Why this function is exactly the same as SpecialPSPHookImageepoch? +static void SpecialPSPHookAlchemist3(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) +{ + DWORD eax = regof(eax, esp_base); + DWORD text = eax + hp->user_value; + static DWORD lasttext; + if (text != lasttext && *(LPCSTR)text) { + *data = lasttext = text; + *len = ::strlen((LPCSTR)text); + *split = regof(ecx, esp_base); // use ecx "this" as split value? + } +} +bool InsertAlchemist3PSPHook() +{ + ConsoleOutput("vnreng: Alchemist3 PSP: enter"); + const BYTE bytes[] = { + //0xcc, // 13400e12 cc int3 + //0xcc, // 13400e13 cc int3 + 0x77, 0x0f, // 13400e14 77 0f ja short 13400e25 + 0xc7,0x05, XX8, // 13400e16 c705 a8aa1001 84>mov dword ptr ds:[0x110aaa8],0x8931084 + 0xe9, XX4, // 13400e20 -e9 dff148f0 jmp 03890004 + 0x8b,0x05, XX4, // 13400e25 8b05 78a71001 mov eax,dword ptr ds:[0x110a778] + 0x81,0xe0, 0xff,0xff,0xff,0x3f, // 13400e2b 81e0 ffffff3f and eax,0x3fffffff + 0x0f,0xbe,0xb0, XX4, // 13400e31 0fbeb0 00004007 movsx esi,byte ptr ds:[eax+0x7400000] ; jichi: hook here + 0x8b,0x3d, XX4, // 13400e38 8b3d 78a71001 mov edi,dword ptr ds:[0x110a778] + 0x81,0xfe, 0x00,0x00,0x00,0x00, // 13400e3e 81fe 00000000 cmp esi,0x0 + 0x89,0x3d, XX4, // 13400e44 893d 7ca71001 mov dword ptr ds:[0x110a77c],edi + 0x89,0x35, XX4, // 13400e4a 8935 80a71001 mov dword ptr ds:[0x110a780],esi + 0x0f,0x85 //, 16000000 // 13400e50 0f85 16000000 jnz 13400e6c + }; + enum { memory_offset = 3 }; + enum { addr_offset = 0x13407711 - 0x134076f4 }; + + DWORD addr = SafeMatchBytesInPSPMemory(bytes, sizeof(bytes)); + if (!addr) + ConsoleOutput("vnreng: Alchemist3 PSP: pattern not found"); + else { + HookParam hp = {}; + hp.address = addr + addr_offset; + hp.user_value = *(DWORD *)(hp.address + memory_offset); // use module to pass membase + hp.text_fun = SpecialPSPHookAlchemist3; + hp.type = USING_STRING|NO_CONTEXT; // no context is needed to get rid of variant retaddr + ConsoleOutput("vnreng: Alchemist3 PSP: INSERT"); + NewHook(hp, "Alchemist3 PSP"); + } + + ConsoleOutput("vnreng: Alchemist3 PSP: leave"); + return addr; +} +#endif // 0 + + +#if 0 // jichi 4/21/2014: Disabled as this does not work before mono.dll is loaded + +static HMODULE WaitForModuleReady(const char *name, int retryCount = 100, int sleepInterval = 100) // retry for 10 seconds +{ + for (int i = 0; i < retryCount; i++) { + if (HMODULE h = ::GetModuleHandleA(name)) + return h; + ::Sleep(sleepInterval); + } + return nullptr; +} + +/** + * jichi 4/21/2014: Mono (Unity3D) + * See (ok123): http://sakuradite.com/topic/214 + * Pattern: 33DB66390175 + * + * FIXME: This approach won't work before mono is loaded into the memory. + * + * Example: /HWN-8*0:3C@ mono.dll search 33DB66390175 + * - length_offset: 1 + * - module: 1690566707 = 0x64c40033 + * - off: 4294967284 = 0xfffffff4 = -0xc + * - split: 60 = 0x3c + * - type: 1114 = 0x45a + * + * Function starts: + * 1003b818 /$ 55 push ebp + * 1003b819 |. 8bec mov ebp,esp + * 1003b81b |. 51 push ecx + * 1003b81c |. 807d 10 00 cmp byte ptr ss:[ebp+0x10],0x0 + * 1003b820 |. 8b50 08 mov edx,dword ptr ds:[eax+0x8] + * 1003b823 |. 53 push ebx + * 1003b824 |. 8b5d 08 mov ebx,dword ptr ss:[ebp+0x8] + * 1003b827 |. 56 push esi + * 1003b828 |. 8b75 0c mov esi,dword ptr ss:[ebp+0xc] + * 1003b82b |. 57 push edi + * 1003b82c |. 8d78 0c lea edi,dword ptr ds:[eax+0xc] + * 1003b82f |. 897d 08 mov dword ptr ss:[ebp+0x8],edi + * 1003b832 |. 74 44 je short mono.1003b878 + * 1003b834 |. 2bf2 sub esi,edx + * 1003b836 |. 03f1 add esi,ecx + * 1003b838 |. 894d 10 mov dword ptr ss:[ebp+0x10],ecx + * 1003b83b |. 8975 08 mov dword ptr ss:[ebp+0x8],esi + * 1003b83e |. 3bce cmp ecx,esi + * 1003b840 |. 7f 67 jg short mono.1003b8a9 + * 1003b842 |. 8d4c4b 0c lea ecx,dword ptr ds:[ebx+ecx*2+0xc] + * 1003b846 |> 0fb707 /movzx eax,word ptr ds:[edi] + * 1003b849 |. 33db |xor ebx,ebx ; jichi hook here + * 1003b84b |. 66:3901 |cmp word ptr ds:[ecx],ax + * 1003b84e |. 75 16 |jnz short mono.1003b866 + * 1003b850 |. 8bf1 |mov esi,ecx + * 1003b852 |> 43 |/inc ebx + * 1003b853 |. 83c6 02 ||add esi,0x2 + * 1003b856 |. 3bda ||cmp ebx,edx + * 1003b858 |. 74 19 ||je short mono.1003b873 + * 1003b85a |. 66:8b06 ||mov ax,word ptr ds:[esi] + * 1003b85d |. 66:3b045f ||cmp ax,word ptr ds:[edi+ebx*2] + * 1003b861 |.^74 ef |\je short mono.1003b852 + * 1003b863 |. 8b75 08 |mov esi,dword ptr ss:[ebp+0x8] + * 1003b866 |> ff45 10 |inc dword ptr ss:[ebp+0x10] + * 1003b869 |. 83c1 02 |add ecx,0x2 + * 1003b86c |. 3975 10 |cmp dword ptr ss:[ebp+0x10],esi + * 1003b86f |.^7e d5 \jle short mono.1003b846 + */ +bool InsertMonoHook() +{ + enum { module = 0x64c40033 }; // hash of "mono.dll" + DWORD base = Util::FindModuleBase(module); + if (!base && WaitForModuleReady("mono.dll")) + base = Util::FindModuleBase(module); + + if (!base) { + ConsoleOutput("vnreng:Mono: module not found"); + return false; + } + + // Instruction pattern: 90FF503C83C4208B45EC + const BYTE ins[] = { + 0x33,0xdb, // 1003b849 |. 33db |xor ebx,ebx ; jichi hook here + 0x66,0x39,0x01, // 1003b84b |. 66:3901 |cmp word ptr ds:[ecx],ax + 0x75 //,0x16 // 1003b84e |. 75 16 |jnz short mono.1003b866 + }; + enum { addr_offset = 0 }; // no offset + enum { range = 0x50000 }; // larger than relative addresses = 0x3b849 + ULONG reladdr = SearchPattern(base, range, ins, sizeof(ins)); + //reladdr = 0x3b849; + GROWL(reladdr); + if (!reladdr) { + ConsoleOutput("vnreng:Mono: pattern not found"); + return false; + } + + HookParam hp = {}; + hp.address = base + reladdr + addr_offset; + //hp.module = module; + hp.length_offset = 1; + hp.offset = -0xc; + hp.split = 0x3c; + //hp.type = NO_CONTEXT|USING_SPLIT|MODULE_OFFSET|USING_UNICODE|DATA_INDIRECT; // 0x45a; + hp.type = NO_CONTEXT|USING_SPLIT|USING_UNICODE|DATA_INDIRECT; + + ConsoleOutput("vnreng: INSERT Mono"); + NewHook(hp, "Mono"); + return true; +} +#endif // 0 diff --git a/vnr/vnrhook/src/engine/engine.h b/vnr/vnrhook/src/engine/engine.h new file mode 100644 index 0000000..1305b09 --- /dev/null +++ b/vnr/vnrhook/src/engine/engine.h @@ -0,0 +1,170 @@ +#pragma once + +// engine/engine.h +// 8/23/2013 jichi +// See: http://ja.wikipedia.org/wiki/プロジェクト:美少女ゲーム系/ゲームエンジン + +#include + +struct HookParam; // defined in ith types.h + +namespace Engine { + +// Global variables +extern wchar_t process_name_[MAX_PATH], // cached + process_path_[MAX_PATH]; // cached +extern DWORD module_base_, + module_limit_; + +//extern LPVOID trigger_addr; +typedef bool (* trigger_fun_t)(LPVOID addr, DWORD frame, DWORD stack); +extern trigger_fun_t trigger_fun_; + +bool InsertMonoHooks(); // Mono + +// Wii engines + +bool InsertGCHooks(); // Dolphin +bool InsertVanillawareGCHook(); + +// PS2 engines + +bool InsertPCSX2Hooks(); // PCSX2 +bool InsertMarvelousPS2Hook(); // http://marvelous.jp +bool InsertMarvelous2PS2Hook(); // http://marvelous.jp +bool InsertTypeMoonPS2Hook(); // http://typemoon.com +//bool InsertNamcoPS2Hook(); + +// PSP engines + +void SpecialPSPHook(DWORD esp_base, HookParam *hp, DWORD *data, DWORD *split, DWORD *len); // General PSP extern hook + +bool InsertPPSSPPHooks(); // PPSSPPWindows + +bool InsertPPSSPPHLEHooks(); +bool InsertOtomatePPSSPPHook(); // PSP otomate.jp, 0.9.9.0 only + +bool Insert5pbPSPHook(); // PSP 5pb.jp +bool InsertAlchemistPSPHook(); // PSP Alchemist-net.co.jp, 0.9.8 only +bool InsertAlchemist2PSPHook(); // PSP Alchemist-net.co.jp +bool InsertBandaiNamePSPHook(); // PSP Bandai.co.jp +bool InsertBandaiPSPHook(); // PSP Bandai.co.jp +bool InsertBroccoliPSPHook(); // PSP Broccoli.co.jp +bool InsertFelistellaPSPHook(); // PSP felistella.co.jp + +bool InsertCyberfrontPSPHook(); // PSP CYBERFRONT (closed) +bool InsertImageepochPSPHook(); // PSP Imageepoch.co.jp +bool InsertImageepoch2PSPHook();// PSP Imageepoch.co.jp +bool InsertKadokawaNamePSPHook(); // PSP Kadokawa.co.jp +bool InsertKonamiPSPHook(); // PSP Konami.jp +bool InsertTecmoPSPHook(); // PSP Koeitecmo.co.jp +//bool InsertTypeMoonPSPHook(); // PSP Typemoon.com + +bool InsertOtomatePSPHook(); // PSP Otomate.jp, 0.9.8 only +//bool InsertOtomate2PSPHook(); // PSP otomate.jp >= 0.9.9.1 + +bool InsertIntensePSPHook(); // PSP Intense.jp +bool InsertKidPSPHook(); // PSP Kid-game.co.jp +bool InsertNippon1PSPHook(); // PSP Nippon1.jp +bool InsertNippon2PSPHook(); // PSP Nippon1.jp +bool InsertYetiPSPHook(); // PSP Yetigame.jp +bool InsertYeti2PSPHook(); // PSP Yetigame.jp + +// Game-speicific engines +bool InsertShinyDaysGameHook(); // ShinyDays +bool InsertLovaGameHook(); // lova.jp + +// PC engines + +bool Insert2RMHook(); // 2RM - Adventure Engine +bool Insert5pbHook(); // 5pb.jp, PSP/PS3 games ported to PC +bool InsertAB2TryHook(); // Yane@AkabeiSoft2Try: YaneSDK.dll. +bool InsertAbelHook(); // Abel +bool InsertAdobeAirHook(); // Adobe AIR +bool InsertAdobeFlash10Hook(); // Adobe Flash Player 10 +bool InsertAliceHook(); // System40@AliceSoft; do not work for latest alice games +//bool InsertAmuseCraftHook(); // AMUSE CRAFT: *.pac +bool InsertAnex86Hook(); // Anex86: anex86.exe +bool InsertAOSHook(); // AOS: *.aos +bool InsertApricoTHook(); // Apricot: arc.a* +bool InsertArtemisHook(); // Artemis Engine: *.pfs +bool InsertAtelierHook(); // Atelier Kaguya: message.dat +bool InsertBGIHook(); // BGI: BGI.* +bool InsertBootupHook(); // Bootup: Bootup.dat +bool InsertC4Hook(); // C4: C4.EXE or XEX.EXE +bool InsertCaramelBoxHook(); // Caramel: *.bin +bool InsertCandyHook(); // SystemC@CandySoft: *.fpk +bool InsertCatSystemHook(); // CatSystem2: *.int +bool InsertCMVSHook(); // CMVS: data/pack/*.cpz; do not support the latest cmvs32.exe and cmvs64.exe +bool InsertCotophaHook(); // Cotopha: *.noa +bool InsertDebonosuHook(); // Debonosu: bmp.bak and dsetup.dll +bool InsertEaglsHook(); // E.A.G.L.S: EAGLES.dll +bool InsertEMEHook(); // EmonEngine: emecfg.ecf +bool InsertEscudeHook(); // Escude +bool InsertEushullyHook(); // Eushully: AGERC.DLL +bool InsertExpHook(); // EXP: http://www.exp-inc.jp +bool InsertFocasLensHook(); // FocasLens: Dat/*.arc, http://www.fo-lens.net +bool InsertGXPHook(); // GXP: *.gxp +bool InsertHorkEyeHook(); // HorkEye: resource string +bool InsertKAGParserHook(); // plugin/KAGParser.dll +bool InsertKAGParserExHook(); // plugin/KAGParserEx.dll +bool InsertKiriKiriHook(); // KiriKiri: *.xp3, resource string +bool InsertKiriKiriZHook(); // KiriKiri: *.xp3, resource string +bool InsertLeafHook(); // Leaf: *.pak +bool InsertLiveHook(); // Live: live.dll +bool InsertLunaSoftHook(); // LunaSoft: Pac/*.pac +bool InsertMalieHook(); // Malie@light: malie.ini +bool InsertMajiroHook(); // Majiro: *.arc +bool InsertMarineHeartHook(); // Marine Heart: SAISYS.exe +bool InsertMBLHook(); // MBL: *.mbl +bool InsertMEDHook(); // MED: *.med +bool InsertMinkHook(); // Mink: *.at2 +//bool InsertMonoHook(); // Mono (Unity3D): */Mono/mono.dll +bool InsertNeXASHook(); // NeXAS: Thumbnail.pac +bool InsertNextonHook(); // NEXTON: aInfo.db +bool InsertNexton1Hook(); +bool InsertNitroplusHook(); // Nitroplus: *.npa +bool InsertPalHook(); // AMUSE CRAFT: *.pac +bool InsertPensilHook(); // Pensil: PSetup.exe +bool InsertQLIEHook(); // QLiE: GameData/*.pack +//bool InsertRai7Hook(); // Rai7puk: rai7.exe +bool InsertRejetHook(); // Rejet: Module/{gd.dat,pf.dat,sd.dat} +bool InsertRUGPHook(); // rUGP: rUGP.exe +bool InsertRetouchHook(); // Retouch: resident.dll +bool InsertRREHook(); // RunrunEngine: rrecfg.rcf +bool InsertShinaHook(); // ShinaRio: Rio.ini +bool InsertElfHook(); // elf: Silky.exe +bool InsertScenarioPlayerHook();// sol-fa-soft: *.iar && *.sec5 +bool InsertSiglusHook(); // SiglusEngine: SiglusEngine.exe +bool InsertSideBHook(); // SideB: Copyright side-B +bool InsertSilkysHook(); // SilkysPlus +bool InsertSyuntadaHook(); // Syuntada: dSoh.dat +bool InsertSystem43Hook(); // System43@AliceSoft: AliceStart.ini +bool InsertSystemAoiHook(); // SystemAoi: *.vfs +bool InsertTamamoHook(); // Tamamo +bool InsertTanukiHook(); // Tanuki: *.tak +bool InsertTaskforce2Hook(); // Taskforce2.exe +bool InsertTencoHook(); // Tenco: Check.mdx +bool InsertTriangleHook(); // Triangle: Execle.exe +bool InsertUnicornHook(); // Gsen18: *.szs|Data/*.szs +bool InsertWillPlusHook(); // WillPlus: Rio.arc +bool InsertWolfHook(); // Wolf: Data.wolf +bool InsertYukaSystem2Hook(); // YukaSystem2: *.ykc +bool InsertYurisHook(); // YU-RIS: *.ypf + +void InsertBrunsHook(); // Bruns: bruns.exe +void InsertIronGameSystemHook();// IroneGameSystem: igs_sample.exe +void InsertLucifenHook(); // Lucifen@Navel: *.lpk +void InsertRyokuchaHook(); // Ryokucha: _checksum.exe +void InsertRealliveHook(); // RealLive: RealLive*.exe +void InsertStuffScriptHook(); // Stuff: *.mpk +void InsertTinkerBellHook(); // TinkerBell: arc00.dat +void InsertWaffleHook(); // WAFFLE: cg.pak + +// CIRCUS: avdata/ +bool InsertCircusHook1(); +bool InsertCircusHook2(); + +} // namespace Engine + +// EOF diff --git a/vnr/vnrhook/src/engine/hookdefs.h b/vnr/vnrhook/src/engine/hookdefs.h new file mode 100644 index 0000000..f429a2d --- /dev/null +++ b/vnr/vnrhook/src/engine/hookdefs.h @@ -0,0 +1,12 @@ +#pragma once + +// engine/hookdefs.h +// 7/20/2014 jichi + +// For HookParam user flags +enum HookParamFlag : unsigned long { + HPF_Null = 0 // never used + , HPF_IgnoreSameAddress = 1 // ignore the last same text address +}; + +// EOF diff --git a/vnr/vnrhook/src/engine/match.cc b/vnr/vnrhook/src/engine/match.cc new file mode 100644 index 0000000..011e83a --- /dev/null +++ b/vnr/vnrhook/src/engine/match.cc @@ -0,0 +1,936 @@ +// match.cc +// 8/9/2013 jichi +// Branch: ITH_Engine/engine.cpp, revision 133 + +#ifdef _MSC_VER +# pragma warning (disable:4100) // C4100: unreference formal parameter +//# pragma warning (disable:4733) // C4733: Inline asm assigning to 'FS:0' : handler not registered as safe handler +#endif // _MSC_VER + +#include "src/engine/match.h" +#include "src/engine/engine.h" +#include "src/engine/pchooks.h" +#include "src/util/growl.h" +#include "src/util/util.h" +#include "src/main.h" +#include "src/except.h" +#include "ithsys/ithsys.h" +#include "ccutil/ccmacro.h" + +//#define ConsoleOutput(...) (void)0 // jichi 8/18/2013: I don't need ConsoleOutput + +enum { MAX_REL_ADDR = 0x200000 }; // jichi 8/18/2013: maximum relative address + +// - Global variables - + +namespace Engine { + +WCHAR process_name_[MAX_PATH], // cached + process_path_[MAX_PATH]; // cached + +DWORD module_base_, + module_limit_; + +//LPVOID trigger_addr; +trigger_fun_t trigger_fun_; + +} // namespace Engine + +// - Methods - + +namespace Engine { namespace { // unnamed + +bool DetermineGameHooks() // 7/19/2015 +{ +#if 0 // jichi 7/19/2015: Disabled as it will crash the game + if (IthFindFile(L"UE3ShaderCompileWorker.exe") && IthFindFile(L"awesomium_process.exe")) { + InsertLovaGameHook(); + return true; + } +#endif // 0 + return false; +} + +// jichi 7/17/2014: Disable GDI hooks for PPSSPP +bool DeterminePCEngine() +{ + if (DetermineGameHooks()) { + ConsoleOutput("vnreng: found game-specific hook"); + return true; + } + + if (IthFindFile(L"PPSSPP*.exe")) { // jichi 7/12/2014 PPSSPPWindows.exe, PPSSPPEX.exe PPSSPPSP.exe + InsertPPSSPPHooks(); + return true; + } + + if (IthFindFile(L"pcsx2*.exe")) { // jichi 7/19/2014 PCSX2.exe or PCSX2WX.exe + InsertPCSX2Hooks(); + return true; + } + + if (IthFindFile(L"Dolphin.exe")) { // jichi 7/20/2014 + InsertGCHooks(); + return true; + } + + // jichi 5/14/2015: Skip hijacking BALDRSKY ZEROs + if (IthCheckFile(L"bsz_Data\\Mono\\mono.dll") || IthCheckFile(L"bsz2_Data\\Mono\\mono.dll")) { + ConsoleOutput("vnreng: IGNORE BALDRSKY ZEROs"); + return true; + } + if (::GetModuleHandleA("mono.dll")) { + InsertMonoHooks(); + + // 3/20/2015 jichi + // Always insert GDI hooks even for Mono games + // For example: ?? need GetGlyphOutlineA + PcHooks::hookGDIFunctions(); + return true; + } + + // PC games + PcHooks::hookGDIFunctions(); + EnableGDIPlusHooks(); + return false; +} + +bool DetermineEngineByFile1() +{ + if (IthFindFile(L"*.xp3") || Util::SearchResourceString(L"TVP(KIRIKIRI)")) { + if (Util::SearchResourceString(L"TVP(KIRIKIRI) Z ")) { // TVP(KIRIKIRI) Z CORE + // jichi 11/24/2014: Disabled that might crash VBH + //if (IthCheckFile(L"plugin\\KAGParser.dll")) + // InsertKAGParserHook(); + //else if (IthCheckFile(L"plugin\\KAGParserEx.dll")) + // InsertKAGParserExHook(); + if (InsertKiriKiriZHook()) + return true; + } + InsertKiriKiriHook(); + return true; + } + // 8/2/2014 jichi: Game name shown as 2RM - Adventure Engine, text also in GetGlyphOutlineA + if (Util::SearchResourceString(L"2RM") && Util::SearchResourceString(L"Adventure Engine")) { + Insert2RMHook(); + return true; + } + // 8/2/2014 jichi: Copyright is side-B, a conf.dat will be generated after the game is launched + // It also contains lua5.1.dll and lua5.dll + if (Util::SearchResourceString(L"side-B")) { + InsertSideBHook(); + return true; + } + if (IthFindFile(L"bgi.*") || IthFindFile(L"BHVC.exe") || IthFindFile(L"sysgrp.arc")) { + InsertBGIHook(); + return true; + } + if (IthCheckFile(L"Bootup.dat") && InsertBootupHook()) // 5/22/2015 Bootup + // lstrlenW can also find text with repetition though + return true; + if (IthCheckFile(L"AGERC.DLL")) { // 6/1/2014 jichi: Eushully, AGE.EXE + InsertEushullyHook(); + return true; + } + if (IthFindFile(L"data*.arc") && IthFindFile(L"stream*.arc")) { + InsertMajiroHook(); + return true; + } + // jichi 5/31/2014 + if (//IthCheckFile(L"Silkys.exe") || // It might or might not have Silkys.exe + // data, effect, layer, mes, music + IthCheckFile(L"data.arc") && IthCheckFile(L"effect.arc") && IthCheckFile(L"mes.arc")) { + InsertElfHook(); + return true; + } + // jichi 6/9/2015: Skip Silkys Sakura + if ( // Almost the same as Silkys except mes.arc is replaced by Script.arc + IthCheckFile(L"data.arc") && IthCheckFile(L"effect.arc") && IthCheckFile(L"Script.arc")) { + InsertSilkysHook(); + return true; + } + if (IthFindFile(L"data\\pack\\*.cpz")) { + InsertCMVSHook(); + return true; + } + // jichi 10/12/2013: Restore wolf engine + // jichi 10/18/2013: Check for data/*.wolf + if (IthFindFile(L"data.wolf") || IthFindFile(L"data\\*.wolf")) { + InsertWolfHook(); + return true; + } + if (IthCheckFile(L"AdvData\\DAT\\NAMES.DAT")) { + InsertCircusHook1(); + return true; + } + if (IthCheckFile(L"AdvData\\GRP\\NAMES.DAT")) { + InsertCircusHook2(); + return true; + } + if (IthFindFile(L"*.noa") || IthFindFile(L"data\\*.noa")) { + InsertCotophaHook(); + return true; + } + if (IthFindFile(L"*.pfs")) { // jichi 10/1/2013 + InsertArtemisHook(); + return true; + } + if (IthFindFile(L"*.int")) { + InsertCatSystemHook(); + return true; + } + if (IthCheckFile(L"message.dat")) { + InsertAtelierHook(); + return true; + } + if (IthCheckFile(L"Check.mdx")) { // jichi 4/1/2014: AUGame + InsertTencoHook(); + return true; + } + // jichi 12/25/2013: It may or may not be QLIE. + // AlterEgo also has GameData/sound.pack but is not QLIE + if (IthFindFile(L"GameData\\*.pack") && InsertQLIEHook()) + return true; + + if (IthCheckFile(L"dll\\Pal.dll")) { + InsertPalHook(); + return true; + } + + if (IthFindFile(L"*.pac")) { + // jichi 6/3/2014: AMUSE CRAFT and SOFTPAL + // Selectively insert, so that lstrlenA can still get correct text if failed + //if (IthCheckFile(L"dll\\resource.dll") && IthCheckFile(L"dll\\pal.dll") && InsertAmuseCraftHook()) + // return true; + + if (IthCheckFile(L"Thumbnail.pac")) { + //ConsoleOutput("vnreng: IGNORE NeXAS"); + InsertNeXASHook(); // jichi 7/6/2014: GIGA + return true; + } + + if (Util::SearchResourceString(L"SOFTPAL")) { + ConsoleOutput("vnreng: IGNORE SoftPal UNiSONSHIFT"); + return true; + } + } + // jichi 12/27/2014: LunaSoft + if (IthFindFile(L"Pac\\*.pac")) { + InsertLunaSoftHook(); + return true; + } + // jichi 9/16/2013: Add Gesen18 + if (IthFindFile(L"*.szs") || IthFindFile(L"Data\\*.szs")) { + InsertUnicornHook(); + return true; + } + // jichi 12/22/2013: Add rejet + if (IthCheckFile(L"gd.dat") && IthCheckFile(L"pf.dat") && IthCheckFile(L"sd.dat")) { + InsertRejetHook(); + return true; + } + // Only examined with version 1.0 + //if (IthFindFile(L"Adobe AIR\\Versions\\*\\Adobe AIR.dll")) { // jichi 4/15/2014: FIXME: Wildcard not working + if (IthCheckFile(L"Adobe AIR\\Versions\\1.0\\Adobe AIR.dll")) { // jichi 4/15/2014: Adobe AIR + InsertAdobeAirHook(); + return true; + } + return false; +} + +bool DetermineEngineByFile2() +{ + if (IthCheckFile(L"resident.dll")) { + InsertRetouchHook(); + return true; + } + if (IthCheckFile(L"Malie.ini") || IthCheckFile(L"Malie.exe")) { // jichi: 9/9/2014: Add malie.exe in case malie.ini is missing + InsertMalieHook(); + return true; + } + if (IthCheckFile(L"live.dll")) { + InsertLiveHook(); + return true; + } + // 9/5/2013 jichi + if (IthCheckFile(L"aInfo.db")) { + InsertNextonHook(); + return true; + } + if (IthFindFile(L"*.lpk")) { + InsertLucifenHook(); + return true; + } + if (IthCheckFile(L"cfg.pak")) { + InsertWaffleHook(); + return true; + } + if (IthCheckFile(L"Arc00.dat")) { + InsertTinkerBellHook(); + return true; + } + if (IthFindFile(L"*.vfs")) { // jichi 7/6/2014: Better to test AoiLib.dll? ja.wikipedia.org/wiki/իȫϫ + InsertSystemAoiHook(); + return true; + } + if (IthFindFile(L"*.mbl")) { + InsertMBLHook(); + return true; + } + // jichi 8/1/2014: YU-RIS engine, lots of clockup game also has this pattern + if (IthFindFile(L"pac\\*.ypf") || IthFindFile(L"*.ypf")) { + // jichi 8/14/2013: CLOCLUP: "?֫쫹֫?" would crash the game. + if (!IthCheckFile(L"noblesse.exe")) + InsertYurisHook(); + return true; + } + if (IthFindFile(L"*.npa")) { + InsertNitroplusHook(); + return true; + } + return false; +} + +bool DetermineEngineByFile3() +{ + //if (IthCheckFile(L"libscr.dll")) { // already checked + // InsertBrunsHook(); + // return true; + //} + + // jichi 10/12/2013: Sample args.txt: + // See: http://tieba.baidu.com/p/2631413816 + // -workdir + // . + // -loadpath + // . + // am.cfg + if (IthCheckFile(L"args.txt")) { + InsertBrunsHook(); + return true; + } + if (IthCheckFile(L"emecfg.ecf")) { + InsertEMEHook(); + return true; + } + if (IthCheckFile(L"rrecfg.rcf")) { + InsertRREHook(); + return true; + } + if (IthFindFile(L"*.fpk") || IthFindFile(L"data\\*.fpk")) { + InsertCandyHook(); + return true; + } + if (IthFindFile(L"arc.a*")) { + InsertApricoTHook(); + return true; + } + if (IthFindFile(L"*.mpk")) { + InsertStuffScriptHook(); + return true; + } + if (IthCheckFile(L"Execle.exe")) { + InsertTriangleHook(); + return true; + } + // jichi 2/28/2015: No longer work for "᡿??꫹ episode I" from Primula + //if (IthCheckFile(L"PSetup.exe")) { + // InsertPensilHook(); + // return true; + //} + if (IthCheckFile(L"Yanesdk.dll")) { + InsertAB2TryHook(); + return true; + } + if (IthFindFile(L"*.med")) { + InsertMEDHook(); + return true; + } + return false; +} + +bool DetermineEngineByFile4() +{ + if (IthCheckFile(L"EAGLS.dll")) { // jichi 3/24/2014: E.A.G.L.S + //ConsoleOutput("vnreng: IGNORE EAGLS"); + InsertEaglsHook(); + return true; + } + if (IthCheckFile(L"bmp.pak") && IthCheckFile(L"dsetup.dll")) { + // 1/1/2016 jich: skip izumo4 from studio ego that is not supported by debonosu + if (IthFindFile(L"*izumo4*.exe")) { + PcHooks::hookLstrFunctions(); + return true; + } + InsertDebonosuHook(); + return true; + } + if (IthCheckFile(L"C4.EXE") || IthCheckFile(L"XEX.EXE")) { + InsertC4Hook(); + return true; + } + if (IthCheckFile(L"Rio.arc") && IthFindFile(L"Chip*.arc")) { + InsertWillPlusHook(); + return true; + } + if (IthFindFile(L"*.tac")) { + InsertTanukiHook(); + return true; + } + if (IthFindFile(L"*.gxp")) { + InsertGXPHook(); + return true; + } + if (IthFindFile(L"*.aos")) { // jichi 4/2/2014: AOS hook + InsertAOSHook(); + return true; + } + if (IthFindFile(L"*.at2")) { // jichi 12/23/2014: Mink, sample files: voice.at2, voice.det, voice.nme + InsertMinkHook(); + return true; + } + if (IthFindFile(L"*.ykc")) { // jichi 7/15/2014: YukaSystem1 is not supported, though + //ConsoleOutput("vnreng: IGNORE YKC:Feng/HookSoft(SMEE)"); + InsertYukaSystem2Hook(); + return true; + } + if (IthFindFile(L"model\\*.hed")) { // jichi 9/8/2014: EXP + InsertExpHook(); + return true; + } + // jichi 2/6/2015 + // dPi.dat, dPih.dat, dSc.dat, dSch.dat, dSo.dat, dSoh.dat, dSy.dat + //if (IthCheckFile(L"dSoh.dat")) { // no idea why this file does not work + if (IthCheckFile(L"dSch.dat")) { + InsertSyuntadaHook(); + return true; + } + + // jichi 2/28/2015: Delay checking Pensil in case something went wrong + // File pattern observed in [Primula] ᡿??꫹ episode I + // - PSetup.exe no longer exists + // - MovieTexture.dll information shows MovieTex dynamic library, copyright Pensil 2013 + // - ta_trial.exe information shows 2XT - Primula Adventure Engine + if (IthCheckFile(L"PSetup.exe") || IthFindFile(L"PENCIL.*") || Util::SearchResourceString(L"2XT -")) { + InsertPensilHook(); + return true; + } + return false; +} + +bool DetermineEngineByProcessName() +{ + WCHAR str[MAX_PATH]; + wcscpy(str, process_name_); + _wcslwr(str); // lower case + + if (wcsstr(str,L"reallive") || IthCheckFile(L"Reallive.exe") || IthCheckFile(L"REALLIVEDATA\\Start.ini")) { + InsertRealliveHook(); + return true; + } + + // jichi 8/19/2013: DO NOT WORK for games likeϫԫ᫢ + //if (wcsstr(str,L"cmvs32") || wcsstr(str,L"cmvs64")) { + // InsertCMVSHook(); + // return true; + //} + + // jichi 8/17/2013: Handle "~" + if (wcsstr(str, L"siglusengine") || !wcsncmp(str, L"siglus~", 7) || IthCheckFile(L"SiglusEngine.exe")) { + InsertSiglusHook(); + return true; + } + + if (wcsstr(str, L"taskforce2") || !wcsncmp(str, L"taskfo~", 7) || IthCheckFile(L"Taskforce2.exe")) { + InsertTaskforce2Hook(); + return true; + } + + if (wcsstr(str,L"rugp") || IthCheckFile(L"rugp.exe")) { + InsertRUGPHook(); + return true; + } + + // jichi 8/17/2013: Handle "~" + if (wcsstr(str, L"igs_sample") || !wcsncmp(str, L"igs_sa~", 7) || IthCheckFile(L"igs_sample.exe")) { + InsertIronGameSystemHook(); + return true; + } + + if (wcsstr(str, L"bruns") || IthCheckFile(L"bruns.exe")) { + InsertBrunsHook(); + return true; + } + + if (wcsstr(str, L"anex86") || IthCheckFile(L"anex86.exe")) { + InsertAnex86Hook(); + return true; + } + + // jichi 8/17/2013: Handle "~" + if (wcsstr(str, L"shinydays") || !wcsncmp(str, L"shinyd~", 7) || IthCheckFile(L"ShinyDays.exe")) { + InsertShinyDaysGameHook(); + return true; + } + + // jichi 10/3/2013: FIXME: Does not work + // Raise C0000005 even with admin priv + //if (wcsstr(str, L"bsz")) { // BALDRSKY ZERO + // InsertBaldrHook(); + // return true; + //} + + if (wcsstr(process_name_, L"SAISYS") || IthCheckFile(L"SaiSys.exe")) { // jichi 4/19/2014: Marine Heart + InsertMarineHeartHook(); + return true; + } + + DWORD len = wcslen(str); + + // jichi 8/24/2013: Checking for Rio.ini or $procname.ini + //wcscpy(str+len-4, L"_?.war"); + //if (IthFindFile(str)) { + // InsertShinaHook(); + // return true; + //} + if (InsertShinaHook()) + return true; + + // jichi 8/10/2013: Since *.bin is common, move CaramelBox to the end + str[len - 3] = L'b'; + str[len - 2] = L'i'; + str[len - 1] = L'n'; + str[len] = 0; + if ((IthCheckFile(str) || IthCheckFile(L"trial.bin")) // jichi 7/8/2014: add trial.bin + && InsertCaramelBoxHook()) + return true; + + // jichi 7/23/2015 It also has gameexe.bin existed + if (IthCheckFile(L"configure.exe") && IthCheckFile(L"configure.cfg") && IthCheckFile(L"gfx.bin")) { + InsertEscudeHook(); + return true; + } + + // This must appear at last since str is modified + wcscpy(str + len - 4, L"_checksum.exe"); + if (IthCheckFile(str)) { + InsertRyokuchaHook(); + + if (IthFindFile(L"*.iar") && IthFindFile(L"*.sec5")) // jichi 9/27/2014: For new Ryokucha games + InsertScenarioPlayerHook(); + return true; + } + + return false; +} + +bool DetermineEngineOther() +{ + if (InsertAliceHook()) + return true; + // jichi 1/19/2015: Disable inserting Lstr for System40 + // See: http://sakuradite.com/topic/618 + if (IthCheckFile(L"System40.ini")) { + ConsoleOutput("vnreng: IGNORE old System40.ini"); + return true; + } + // jichi 12/26/2013: Add this after alicehook + if (IthCheckFile(L"AliceStart.ini")) { + InsertSystem43Hook(); + return true; + } + + // jichi 8/24/2013: Move into functions + static BYTE static_file_info[0x1000]; + if (IthGetFileInfo(L"*01", static_file_info)) + if (*(DWORD*)static_file_info == 0) { + static WCHAR static_search_name[MAX_PATH]; + LPWSTR name=(LPWSTR)(static_file_info+0x5E); + int len = wcslen(name); + name[len - 2] = L'*'; + name[len - 1] = 0; + wcscpy(static_search_name, name); + IthGetFileInfo(static_search_name, static_file_info); + union { + FILE_BOTH_DIR_INFORMATION *both_info; + DWORD addr; + }; + both_info = (FILE_BOTH_DIR_INFORMATION *)static_file_info; + //BYTE* ptr=static_file_info; + len = 0; + while (both_info->NextEntryOffset) { + addr += both_info->NextEntryOffset; + len++; + } + if (len > 3) { + InsertAbelHook(); + return true; + } + } + + return false; +} + +// jichi 8/17/2014 +// Put the patterns that might break other games at last +bool DetermineEngineAtLast() +{ + if (IthCheckFile(L"MovieTexture.dll") && (InsertPensilHook() || Insert2RMHook())) // MovieTexture.dll also exists in 2RM games such as ٽ2??, which is checked first + return true; + if (IthFindFile(L"system") && IthFindFile(L"system.dat")) { // jichi 7/31/2015 + InsertAbelHook(); + return true; + } + if (IthFindFile(L"data\\*.cpk")) { // jichi 12/2/2014 + Insert5pbHook(); + return true; + } + // jichi 7/6/2014: named as ScenarioPlayer since resource string could be: scenario player program for xxx + // Do this at last as it is common + if (IthFindFile(L"*.iar") && IthFindFile(L"*.sec5")) { // jichi 4/18/2014: Other game engine could also have *.iar such as Ryokucha + InsertScenarioPlayerHook(); + return true; + } + //if (IthCheckFile(L"arc0.dat") && IthCheckFile(L"script.dat") // jichi 11/14/2014: too common + if (Util::SearchResourceString(L"HorkEye")) { // appear in copyright: Copyright (C) HorkEye, http://horkeye.com + InsertHorkEyeHook(); + return true; + } + if (IthCheckFile(L"comnArc.arc") // jichi 8/17/2014: this file might exist in multiple files + && InsertNexton1Hook()) // old nexton game + return true; + if (IthCheckFile(L"arc.dat") // jichi 9/27/2014: too common + && InsertApricoTHook()) + return true; + if (IthFindFile(L"*.pak") // jichi 12/25/2014: too common + && InsertLeafHook()) + return true; + // jichi 10/31/2014 + // File description: Adobe Flash Player 10.2r153 + // Product name: Shockwave Flash + // Original filename: SAFlashPlayer.exe + // Legal trademarks: Adobe Flash Player + // No idea why, this must appear at last or it will crash + if (Util::SearchResourceString(L"Adobe Flash Player 10")) { + InsertAdobeFlash10Hook(); // only v10 might be supported. Otherwise, fallback to Lstr hooks + return true; + } + if (IthFindFile(L"dat\\*.arc")) { // jichi 2/6/2015 + InsertFocasLensHook(); // Touhou + return true; + } + + // jichi 8/23/2015: Tamamo + if (IthCheckFile(L"data.pck") && IthCheckFile(L"image.pck") && IthCheckFile(L"script.pck")) { + //if (IthCheckFile(L"QtGui.dll")) + InsertTamamoHook(); + return true; + } + + return false; +} + +// jichi 6/1/2014 +bool DetermineEngineGeneric() +{ + bool ret = false; + + if (IthCheckFile(L"AlterEgo.exe")) { + ConsoleOutput("vnreng: AlterEgo, INSERT WideChar hooks"); + ret = true; + } else if (IthFindFile(L"data\\Sky\\*")) { + ConsoleOutput("vnreng: TEATIME, INSERT WideChar hooks"); + ret = true; + } + //} else if (IthFindFile(L"image\\*.po2") || IthFindFile(L"image\\*.jo2")) { + // ConsoleOutput("vnreng: HarukaKanata, INSERT WideChar hooks"); // Ϫ몫ʪ + // ret = true; + //} + if (ret) + PcHooks::hookWcharFunctions(); + return ret; +} + +bool DetermineNoEngine() +{ + //if (IthFindFile(L"*\\Managed\\UnityEngine.dll")) { // jichi 12/3/2013: Unity (BALDRSKY ZERO) + // ConsoleOutput("vnreng: IGNORE Unity"); + // return true; + //} + //if (IthCheckFile(L"bsz_Data\\Managed\\UnityEngine.dll") || IthCheckFile(L"bsz2_Data\\Managed\\UnityEngine.dll")) { + // ConsoleOutput("vnreng: IGNORE Unity"); + // return true; + //} + + // jichi 6/7/2015: RPGMaker v3 + if (IthFindFile(L"*.rgss3a")) { + ConsoleOutput("vnreng: IGNORE RPGMaker RGSS3"); + return true; + } + + // jichi 11/22/2015: NECRO ?? + if (IthFindFile(L"*.npk")) { + ConsoleOutput("vnreng: IGNORE new Nitroplus"); + return true; + } + + // 8/29/2015 jichi: minori, text in GetGlyphOutlineA + if (IthFindFile(L"*.paz")) { + ConsoleOutput("vnreng: IGNORE minori"); + return true; + } + + // 7/28/2015 jichi: Favorite games + if (IthFindFile(L"*.hcb")) { + ConsoleOutput("vnreng: IGNORE FVP"); + return true; + } + + // jichi 2/14/2015: Guilty+ ңɣΡӣţ (PK) + if (IthCheckFile(L"rio.ini") || IthFindFile(L"*.war")) { + ConsoleOutput("vnreng: IGNORE unknown ShinaRio"); + return true; + } + + if (IthCheckFile(L"AdvHD.exe") || IthCheckFile(L"AdvHD.dll")) { + ConsoleOutput("vnreng: IGNORE Adv Player HD"); // supposed to be WillPlus + return true; + } + + if (IthCheckFile(L"ScrPlayer.exe")) { + ConsoleOutput("vnreng: IGNORE ScrPlayer"); + return true; + } + + if (IthCheckFile(L"nnnConfig2.exe")) { + ConsoleOutput("vnreng: IGNORE Nya NNNConfig"); + return true; + } + + // jichi 4/30/2015: Skip games made from 骹, such as ȪΫͫȫ + // It has garbage from lstrlenW. Correct text is supposed to be in TabbedTextOutA. + if (IthCheckFile(L"data_cg.dpm")) { + ConsoleOutput("vnreng: IGNORE DPM data_cg.dpm"); + return true; + } + + //if (IthCheckFile(L"AGERC.DLL")) { // jichi 3/17/2014: Eushully, AGE.EXE + // ConsoleOutput("vnreng: IGNORE Eushully"); + // return true; + //} + + if (IthCheckFile(L"game_sys.exe")) { + ConsoleOutput("vnreng: IGNORE Atelier Kaguya BY/TH"); + return true; + } + + if (IthFindFile(L"*.bsa")) { + ConsoleOutput("vnreng: IGNORE Bishop"); + return true; + } + + // jichi 3/19/2014: Escude game + // Example: bgm.bin gfx.bin maou.bin script.bin snd.bin voc.bin + if (IthCheckFile(L"gfx.bin") && IthCheckFile(L"snd.bin") && IthCheckFile(L"voc.bin")) { + ConsoleOutput("vnreng: IGNORE Escude"); + return true; + } + + // jichi 2/18/2015: Ignore if there is Nitro+ copyright + if (Util::SearchResourceString(L"Nitro+")) { + ConsoleOutput("vnreng: IGNORE unknown Nitro+"); + return true; + } + + // jichi 12/28/2014: "Chartreux Inc." in Copyright. + // Sublimary brands include Rosebleu, MORE, etc. + // GetGlyphOutlineA already works. + if (Util::SearchResourceString(L"Chartreux")) { + ConsoleOutput("vnreng: IGNORE Chartreux"); + return true; + } + + if (IthCheckFile(L"MovieTexture.dll")) { + ConsoleOutput("vnreng: IGNORE MovieTexture"); + return true; + } + + if (wcsstr(process_name_, L"lcsebody") || !wcsncmp(process_name_, L"lcsebo~", 7) || IthFindFile(L"lcsebody*")) { // jichi 3/19/2014: LC-ScriptEngine, GetGlyphOutlineA + ConsoleOutput("vnreng: IGNORE lcsebody"); + return true; + } + + wchar_t str[MAX_PATH]; + DWORD i; + for (i = 0; process_name_[i]; i++) { + str[i] = process_name_[i]; + if (process_name_[i] == L'.') + break; + } + *(DWORD *)(str + i + 1) = 0x630068; //.hcb + *(DWORD *)(str + i + 3) = 0x62; + if (IthCheckFile(str)) { + ConsoleOutput("vnreng: IGNORE FVP"); // jichi 10/3/2013: such like ȫꫨ + return true; + } + return false; +} + +// 12/13/2013: Declare it in a way compatible to EXCEPTION_PROCEDURE +EXCEPTION_DISPOSITION ExceptHandler(PEXCEPTION_RECORD ExceptionRecord, LPVOID, PCONTEXT, LPVOID) +{ + if (ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) { + module_limit_ = ExceptionRecord->ExceptionInformation[1]; + //OutputDWORD(module_limit_); + __asm + { + mov eax,fs:[0x30] // jichi 12/13/2013: get PEB + mov eax,[eax+0xc] + mov eax,[eax+0xc] + mov ecx,module_limit_ + sub ecx,module_base_ + mov [eax+0x20],ecx + } + } + //ContextRecord->Esp = recv_esp; + //ContextRecord->Eip = recv_eip; + //return ExceptionContinueExecution; // jichi 3/11/2014: this will still crash. Not sure why ITH use this. Change to ExceptionContinueSearch + return ExceptionContinueSearch; // an unwind is in progress, +} + +// jichi 9/14/2013: Certain ITH functions like FindEntryAligned might raise exception without admin priv +// Return if succeeded. +bool UnsafeDetermineEngineType() +{ + return DeterminePCEngine() + || DetermineEngineByFile1() + || DetermineEngineByFile2() + || DetermineEngineByFile3() + || DetermineEngineByFile4() + || DetermineEngineByProcessName() + || DetermineEngineOther() + || DetermineEngineAtLast() + || DetermineEngineGeneric() + || DetermineNoEngine() + ; +} + +// jichi 10/21/2014: Return whether found the game engine +bool DetermineEngineType() +{ + // jichi 9/27/2013: disable game engine for debugging use +#ifdef ITH_DISABLE_ENGINE + PcHooks::hookLstrFunctions(); + PcHooks::hookCharNextFunctions(); + return false; +#else + bool found = false; +#ifdef ITH_HAS_SEH + __try { found = UnsafeDetermineEngineType(); } + __except(ExceptHandler((GetExceptionInformation())->ExceptionRecord, 0, 0, 0)) {} +#else // use my own SEH + seh_with_eh(ExceptHandler, + found = UnsafeDetermineEngineType()); +#endif // ITH_HAS_SEH + if (::GDIPlusHooksEnabled()) + PcHooks::hookGDIPlusFunctions(); + if (!found) { // jichi 10/2/2013: Only enable it if no game engine is detected + PcHooks::hookLstrFunctions(); + PcHooks::hookCharNextFunctions(); + } else + ConsoleOutput("vnreng: found game engine, IGNORE non gui hooks"); + return found; +#endif // ITH_DISABLE_ENGINE +} + +// __asm +// { +// mov eax,seh_recover +// mov recv_eip,eax +// push ExceptHandler +// push fs:[0] +// mov fs:[0],esp +// pushad +// mov recv_esp,esp +// } +// DetermineEngineType(); +// status++; +// __asm +// { +//seh_recover: +// popad +// mov eax,[esp] +// mov fs:[0],eax +// add esp,8 +// } +// if (status == 0) +// ConsoleOutput("Fail to identify engine type."); +// else +// ConsoleOutput("Initialized successfully."); +//} +// + +HANDLE hijackThread; +void hijackThreadProc(LPVOID lpThreadParameter) +{ + CC_UNUSED(lpThreadParameter); + + //static bool done = false; + //if (done) + // return; + //done = true; + + // jichi 12/18/2013: Though FillRange could raise, it should never raise for he current process + // So, SEH is not used here. + Util::GetProcessName(process_name_); // Initialize shared process name + Util::GetProcessPath(process_path_); // Initialize shared process path + + FillRange(process_name_, &module_base_, &module_limit_); + DetermineEngineType(); +} + +}} // namespace Engine unnamed + +// - API - + +DWORD Engine::InsertDynamicHook(LPVOID addr, DWORD frame, DWORD stack) +{ return trigger_fun_ ? !trigger_fun_(addr, frame, stack) : 0; } + +void Engine::hijack() +{ + if (!hijackThread) { + ConsoleOutput("vnreng: hijack process"); + hijackThread = IthCreateThread(hijackThreadProc, 0); + } +} + +void Engine::terminate() +{ + if (hijackThread) { + const LONGLONG timeout = -50000000; // in nanoseconds = 5 seconds + NtWaitForSingleObject(hijackThread, 0, (PLARGE_INTEGER)&timeout); + NtClose(hijackThread); + hijackThread = 0; + } +} + +// EOF + +/* +extern "C" { + // http://gmogre3d.googlecode.com/svn-history/r815/trunk/OgreMain/src/WIN32/OgreMinGWSupport.cpp + // http://forum.osdev.org/viewtopic.php?f=8&t=22352 + //#pragma data_seg() + //#pragma comment(linker, "/merge:.CRT=.data") // works fine in visual c++ 6 + //#pragma data_seg() + //#pragma comment(linker, "/merge:.CRT=.rdata") + // MSVC libs use _chkstk for stack-probing. MinGW equivalent is _alloca. + //void _alloca(); + //void _chkstk() { _alloca(); } + + // MSVC uses security cookies to prevent some buffer overflow attacks. + // provide dummy implementations. + //void _fastcall __security_check_cookie(intptr_t i) {} + void __declspec(naked) __fastcall __security_check_cookie(UINT_PTR cookie) {} +} +*/ diff --git a/vnr/vnrhook/src/engine/match.h b/vnr/vnrhook/src/engine/match.h new file mode 100644 index 0000000..bbe0fda --- /dev/null +++ b/vnr/vnrhook/src/engine/match.h @@ -0,0 +1,23 @@ +#pragma once + +// engine/match.h +// 8/23/2013 jichi +// TODO: Clean up the interface to match game engines. +// Split the engine match logic out of hooks. +// Modify the game hook to allow replace functions for arbitary purpose +// instead of just extracting text. + +#include + +namespace Engine { + +// jichi 10/21/2014: Return whether found the engine +void hijack(); +void terminate(); + +// jichi 10/21/2014: Return 0 if failed +DWORD InsertDynamicHook(LPVOID addr, DWORD frame, DWORD stack); + +} // namespace Engine + +// EOF diff --git a/vnr/vnrhook/src/engine/mono/funcinfo.h b/vnr/vnrhook/src/engine/mono/funcinfo.h new file mode 100644 index 0000000..88d9535 --- /dev/null +++ b/vnr/vnrhook/src/engine/mono/funcinfo.h @@ -0,0 +1,55 @@ +#pragma once + +// mono/funcinfo.h +// 12/26/2014 +// https://github.com/mono/mono/blob/master/mono/metadata/object.h +// http://api.xamarin.com/index.aspx?link=xhtml%3Adeploy%2Fmono-api-string.html + +//#include "ith/import/mono/types.h" + +// MonoString* mono_string_new (MonoDomain *domain, +// const char *text); +// MonoString* mono_string_new_len (MonoDomain *domain, +// const char *text, +// guint length); +// MonoString* mono_string_new_size (MonoDomain *domain, +// gint32 len); +// MonoString* mono_string_new_utf16 (MonoDomain *domain, +// const guint16 *text, +// gint32 len); +// MonoString* mono_string_from_utf16 (gunichar2 *data); +// mono_unichar2* mono_string_to_utf16 (MonoString *s); +// char* mono_string_to_utf8 (MonoString *s); +// gboolean mono_string_equal (MonoString *s1, +// MonoString *s2); +// guint mono_string_hash (MonoString *s); +// MonoString* mono_string_intern (MonoString *str); +// MonoString* mono_string_is_interned (MonoString *o); +// MonoString* mono_string_new_wrapper (const char *text); +// gunichar2* mono_string_chars (MonoString *s); +// int mono_string_length (MonoString *s); +// gunichar2* mono_unicode_from_external (const gchar *in, gsize *bytes); +// gchar* mono_unicode_to_external (const gunichar2 *uni); +// gchar* mono_utf8_from_external (const gchar *in); + +struct MonoFunction { + const char *functionName; + size_t textIndex; // argument index, starting from 0 + size_t lengthIndex; // argument index, start from 0 + unsigned long hookType; // HookParam type + void *text_fun; // HookParam::text_fun_t +}; + +#define MONO_FUNCTIONS_INITIALIZER \ + { "mono_string_to_utf8", 0, 0, USING_UNICODE|NO_CONTEXT, SpecialHookMonoString } \ + , { "mono_string_to_utf8_checked", 0, 0, USING_UNICODE|NO_CONTEXT, SpecialHookMonoString } \ + , { "mono_string_to_utf16", 0, 0, USING_UNICODE|NO_CONTEXT, SpecialHookMonoString } \ + , { "mono_utf8_from_external", 1, 0, USING_STRING|USING_UTF8, nullptr } \ + , { "mono_string_from_utf16", 1, 0, USING_UNICODE, nullptr } \ + , { "mono_string_new_utf16", 2, 3, USING_UNICODE, nullptr } \ + , { "mono_unicode_from_external", 1, 0, USING_UNICODE, nullptr } \ + , { "mono_unicode_to_external", 1, 0, USING_UNICODE, nullptr } + //, { "mono_string_new", 2, 0, USING_STRING|USING_UTF8, nullptr } + //, { "mono_string_new_wrapper", 1, 0, USING_STRING|USING_UTF8, nullptr } + +// EOF diff --git a/vnr/vnrhook/src/engine/mono/types.h b/vnr/vnrhook/src/engine/mono/types.h new file mode 100644 index 0000000..7f7d9b7 --- /dev/null +++ b/vnr/vnrhook/src/engine/mono/types.h @@ -0,0 +1,41 @@ +#pragma once + +// mono/types.h +// 12/26/2014 +// https://github.com/mono/mono/blob/master/mono/metadata/object.h +// http://api.xamarin.com/index.aspx?link=xhtml%3Adeploy%2Fmono-api-string.html + +#include + +// mono/io-layer/uglify.h +typedef int8_t gint8; +typedef int32_t gint32; +typedef wchar_t gunichar2; // either char or wchar_t, depending on how mono is compiled + +typedef gint8 mono_byte; +typedef gunichar2 mono_unichar2; + +// mono/metadata/object.h + +typedef mono_byte MonoBoolean; + +struct MonoArray; +struct MonoDelegate; +struct MonoException; +struct MonoString; +struct MonoThreadsSync; +struct MonoThread; +struct MonoVTable; + +struct MonoObject { + MonoVTable *vtable; + MonoThreadsSync *synchronisation; +}; + +struct MonoString { + MonoObject object; + gint32 length; + gunichar2 chars[0]; +}; + +// EOF diff --git a/vnr/vnrhook/src/engine/pchooks.cc b/vnr/vnrhook/src/engine/pchooks.cc new file mode 100644 index 0000000..4c6442b --- /dev/null +++ b/vnr/vnrhook/src/engine/pchooks.cc @@ -0,0 +1,278 @@ +// pchooks.cc +// 8/1/2014 jichi + +#include "src/engine/pchooks.h" +#include "src/main.h" +//#include + +#define DEBUG "vnrcli" +#define DPRINT(cstr) ConsoleOutput(DEBUG ":" __FUNCTION__ ":" cstr) // defined in vnrcli + +// 8/1/2014 jichi: Split is not used. +// Although split is specified, USING_SPLIT is not assigned. + +// Use LPASTE to convert to wchar_t +// http://bytes.com/topic/c/answers/135834-defining-wide-character-strings-macros +//#define LPASTE(s) L##s +//#define L(s) LPASTE(s) +#define NEW_HOOK_AT(_addr, _fun, _data, _data_ind, _split_off, _split_ind, _type, _len_off) \ + { \ + HookParam hp = {}; \ + hp.address = _addr; \ + hp.offset = _data; \ + hp.index = _data_ind; \ + hp.split = _split_off; \ + hp.split_index = _split_ind; \ + hp.type = _type; \ + hp.length_offset = _len_off; \ + NewHook(hp, #_fun); \ + } + +// Static hook +#define NEW_HOOK(_fun, _data, _data_ind, _split_off, _split_ind, _type, _len_off) \ + NEW_HOOK_AT((DWORD)_fun, _fun, _data, _data_ind, _split_off, _split_ind, _type, _len_off) \ + +#define NEW_MODULE_HOOK(_module, _fun, _data, _data_ind, _split_off, _split_ind, _type, _len_off) \ + { \ + if (DWORD addr = (DWORD)::GetProcAddress(_module, #_fun)) \ + NEW_HOOK_AT(addr, _fun, _data, _data_ind, _split_off, _split_ind, _type, _len_off) \ + } + +// jichi 7/17/2014: Renamed from InitDefaultHook +void PcHooks::hookGDIFunctions() +{ + DPRINT("enter"); + // int TextHook::InitHook(LPVOID addr, DWORD data, DWORD data_ind, DWORD split_off, DWORD split_ind, WORD type, DWORD len_off) + // + // jichi 9/8/2013: Guessed meaning + // - data(off): 4 * the n-th (base 1) parameter representing the data of the string + // - len_off: + // - the n-th (base 1) parameter representing the length of the string + // - or 1 if is char + // - or 0 if detect on run time + // - type: USING_STRING if len_off != 1 else BIG_ENDIAN or USING_UNICODE + // + // Examples: + // int WINAPI lstrlenA(LPCSTR lpString) + // - data: 4 * 1 = 4, as lpString is the first + // - len_off: 0, as no parameter representing string length + // - type: BIG_ENDIAN, since len_off == 1 + // BOOL GetTextExtentPoint32(HDC hdc, LPCTSTR lpString, int c, LPSIZE lpSize); + // - data: 4 * 2 = 0x8, as lpString is the second + // - len_off: 3, as nCount is the 3rd parameter + // - type: USING_STRING, since len_off != 1 + // + // Note: All functions does not have NO_CONTEXT attribute and will be filtered. + + enum stack { + s_retaddr = 0 + , s_arg1 = 4 * 1 // 0x4 + , s_arg2 = 4 * 2 // 0x8 + , s_arg3 = 4 * 3 // 0xc + , s_arg4 = 4 * 4 // 0x10 + , s_arg5 = 4 * 5 // 0x14 + , s_arg6 = 4 * 6 // 0x18 + }; + +//#define _(Name, ...) \ +// hookman[HF_##Name].InitHook(Name, __VA_ARGS__); \ +// hookman[HF_##Name].SetHookName(names[HF_##Name]); + + // Always use s_arg1 = hDC as split_off + // 7/26/2014 jichi: Why there is no USING_SPLIT type? + + // gdi32.dll + NEW_HOOK(GetTextExtentPoint32A, s_arg2, 0,s_arg1,0, USING_STRING, 3) // BOOL GetTextExtentPoint32(HDC hdc, LPCTSTR lpString, int c, LPSIZE lpSize); + NEW_HOOK(GetTextExtentExPointA, s_arg2, 0,s_arg1,0, USING_STRING, 3) // BOOL GetTextExtentExPoint(HDC hdc, LPCTSTR lpszStr, int cchString, int nMaxExtent, LPINT lpnFit, LPINT alpDx, LPSIZE lpSize); + NEW_HOOK(GetTabbedTextExtentA, s_arg2, 0,s_arg1,0, USING_STRING, 3) // DWORD GetTabbedTextExtent(HDC hDC, LPCTSTR lpString, int nCount, int nTabPositions, const LPINT lpnTabStopPositions); + NEW_HOOK(GetCharacterPlacementA, s_arg2, 0,s_arg1,0, USING_STRING, 3) // DWORD GetCharacterPlacement(HDC hdc, LPCTSTR lpString, int nCount, int nMaxExtent, LPGCP_RESULTS lpResults, DWORD dwFlags); + NEW_HOOK(GetGlyphIndicesA, s_arg2, 0,s_arg1,0, USING_STRING, 3) // DWORD GetGlyphIndices( HDC hdc, LPCTSTR lpstr, int c, LPWORD pgi, DWORD fl); + NEW_HOOK(GetGlyphOutlineA, s_arg2, 0,s_arg1,0, BIG_ENDIAN, 1) // DWORD GetGlyphOutline(HDC hdc, UINT uChar, UINT uFormat, LPGLYPHMETRICS lpgm, DWORD cbBuffer, LPVOID lpvBuffer, const MAT2 *lpmat2); + NEW_HOOK(ExtTextOutA, s_arg6, 0,s_arg1,0, USING_STRING, 7) // BOOL ExtTextOut(HDC hdc, int X, int Y, UINT fuOptions, const RECT *lprc, LPCTSTR lpString, UINT cbCount, const INT *lpDx); + NEW_HOOK(TextOutA, s_arg4, 0,s_arg1,0, USING_STRING, 5) // BOOL TextOut(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cchString); + NEW_HOOK(TabbedTextOutA, s_arg4, 0,s_arg1,0, USING_STRING, 5) // LONG TabbedTextOut(HDC hDC, int X, int Y, LPCTSTR lpString, int nCount, int nTabPositions, const LPINT lpnTabStopPositions, int nTabOrigin); + NEW_HOOK(GetCharABCWidthsA, s_arg2, 0,s_arg1,0, BIG_ENDIAN, 1) // BOOL GetCharABCWidths(HDC hdc, UINT uFirstChar, UINT uLastChar, LPABC lpabc); + NEW_HOOK(GetCharABCWidthsFloatA, s_arg2, 0,s_arg1,0, BIG_ENDIAN, 1) // BOOL GetCharABCWidthsFloat(HDC hdc, UINT iFirstChar, UINT iLastChar, LPABCFLOAT lpABCF); + NEW_HOOK(GetCharWidth32A, s_arg2, 0,s_arg1,0, BIG_ENDIAN, 1) // BOOL GetCharWidth32(HDC hdc, UINT iFirstChar, UINT iLastChar, LPINT lpBuffer); + NEW_HOOK(GetCharWidthFloatA, s_arg2, 0,s_arg1,0, BIG_ENDIAN, 1) // BOOL GetCharWidthFloat(HDC hdc, UINT iFirstChar, UINT iLastChar, PFLOAT pxBuffer); + + NEW_HOOK(GetTextExtentPoint32W, s_arg2, 0,s_arg1,0, USING_UNICODE|USING_STRING, 3) + NEW_HOOK(GetTextExtentExPointW, s_arg2, 0,s_arg1,0, USING_UNICODE|USING_STRING, 3) + NEW_HOOK(GetTabbedTextExtentW, s_arg2, 0,s_arg1,0, USING_UNICODE|USING_STRING, 3) + NEW_HOOK(GetCharacterPlacementW, s_arg2, 0,s_arg1,0, USING_UNICODE|USING_STRING, 3) + NEW_HOOK(GetGlyphIndicesW, s_arg2, 0,s_arg1,0, USING_UNICODE|USING_STRING, 3) + NEW_HOOK(GetGlyphOutlineW, s_arg2, 0,s_arg1,0, USING_UNICODE, 1) + NEW_HOOK(ExtTextOutW, s_arg6, 0,s_arg1,0, USING_UNICODE|USING_STRING, 7) + NEW_HOOK(TextOutW, s_arg4, 0,s_arg1,0, USING_UNICODE|USING_STRING, 5) + NEW_HOOK(TabbedTextOutW, s_arg4, 0,s_arg1,0, USING_UNICODE|USING_STRING, 5) + NEW_HOOK(GetCharABCWidthsW, s_arg2, 0,s_arg1,0, USING_UNICODE, 1) + NEW_HOOK(GetCharABCWidthsFloatW, s_arg2, 0,s_arg1,0, USING_UNICODE, 1) + NEW_HOOK(GetCharWidth32A, s_arg2, 0,s_arg1,0, USING_UNICODE, 1) + NEW_HOOK(GetCharWidthFloatA, s_arg2, 0,s_arg1,0, USING_UNICODE, 1) + + // user32.dll + NEW_HOOK(DrawTextA, s_arg2, 0,s_arg1,0, USING_STRING, 3) // int DrawText(HDC hDC, LPCTSTR lpchText, int nCount, LPRECT lpRect, UINT uFormat); + NEW_HOOK(DrawTextExA, s_arg2, 0,s_arg1,0, USING_STRING, 3) // int DrawTextEx(HDC hdc, LPTSTR lpchText,int cchText, LPRECT lprc, UINT dwDTFormat, LPDRAWTEXTPARAMS lpDTParams); + NEW_HOOK(DrawTextW, s_arg2, 0,s_arg1,0, USING_UNICODE|USING_STRING, 3) + NEW_HOOK(DrawTextExW, s_arg2, 0,s_arg1,0, USING_UNICODE|USING_STRING, 3) + + DPRINT("leave"); +} + +// jichi 6/18/2015: GDI+ functions +void PcHooks::hookGDIPlusFunctions() +{ + HMODULE hModule = ::GetModuleHandleA("gdiplus.dll"); + if (!hModule) { + DPRINT("not loaded"); + return; + } + + DPRINT("enter"); + enum stack { + s_retaddr = 0 + , s_arg1 = 4 * 1 // 0x4 + , s_arg2 = 4 * 2 // 0x8 + , s_arg3 = 4 * 3 // 0xc + , s_arg4 = 4 * 4 // 0x10 + , s_arg5 = 4 * 5 // 0x14 + , s_arg6 = 4 * 6 // 0x18 + }; + + // gdiplus.dll + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms534053%28v=vs.85%29.aspx + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms534052%28v=vs.85%29.aspx + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms534039%28v=vs.85%29.aspx + // Use arg1 pionter to GpGraphics as split + //using namespace Gdiplus::DllExports; + // Use arg5 style as split + NEW_MODULE_HOOK(hModule, GdipAddPathString, s_arg2, 0,s_arg5,0, USING_UNICODE|USING_STRING, 3) // GpStatus WINGDIPAPI GdipAddPathString(GpPath *path, GDIPCONST WCHAR *string, INT length, GDIPCONST GpFontFamily *family, INT style, REAL emSize, GDIPCONST RectF *layoutRect, GDIPCONST GpStringFormat *format) + NEW_MODULE_HOOK(hModule, GdipAddPathStringl, s_arg2, 0,s_arg5,0, USING_UNICODE|USING_STRING, 3) // GpStatus WINGDIPAPI GdipAddPathStringI(GpPath *path, GDIPCONST WCHAR *string, INT length, GDIPCONST GpFontFamily *family, INT style, REAL emSize, GDIPCONST Rect *layoutRect, GDIPCONST GpStringFormat *format) + //NEW_MODULE_HOOK(hModule, GdipMeasureCharacterRanges, s_arg2, 0,s_arg1,0, USING_UNICODE|USING_STRING, 3) // GpStatus WINGDIPAPI GdipMeasureCharacterRanges(GpGraphics *graphics, GDIPCONST WCHAR *string, INT length, GDIPCONST GpFont *font, GDIPCONST RectF &layoutRect, GDIPCONST GpStringFormat *stringFormat, INT regionCount, GpRegion **regions) + NEW_MODULE_HOOK(hModule, GdipDrawString, s_arg2, 0,s_arg1,0, USING_UNICODE|USING_STRING, 3) // GpStatus WINGDIPAPI GdipDrawString(GpGraphics *graphics, GDIPCONST WCHAR *string, INT length, GDIPCONST GpFont *font, GDIPCONST RectF *layoutRect, GDIPCONST GpStringFormat *stringFormat, GDIPCONST GpBrush *brush); + NEW_MODULE_HOOK(hModule, GdipMeasureString, s_arg2, 0,s_arg1,0, USING_UNICODE|USING_STRING, 3) // GpStatus WINGDIPAPI GdipMeasureString(GpGraphics *graphics, GDIPCONST WCHAR *string, INT length, GDIPCONST GpFont *font, GDIPCONST RectF *layoutRect, GDIPCONST GpStringFormat *stringFormat, RectF *boundingBox, INT *codepointsFitted, INT *linesFilled ) + + DPRINT("leave"); +} + +// jichi 10/2/2013 +// Note: All functions does not have NO_CONTEXT attribute and will be filtered. +void PcHooks::hookLstrFunctions() +{ + DPRINT("enter"); + // int TextHook::InitHook(LPVOID addr, DWORD data, DWORD data_ind, DWORD split_off, DWORD split_ind, WORD type, DWORD len_off) + + enum stack { + s_retaddr = 0 + , s_arg1 = 4 * 1 // 0x4 + //, s_arg2 = 4 * 2 // 0x8 + //, s_arg3 = 4 * 3 // 0xc + //, s_arg4 = 4 * 4 // 0x10 + //, s_arg5 = 4 * 5 // 0x14 + //, s_arg6 = 4 * 6 // 0x18 + }; + + // http://msdn.microsoft.com/en-us/library/78zh94ax.aspx + // int WINAPI lstrlen(LPCTSTR lpString); + // Lstr functions usually extracts rubbish, and might crash certain games like 「Magical Marriage Lunatics!!」 + // Needed by Gift + // Use arg1 address for both split and data + NEW_HOOK(lstrlenA, s_arg1, 0,s_arg1,0, USING_STRING, 0) // 9/8/2013 jichi: int WINAPI lstrlen(LPCTSTR lpString); + NEW_HOOK(lstrlenW, s_arg1, 0,s_arg1,0, USING_UNICODE|USING_STRING, 0) // 9/8/2013 jichi: add lstrlen + + // size_t strlen(const char *str); + // size_t strlen_l(const char *str, _locale_t locale); + // size_t wcslen(const wchar_t *str); + // size_t wcslen_l(const wchar_t *str, _locale_t locale); + // size_t _mbslen(const unsigned char *str); + // size_t _mbslen_l(const unsigned char *str, _locale_t locale); + // size_t _mbstrlen(const char *str); + // size_t _mbstrlen_l(const char *str, _locale_t locale); + + // http://msdn.microsoft.com/en-us/library/ex0hs2ad.aspx + // Needed by 娘姉妹 + // + // + // char *_strinc(const char *current, _locale_t locale); + // wchar_t *_wcsinc(const wchar_t *current, _locale_t locale); + // + // unsigned char *_mbsinc(const unsigned char *current); + // unsigned char *_mbsinc_l(const unsigned char *current, _locale_t locale); + //_(L"_strinc", _strinc, 4, 0,4,0, USING_STRING, 0) // 12/13/2013 jichi + //_(L"_wcsinc", _wcsinc, 4, 0,4,0, USING_UNICODE|USING_STRING, 0) + DPRINT("leave"); +} + +void PcHooks::hookWcharFunctions() +{ + DPRINT("enter"); + // 12/1/2013 jichi: + // AlterEgo + // http://tieba.baidu.com/p/2736475133 + // http://www.hongfire.com/forum/showthread.php/36807-AGTH-text-extraction-tool-for-games-translation/page355 + // + // MultiByteToWideChar + // http://blgames.proboards.com/thread/265 + // + // WideCharToMultiByte + // http://www.hongfire.com/forum/showthread.php/36807-AGTH-text-extraction-tool-for-games-translation/page156 + // + // int MultiByteToWideChar( + // _In_ UINT CodePage, + // _In_ DWORD dwFlags, + // _In_ LPCSTR lpMultiByteStr, // hook here + // _In_ int cbMultiByte, + // _Out_opt_ LPWSTR lpWideCharStr, + // _In_ int cchWideChar + // ); + // int WideCharToMultiByte( + // _In_ UINT CodePage, + // _In_ DWORD dwFlags, + // _In_ LPCWSTR lpWideCharStr, + // _In_ int cchWideChar, + // _Out_opt_ LPSTR lpMultiByteStr, + // _In_ int cbMultiByte, + // _In_opt_ LPCSTR lpDefaultChar, + // _Out_opt_ LPBOOL lpUsedDefaultChar + // ); + + enum stack { + s_retaddr = 0 + , s_arg1 = 4 * 1 // 0x4 + //, s_arg2 = 4 * 2 // 0x8 + , s_arg3 = 4 * 3 // 0xc + //, s_arg4 = 4 * 4 // 0x10 + //, s_arg5 = 4 * 5 // 0x14 + //, s_arg6 = 4 * 6 // 0x18 + }; + + // 3/17/2014 jichi: Temporarily disabled + // http://sakuradite.com/topic/159 + NEW_HOOK(MultiByteToWideChar, s_arg3, 0,4,0, USING_STRING, 4) + NEW_HOOK(WideCharToMultiByte, s_arg3, 0,4,0, USING_UNICODE|USING_STRING, 4) + DPRINT("leave"); +} + +void PcHooks::hookCharNextFunctions() +{ + enum stack { + s_retaddr = 0 + , s_arg1 = 4 * 1 // 0x4 + , s_arg2 = 4 * 2 // 0x8 + //, s_arg3 = 4 * 3 // 0xc + //, s_arg4 = 4 * 4 // 0x10 + //, s_arg5 = 4 * 5 // 0x14 + //, s_arg6 = 4 * 6 // 0x18 + }; + + DPRINT("enter"); + NEW_HOOK(CharNextA, s_arg1, 0,0,0, USING_STRING|DATA_INDIRECT, 1) // LPTSTR WINAPI CharNext(_In_ LPCTSTR lpsz); + NEW_HOOK(CharNextW, s_arg1, 0,0,0, USING_UNICODE|DATA_INDIRECT, 1) + NEW_HOOK(CharPrevA, s_arg1, 0,0,0, USING_STRING|DATA_INDIRECT, 1) // LPTSTR WINAPI CharPrev(_In_ LPCTSTR lpszStart, _In_ LPCTSTR lpszCurrent); + NEW_HOOK(CharPrevW, s_arg1, 0,0,0, USING_UNICODE|DATA_INDIRECT, 1) + //NEW_HOOK(CharNextExA, s_arg2, 0,0,0, USING_STRING|DATA_INDIRECT, 1) // LPSTR WINAPI CharNextExA(_In_ WORD CodePage, _In_ LPCSTR lpCurrentChar, _In_ DWORD dwFlags); + //NEW_HOOK(CharNextExW, s_arg2, 0,0,0, USING_UNICODE|DATA_INDIRECT, 1) + DPRINT("leave"); +} + +// EOF diff --git a/vnr/vnrhook/src/engine/pchooks.h b/vnr/vnrhook/src/engine/pchooks.h new file mode 100644 index 0000000..7ce99b5 --- /dev/null +++ b/vnr/vnrhook/src/engine/pchooks.h @@ -0,0 +1,16 @@ +#pragma once + +// pchooks.h +// 8/1/2014 jichi + +namespace PcHooks { + +void hookGDIFunctions(); +void hookGDIPlusFunctions(); +void hookLstrFunctions(); +void hookWcharFunctions(); +void hookCharNextFunctions(); + +} // namespace PcHooks + +// EOF diff --git a/vnr/vnrhook/src/engine/ppsspp/funcinfo.h b/vnr/vnrhook/src/engine/ppsspp/funcinfo.h new file mode 100644 index 0000000..2ad2e0d --- /dev/null +++ b/vnr/vnrhook/src/engine/ppsspp/funcinfo.h @@ -0,0 +1,105 @@ +#pragma once + +// ppsspp/funcinfo.h +// 12/26/2014 +// See: https://github.com/hrydgard/ppsspp + +// Core/HLE (High Level Emulator) +// - sceCcc +// #void sceCccSetTable(u32 jis2ucs, u32 ucs2jis) +// int sceCccUTF8toUTF16(u32 dstAddr, u32 dstSize, u32 srcAddr) +// int sceCccUTF8toSJIS(u32 dstAddr, u32 dstSize, u32 srcAddr) +// int sceCccUTF16toUTF8(u32 dstAddr, u32 dstSize, u32 srcAddr) +// int sceCccUTF16toSJIS(u32 dstAddr, u32 dstSize, u32 srcAddr) +// int sceCccSJIStoUTF8(u32 dstAddr, u32 dstSize, u32 srcAddr) +// int sceCccSJIStoUTF16(u32 dstAddr, u32 dstSize, u32 srcAddr) +// int sceCccStrlenUTF8(u32 strAddr) +// int sceCccStrlenUTF16(u32 strAddr) +// int sceCccStrlenSJIS(u32 strAddr) +// u32 sceCccEncodeUTF8(u32 dstAddrAddr, u32 ucs) +// void sceCccEncodeUTF16(u32 dstAddrAddr, u32 ucs) +// u32 sceCccEncodeSJIS(u32 dstAddrAddr, u32 jis) +// u32 sceCccDecodeUTF8(u32 dstAddrAddr) +// u32 sceCccDecodeUTF16(u32 dstAddrAddr) +// u32 sceCccDecodeSJIS(u32 dstAddrAddr) +// int sceCccIsValidUTF8(u32 c) +// int sceCccIsValidUTF16(u32 c) +// int sceCccIsValidSJIS(u32 c) +// int sceCccIsValidUCS2(u32 c) +// int sceCccIsValidUCS4(u32 c) +// int sceCccIsValidJIS(u32 c) +// int sceCccIsValidUnicode(u32 c) +// #u32 sceCccSetErrorCharUTF8(u32 c) +// #u32 sceCccSetErrorCharUTF16(u32 c) +// #u32 sceCccSetErrorCharSJIS(u32 c) +// u32 sceCccUCStoJIS(u32 c, u32 alt) +// u32 sceCccJIStoUCS(u32 c, u32 alt) +// - sceFont: search charCode +// int sceFontGetCharInfo(u32 fontHandle, u32 charCode, u32 charInfoPtr) +// int sceFontGetShadowInfo(u32 fontHandle, u32 charCode, u32 charInfoPtr) +// int sceFontGetCharImageRect(u32 fontHandle, u32 charCode, u32 charRectPtr) +// int sceFontGetShadowImageRect(u32 fontHandle, u32 charCode, u32 charRectPtr) +// int sceFontGetCharGlyphImage(u32 fontHandle, u32 charCode, u32 glyphImagePtr) +// int sceFontGetCharGlyphImage_Clip(u32 fontHandle, u32 charCode, u32 glyphImagePtr, int clipXPos, int clipYPos, int clipWidth, int clipHeight) +// #int sceFontSetAltCharacterCode(u32 fontLibHandle, u32 charCode) +// int sceFontGetShadowGlyphImage(u32 fontHandle, u32 charCode, u32 glyphImagePtr) +// int sceFontGetShadowGlyphImage_Clip(u32 fontHandle, u32 charCode, u32 glyphImagePtr, int clipXPos, int clipYPos, int clipWidth, int clipHeight) +// - sceKernelInterrupt +// u32 sysclib_strcat(u32 dst, u32 src) +// int sysclib_strcmp(u32 dst, u32 src) +// u32 sysclib_strcpy(u32 dst, u32 src) +// u32 sysclib_strlen(u32 src) +// +// Sample debug string: +// 006EFD8E PUSH PPSSPPWi.00832188 ASCII "sceCccEncodeSJIS(%08x, U+%04x)" +// Corresponding source code in sceCcc: +// ERROR_LOG(HLE, "sceCccEncodeSJIS(%08x, U+%04x): invalid pointer", dstAddrAddr, jis); + +struct PPSSPPFunction +{ + const char *hookName; // hook name + size_t argIndex; // argument index + unsigned long hookType; // hook parameter type + unsigned long hookSplit; // hook parameter split, positive: stack, negative: registers + const char *pattern; // debug string used within the function +}; + +// jichi 7/14/2014: UTF-8 is treated as STRING +// http://867258173.diandian.com/post/2014-06-26/40062099618 +// sceFontGetCharGlyphImage_Clip +// Sample game: [KID] Monochrome: sceFontGetCharInfo, sceFontGetCharGlyphImage_Clip +// +// Example: { L"sceFontGetCharInfo", 2, USING_UNICODE, 4, "sceFontGetCharInfo(" } +// Text is at arg2, using arg1 as split +#define PPSSPP_FUNCTIONS_INITIALIZER \ + { "sceCccStrlenSJIS", 1, USING_STRING, 0, "sceCccStrlenSJIS(" } \ + , { "sceCccStrlenUTF8", 1, USING_UTF8, 0, "sceCccStrlenUTF8(" } \ + , { "sceCccStrlenUTF16", 1, USING_UNICODE, 0, "sceCccStrlenUTF16(" } \ +\ + , { "sceCccSJIStoUTF8", 3, USING_UTF8, 0, "sceCccSJIStoUTF8(" } \ + , { "sceCccSJIStoUTF16", 3, USING_STRING, 0, "sceCccSJIStoUTF16(" } \ + , { "sceCccUTF8toSJIS", 3, USING_UTF8, 0, "sceCccUTF8toSJIS(" } \ + , { "sceCccUTF8toUTF16", 3, USING_UTF8, 0, "sceCccUTF8toUTF16(" } \ + , { "sceCccUTF16toSJIS", 3, USING_UNICODE, 0, "sceCccUTF16toSJIS(" } \ + , { "sceCccUTF16toUTF8", 3, USING_UNICODE, 0, "sceCccUTF16toUTF8(" } \ +\ + , { "sceFontGetCharInfo", 2, USING_UNICODE, 4, "sceFontGetCharInfo(" } \ + , { "sceFontGetShadowInfo", 2, USING_UNICODE, 4, "sceFontGetShadowInfo("} \ + , { "sceFontGetCharImageRect", 2, USING_UNICODE, 4, "sceFontGetCharImageRect(" } \ + , { "sceFontGetShadowImageRect", 2, USING_UNICODE, 4, "sceFontGetShadowImageRect(" } \ + , { "sceFontGetCharGlyphImage", 2, USING_UNICODE, 4, "sceFontGetCharGlyphImage(" } \ + , { "sceFontGetCharGlyphImage_Clip", 2, USING_UNICODE, 4, "sceFontGetCharGlyphImage_Clip(" } \ + , { "sceFontGetShadowGlyphImage", 2, USING_UNICODE, 4, "sceFontGetShadowGlyphImage(" } \ + , { "sceFontGetShadowGlyphImage_Clip", 2, USING_UNICODE, 4, "sceFontGetShadowGlyphImage_Clip(" } \ +\ + , { "sysclib_strcat", 2, USING_STRING, 0, "Untested sysclib_strcat(" } \ + , { "sysclib_strcpy", 2, USING_STRING, 0, "Untested sysclib_strcpy(" } \ + , { "sysclib_strlen", 1, USING_STRING, 0, "Untested sysclib_strlen(" } + + // Disabled as I am not sure how to deal with the source string + //, { "sceCccEncodeSJIS", 2, USING_STRING, 0, "sceCccEncodeSJIS(" } + //, { "sceCccEncodeUTF8", 2, USING_UTF8, 0, "sceCccEncodeUTF8(" } + //, { "sceCccEncodeUTF16", 2, USING_UNICODE, 0, "sceCccEncodeUTF16(" } + //, { "sysclib_strcmp", 2, USING_STRING, 0, "Untested sysclib_strcmp(" } + +// EOF diff --git a/vnr/vnrhook/src/except.h b/vnr/vnrhook/src/except.h new file mode 100644 index 0000000..a8540bb --- /dev/null +++ b/vnr/vnrhook/src/except.h @@ -0,0 +1,25 @@ +#pragma once + +// except.h +// 9/17/2013 jichi + +#define ITH_RAISE (*(int*)0 = 0) // raise C000005, for debugging only + +#ifdef ITH_HAS_SEH + +# define ITH_TRY __try +# define ITH_EXCEPT __except(EXCEPTION_EXECUTE_HANDLER) +# define ITH_WITH_SEH(...) \ + ITH_TRY { __VA_ARGS__; } ITH_EXCEPT {} + +#else // for old msvcrt.dll on Windows XP that does not have exception handler + +// Currently, only with_seh is implemented. Try and catch are not. +# define ITH_TRY if (true) +# define ITH_EXCEPT else +# include "winseh/winseh.h" +# define ITH_WITH_SEH(...) seh_with(__VA_ARGS__) + +#endif // ITH_HAS_SEH + +// EOF diff --git a/vnr/vnrhook/src/hijack/texthook.cc b/vnr/vnrhook/src/hijack/texthook.cc new file mode 100644 index 0000000..27f4f20 --- /dev/null +++ b/vnr/vnrhook/src/hijack/texthook.cc @@ -0,0 +1,910 @@ +// texthook.cc +// 8/24/2013 jichi +// Branch: ITH_DLL/texthook.cpp, rev 128 +// 8/24/2013 TODO: Clean up this file + +#ifdef _MSC_VER +# pragma warning (disable:4100) // C4100: unreference formal parameter +# pragma warning (disable:4018) // C4018: sign/unsigned mismatch +//# pragma warning (disable:4733) // C4733: Inline asm assigning to 'FS:0' : handler not registered as safe handler +#endif // _MSC_VER + +#include "src/hijack/texthook.h" +#include "src/engine/match.h" +#include "src/except.h" +#include "src/main.h" +#include "include/const.h" +#include "ithsys/ithsys.h" +#include "winkey/winkey.h" +#include "disasm/disasm.h" +//#include "winseh/winseh.h" + +//#define ConsoleOutput(...) (void)0 // jichi 9/17/2013: I don't need this >< + +// - Global variables - + +// 10/14/2014 jichi: disable GDI hooks +static bool gdi_hook_enabled_ = true; // enable GDI by default +static bool gdiplus_hook_enabled_ = false; // disable GDIPlus by default +bool GDIHooksEnabled() { return ::gdi_hook_enabled_; } +bool GDIPlusHooksEnabled() { return ::gdiplus_hook_enabled_; } +void EnableGDIHooks() { ::gdi_hook_enabled_ = true; } +void EnableGDIPlusHooks() { ::gdiplus_hook_enabled_ = true; } +void DisableGDIHooks() { ::gdi_hook_enabled_ = false; } +void DisableGDIPlusHooks() { ::gdiplus_hook_enabled_ = false; } + +static bool IsGDIFunction(LPCVOID addr) +{ + static LPVOID funcs[] = { HOOK_GDI_FUNCTION_LIST }; + for (size_t i = 0; i < sizeof(funcs)/sizeof(*funcs); i++) + if (addr == funcs[i]) + return true; + return false; +} + +//FilterRange filter[8]; + +DWORD flag, + enter_count; + +TextHook *hookman, + *current_available; + +// - Unnamed helpers - + +namespace { // unnamed +//provide const time hook entry. +int userhook_count; + +#if 0 // 3/6/2015 jichi: this hook is not used and hence disabled +const byte common_hook2[] = { + 0x89, 0x3c,0xe4, // mov [esp],edi + 0x60, // pushad + 0x9c, // pushfd + 0x8d,0x54,0x24,0x28, // lea edx,[esp+0x28] ; esp value + 0x8b,0x32, // mov esi,[edx] ; return address + 0xb9, 0,0,0,0, // mov ecx, $ ; pointer to TextHook + 0xe8, 0,0,0,0, // call @hook + 0x9d, // popfd + 0x61, // popad + 0x5f, // pop edi ; skip return address on stack +}; //... +#endif // 0 + +const BYTE common_hook[] = { + 0x9c, // pushfd + 0x60, // pushad + 0x9c, // pushfd + 0x8d,0x54,0x24,0x28, // lea edx,[esp+0x28] ; esp value + 0x8b,0x32, // mov esi,[edx] ; return address + 0xb9, 0,0,0,0, // mov ecx, $ ; pointer to TextHook + 0xe8, 0,0,0,0, // call @hook + 0x9d, // popfd + 0x61, // popad + 0x9d // popfd +}; + +/** + * jichi 7/19/2014 + * + * @param original_addr + * @param new_addr + * @param hook_len + * @param original_len + * @return -1 if failed, else 0 if ?, else ? + */ +int MapInstruction(DWORD original_addr, DWORD new_addr, BYTE &hook_len, BYTE &original_len) +{ + int flag = 0; + DWORD l = 0; + const BYTE *r = (const BYTE *)original_addr; // 7/19/2014 jichi: original address is not modified + BYTE *c = (BYTE *)new_addr; // 7/19/2014 jichi: but new address might be modified + while((r - (BYTE *) original_addr) < 5) { + l = ::disasm(r); + if (l == 0) { + ConsoleOutput("vnrcli:MapInstruction: FAILED: failed to disasm"); + return -1; + } + + ::memcpy(c, r, l); + if (*r >= 0x70 && *r < 0x80) { + c[0] = 0xf; + c[1] = *r + 0x10; + c += 6; + __asm + { + mov eax,r + add eax,2 + movsx edx,byte ptr [eax-1] + add edx,eax + mov eax,c + sub edx,eax + mov [eax-4],edx + } + } else if (*r == 0xeb) { + c[0] = 0xe9; + c += 5; + __asm + { + mov eax,r + add eax,2 + movsx edx,[eax-1] + add edx,eax + mov eax,c + sub edx,eax + mov [eax-4],edx + } + if (r - (BYTE *)original_addr < 5 - l) { + ConsoleOutput("vnrcli:MapInstruction: not safe to move instruction right after short jmp"); + return -1; // Not safe to move instruction right after short jmp. + } else + flag = 1; + } else if (*r == 0xe8 || *r == 0xe9) { + c[0]=*r; + c += 5; + flag = (*r == 0xe9); + __asm + { + mov eax,r + add eax,5 + mov edx,[eax-4] + add edx,eax + mov eax,c + sub edx,eax + mov [eax-4],edx + } + } else if (*r == 0xf && (*(r + 1) >> 4) == 0x8) { + c += 6; + __asm + { + mov eax,r + mov edx,dword ptr [eax+2] + add eax,6 + add eax,edx + mov edx,c + sub eax,edx + mov [edx-4],eax + } + } + else + c += l; + r += l; + } + original_len = r - (BYTE *)original_addr; + hook_len = c - (BYTE *)new_addr; + return flag; +} + +//copy original instruction +//jmp back +DWORD GetModuleBase(DWORD hash) +{ + __asm + { + mov eax,fs:[0x30] + mov eax,[eax+0xc] + mov esi,[eax+0x14] + mov edi,_wcslwr +listfind: + mov edx,[esi+0x28] + test edx,edx + jz notfound + push edx + call edi + pop edx + xor eax,eax +calc: + movzx ecx, word ptr [edx] + test cl,cl + jz fin + ror eax,7 + add eax,ecx + add edx,2 + jmp calc +fin: + cmp eax,[hash] + je found + mov esi,[esi] + jmp listfind +notfound: + xor eax,eax + jmp termin +found: + mov eax,[esi+0x10] +termin: + } +} + +DWORD GetModuleBase() +{ + __asm + { + mov eax, fs:[0x18] + mov eax, [eax + 0x30] + mov eax, [eax + 0xc] + mov eax, [eax + 0xc] + mov eax, [eax + 0x18] + } +} + +//void NotifyHookInsert() +//{ +// if (live) +// { +// BYTE buffer[0x10]; +// *(DWORD*)buffer=-1; +// *(DWORD*)(buffer+4)=1; +// IO_STATUS_BLOCK ios; +// NtWriteFile(hPipe,0,0,0,&ios,buffer,0x10,0,0); +// } +//} + +__declspec(naked) void SafeExit() // Return to eax +{ + __asm + { + mov [esp+0x24], eax + popfd + popad + retn + } +} + +#if 0 +// jichi 12/2/2013: This function mostly return 0. +// But sometimes return the hook address from TextHook::Send +__declspec(naked) // jichi 10/2/2013: No prolog and epilog +int ProcessHook(DWORD dwDataBase, DWORD dwRetn, TextHook *hook) // Use SEH to ensure normal execution even bad hook inserted. +{ + //with_seh(hook->Send(dwDataBase, dwRetn)); + seh_push_(seh_exit, 0, eax, ebx) // jichi 12/13/2013: only eax and ebx are available. ecx and edx are used. + __asm + { + push esi + push edx + call TextHook::UnsafeSend + test eax, eax + jz seh_exit // label in seh_pop + mov ecx, SafeExit + mov [esp + 8], ecx // jichi 12/13/2013: change exit point if Send returns non-zero, not + 8 beause two elements has been pused + } + seh_pop_(seh_exit) + __asm retn // jichi 12/13/2013: return near, see: http://stackoverflow.com/questions/1396909/ret-retn-retf-how-to-use-them +} +#endif // 0 + +#if 1 +__declspec(naked) // jichi 10/2/2013: No prolog and epilog +int ProcessHook(DWORD dwDataBase, DWORD dwRetn, TextHook *hook) // Use SEH to ensure normal execution even bad hook inserted. +{ + // jichi 12/17/2013: The function parameters here are meaning leass. The parameters are in esi and edi + __asm + { + push esi + push edx + call TextHook::Send + test eax, eax + jz ok // label in seh_pop + mov ecx, SafeExit + mov [esp], ecx // jichi 12/13/2013: change exit point if Send returns non-zero + ok: + retn // jichi 12/13/2013: return near, see: http://stackoverflow.com/questions/1396909/ret-retn-retf-how-to-use-them + } +} +#endif // 1 + + // jichi 12/13/2013: return if the retn address is within the filter dlls +inline bool HookFilter(DWORD retn) +{ + for (DWORD i = 0; ::filter[i].lower; i++) + if (retn > ::filter[i].lower && retn < ::filter[i].upper) + return true; + return false; +} + +// Return false if all text are ascii +bool NoAsciiFilter(LPVOID data, DWORD *size, HookParam *, BYTE) +{ + auto text = reinterpret_cast(data); + if (text) + for (size_t i = 0; i < *size; i++) + if (text[i] > 127) + return true; + return false; +} + +} // unnamed namespace + +// - TextHook methods - + +// jichi 12/2/2013: This function mostly return 0. +// It return the hook address only for auxiliary case. +// However, because no known hooks are auxiliary, this function always return 0. +// +// jichi 5/11/2014: +// - dwDataBase: the stack address +// - dwRetn: the return address of the hook +DWORD TextHook::Send(DWORD dwDataBase, DWORD dwRetn) +{ + // jich: 6/17/2015: do not send when ctrl/shift are controlled + //if (WinKey::isKeyControlPressed() || WinKey::isKeyShiftPressed() && !WinKey::isKeyReturnPressed()) + // return 0; + + DWORD ret = 0; + //char b[0x100]; + //::wcstombs(b, hook_name, 0x100); + //ConsoleOutput(b); + ITH_WITH_SEH(ret = UnsafeSend(dwDataBase, dwRetn)); + return ret; +} + +DWORD TextHook::UnsafeSend(DWORD dwDataBase, DWORD dwRetn) +{ + enum { SMALL_BUFF_SIZE = 0x80 }; + enum { MAX_DATA_SIZE = 0x10000 }; // jichi 12/25/2013: The same as the original ITH + DWORD dwCount, + dwAddr, + dwDataIn, + dwSplit; + BYTE *pbData, + pbSmallBuff[SMALL_BUFF_SIZE]; + DWORD dwType = hp.type; + if (!::live) // the pipe thread is busy + return 0; + if ((dwType & NO_CONTEXT) == 0 && HookFilter(dwRetn)) + return 0; + + if ((dwType & NO_ASCII) && !hp.filter_fun) + hp.filter_fun = NoAsciiFilter; + + // jichi 10/24/2014: Skip GDI functions + if (!::gdi_hook_enabled_ && ::IsGDIFunction((LPCVOID)hp.address)) + return 0; + + dwAddr = hp.address; + + /** jichi 12/24/2014 + * @param addr function address + * @param frame real address of the function, supposed to be the same as addr + * @param stack address of current stack - 4 + * @return If success, which is reverted + */ + if (::trigger) + ::trigger = Engine::InsertDynamicHook((LPVOID)dwAddr, *(DWORD *)(dwDataBase - 0x1c), *(DWORD *)(dwDataBase-0x18)); + // jichi 10/21/2014: Directly invoke engine functions. + //if (trigger) { + // if (InsertDynamicHook) + // trigger = InsertDynamicHook((LPVOID)dwAddr, *(DWORD *)(dwDataBase - 0x1c), *(DWORD *)(dwDataBase-0x18)); + // else + // trigger = 0; + //} +#if 0 // diasble HOOK_AUXILIARY + // jichi 12/13/2013: None of known hooks are auxiliary + if (dwType & HOOK_AUXILIARY) { + //Clean hook when dynamic hook finished. + //AUX hook is only used for a foothold of dynamic hook. + if (!trigger) { + ClearHook(); + // jichi 12/13/2013: This is the only place where this function could return non-zero value + // However, I non of the known hooks are auxiliary + return dwAddr; + } + return 0; + } +#endif // 0 + // jichi 10/24/2014: generic hook function + if (hp.hook_fun && !hp.hook_fun(dwDataBase, &hp)) + hp.hook_fun = nullptr; + + if (dwType & HOOK_EMPTY) // jichi 10/24/2014: dummy hook only for dynamic hook + return 0; + + // jichi 2/2/2015: Send multiple texts + for (BYTE textIndex = 0; textIndex <= hp.extra_text_count; textIndex++) { + dwCount = 0; + dwSplit = 0; + dwDataIn = *(DWORD *)(dwDataBase + hp.offset); // default value + + //if (dwType & EXTERN_HOOK) { + if (hp.text_fun) { // jichi 10/24/2014: remove EXTERN_HOOK + //DataFun fun=(DataFun)hp.text_fun; + //auto fun = hp.text_fun; + hp.text_fun(dwDataBase, &hp, textIndex, &dwDataIn, &dwSplit, &dwCount); + //if (dwCount == 0 || dwCount > MAX_DATA_SIZE) + // return 0; + if (dwSplit && (dwType & RELATIVE_SPLIT) && dwSplit > ::processStartAddress) + dwSplit -= ::processStartAddress; + } else { + if (dwDataIn == 0) + return 0; + if (dwType & FIXING_SPLIT) + dwSplit = FIXED_SPLIT_VALUE; // fuse all threads, and prevent floating + else if (dwType & USING_SPLIT) { + dwSplit = *(DWORD *)(dwDataBase + hp.split); + if (dwType & SPLIT_INDIRECT) { + if (IthGetMemoryRange((LPVOID)(dwSplit + hp.split_index), 0, 0)) + dwSplit = *(DWORD *)(dwSplit + hp.split_index); + else + return 0; + } + if (dwSplit && (dwType & RELATIVE_SPLIT) && dwSplit > ::processStartAddress) + dwSplit -= ::processStartAddress; + } + if (dwType & DATA_INDIRECT) { + if (IthGetMemoryRange((LPVOID)(dwDataIn + hp.index), 0, 0)) + dwDataIn = *(DWORD *)(dwDataIn + hp.index); + else + return 0; + } + //if (dwType & PRINT_DWORD) { + // swprintf((WCHAR *)(pbSmallBuff + HEADER_SIZE), L"%.8X ", dwDataIn); + // dwDataIn = (DWORD)pbSmallBuff + HEADER_SIZE; + //} + dwCount = GetLength(dwDataBase, dwDataIn); + } + + // jichi 12/25/2013: validate data size + if (dwCount == 0 || dwCount > MAX_DATA_SIZE) + return 0; + + size_t sz = dwCount + HEADER_SIZE; + if (sz >= SMALL_BUFF_SIZE) + pbData = new BYTE[sz]; + //ITH_MEMSET_HEAP(pbData, 0, sz * sizeof(BYTE)); // jichi 9/26/2013: zero memory + else + pbData = pbSmallBuff; + + if (hp.length_offset == 1) { + if (dwType & STRING_LAST_CHAR) { + LPWSTR ts = (LPWSTR)dwDataIn; + dwDataIn = ts[::wcslen(ts) -1]; + } + dwDataIn &= 0xffff; + if ((dwType & BIG_ENDIAN) && (dwDataIn >> 8)) + dwDataIn = _byteswap_ushort(dwDataIn & 0xffff); + if (dwCount == 1) + dwDataIn &= 0xff; + *(WORD *)(pbData + HEADER_SIZE) = dwDataIn & 0xffff; + } + else + ::memcpy(pbData + HEADER_SIZE, (void *)dwDataIn, dwCount); + + // jichi 10/14/2014: Add filter function + if (hp.filter_fun && !hp.filter_fun(pbData + HEADER_SIZE, &dwCount, &hp, textIndex) || dwCount <= 0) { + if (pbData != pbSmallBuff) + delete[] pbData; + return 0; + } + + *(DWORD *)pbData = dwAddr; + if (dwType & (NO_CONTEXT|FIXING_SPLIT)) + dwRetn = 0; + else if (dwRetn && (dwType & RELATIVE_SPLIT)) + dwRetn -= ::processStartAddress; + + *((DWORD *)pbData + 1) = dwRetn; + *((DWORD *)pbData + 2) = dwSplit; + if (dwCount) { + IO_STATUS_BLOCK ios = {}; + + IthCoolDown(); // jichi 9/28/2013: cool down to prevent parallelization in wine + //CliLockPipe(); + if (STATUS_PENDING == NtWriteFile(::hPipe, 0, 0, 0, &ios, pbData, dwCount + HEADER_SIZE, 0, 0)) { + NtWaitForSingleObject(::hPipe, 0, 0); + NtFlushBuffersFile(::hPipe, &ios); + } + //CliUnlockPipe(); + } + if (pbData != pbSmallBuff) + delete[] pbData; + } + return 0; + +} + +int TextHook::InsertHook() +{ + //ConsoleOutput("vnrcli:InsertHook: enter"); + NtWaitForSingleObject(hmMutex, 0, 0); + int ok = InsertHookCode(); + IthReleaseMutex(hmMutex); + if (hp.type & HOOK_ADDITIONAL) { + NotifyHookInsert(hp.address); + //ConsoleOutput(hook_name); + //RegisterHookName(hook_name,hp.address); + } + //ConsoleOutput("vnrcli:InsertHook: leave"); + return ok; +} + +int TextHook::InsertHookCode() +{ + enum : int { yes = 0, no = 1 }; + DWORD ret = no; + // jichi 9/17/2013: might raise 0xC0000005 AccessViolationException on win7 + ITH_WITH_SEH(ret = UnsafeInsertHookCode()); + //if (ret == no) + // ITH_WARN(L"Failed to insert hook"); + return ret; +} + +int TextHook::UnsafeInsertHookCode() +{ + //ConsoleOutput("vnrcli:UnsafeInsertHookCode: enter"); + enum : int { yes = 0, no = 1 }; + // MODULE_OFFSET is set, but there's no module address + // this means that this is an absolute address found on Windows 2000/XP + // we make the address relative to the process base + // we also store the original address in the function field because normally there can not + // exist a function address without a module address + if (hp.type & MODULE_OFFSET && !hp.module) { + DWORD base = GetModuleBase(); + hp.function = hp.address; + hp.address -= 0x400000; + hp.address += base; + hp.type &= ~MODULE_OFFSET; + } + else if (hp.module && (hp.type & MODULE_OFFSET)) { // Map hook offset to real address. + if (DWORD base = GetModuleBase(hp.module)) { + if (hp.function && (hp.type & FUNCTION_OFFSET)) { + base = GetExportAddress(base, hp.function); + if (base) + hp.address += base; + else { + current_hook--; + ConsoleOutput("vnrcli:UnsafeInsertHookCode: FAILED: function not found in the export table"); + return no; + } + } + else { + hp.address += base; + } + hp.type &= ~(MODULE_OFFSET | FUNCTION_OFFSET); + } + else { + current_hook--; + ConsoleOutput("vnrcli:UnsafeInsertHookCode: FAILED: module not present"); + return no; + } + } + + { + TextHook *it = hookman; + for (int i = 0; (i < current_hook) && it; it++) { // Check if there is a collision. + if (it->Address()) + i++; + //it = hookman + i; + if (it == this) + continue; + if (it->Address() <= hp.address && + it->Address() + it->Length() > hp.address) { + it->ClearHook(); + break; + } + } + } + + // Verify hp.address. + MEMORY_BASIC_INFORMATION info = {}; + NtQueryVirtualMemory(NtCurrentProcess(), (LPVOID)hp.address, MemoryBasicInformation, &info, sizeof(info), nullptr); + if (info.Type & PAGE_NOACCESS) { + ConsoleOutput("vnrcli:UnsafeInsertHookCode: FAILED: page no access"); + return no; + } + + // Initialize common routine. + memcpy(recover, common_hook, sizeof(common_hook)); + BYTE *c = (BYTE *)hp.address, + *r = recover; + BYTE inst[8]; // jichi 9/27/2013: Why 8? Only 5 bytes will be written using NtWriteVirtualMemory + inst[0] = 0xe9; // jichi 9/27/2013: 0xe9 is jump, see: http://code.google.com/p/sexyhook/wiki/SEXYHOOK_Hackers_Manual + __asm + { + mov edx,r // r = recover + mov eax,this + mov [edx+0xa],eax // push TextHook*, resolve to correspond hook. + lea eax,[edx+0x13] + mov edx,ProcessHook + sub edx,eax + mov [eax-4],edx // call ProcessHook + mov eax,c + add eax,5 + mov edx,r + sub edx,eax + lea eax,inst+1 + mov [eax],edx // jichi 12/17/2013: the parameter of jmp is in edx. So, ProcessHook must be naked. + } + r += sizeof(common_hook); + hp.hook_len = 5; + //bool jmpflag=false; // jichi 9/28/2013: nto used + // Copy original code. + switch (MapInstruction(hp.address, (DWORD)r, hp.hook_len, hp.recover_len)) { + case -1: + ConsoleOutput("vnrcli:UnsafeInsertHookCode: FAILED: failed to map instruction"); + return no; + case 0: + __asm + { + mov ecx,this + movzx eax,[ecx]hp.hook_len + movzx edx,[ecx]hp.recover_len + add edx,[ecx]hp.address + add eax,r + add eax,5 + sub edx,eax + mov [eax-5],0xe9 // jichi 9/27/2013: 0xe9 is jump + mov [eax-4],edx + } + } + // jichi 9/27/2013: Save the original instructions in the memory + memcpy(original, (LPVOID)hp.address, hp.recover_len); + //Check if the new hook range conflict with existing ones. Clear older if conflict. + { + TextHook *it = hookman; + for (int i = 0; i < current_hook; it++) { + if (it->Address()) + i++; + if (it == this) + continue; + if (it->Address() >= hp.address && + it->Address() < hp.hook_len + hp.address) { + it->ClearHook(); + break; + } + } + } + // Insert hook and flush instruction cache. + enum {c8 = 0xcccccccc}; + DWORD int3[] = {c8, c8}; + DWORD t = 0x100, + old, + len; + // jichi 9/27/2013: Overwrite the memory with inst + // See: http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Memory%20Management/Virtual%20Memory/NtProtectVirtualMemory.html + // See: http://doxygen.reactos.org/d8/d6b/ndk_2mmfuncs_8h_af942709e0c57981d84586e74621912cd.html + DWORD addr = hp.address; + NtProtectVirtualMemory(NtCurrentProcess(), (PVOID *)&addr, &t, PAGE_EXECUTE_READWRITE, &old); + NtWriteVirtualMemory(NtCurrentProcess(), (BYTE *)hp.address, inst, 5, &t); + len = hp.recover_len - 5; + if (len) + NtWriteVirtualMemory(NtCurrentProcess(), (BYTE *)hp.address + 5, int3, len, &t); + NtFlushInstructionCache(NtCurrentProcess(), (LPVOID)hp.address, hp.recover_len); + NtFlushInstructionCache(NtCurrentProcess(), (LPVOID)::hookman, 0x1000); + //ConsoleOutput("vnrcli:UnsafeInsertHookCode: leave: succeed"); + return 0; +} + +int TextHook::InitHook(LPVOID addr, DWORD data, DWORD data_ind, + DWORD split_off, DWORD split_ind, WORD type, DWORD len_off) +{ + NtWaitForSingleObject(hmMutex, 0, 0); + hp.address = (DWORD)addr; + hp.offset = data; + hp.index = data_ind; + hp.split = split_off; + hp.split_index = split_ind; + hp.type = type; + hp.hook_len = 0; + hp.module = 0; + hp.length_offset = len_off & 0xffff; + current_hook++; + if (current_available >= this) + for (current_available = this + 1; current_available->Address(); current_available++); + IthReleaseMutex(hmMutex); + return this - hookman; +} + +int TextHook::InitHook(const HookParam &h, LPCSTR name, WORD set_flag) +{ + NtWaitForSingleObject(hmMutex, 0, 0); + hp = h; + hp.type |= set_flag; + if (name && name != hook_name) { + SetHookName(name); + } + current_hook++; + current_available = this+1; + while (current_available->Address()) + current_available++; + IthReleaseMutex(hmMutex); + return 1; +} + +int TextHook::RemoveHook() +{ + enum : int { yes = 1, no = 0 }; + if (!hp.address) + return no; + ConsoleOutput("vnrcli:RemoveHook: enter"); + const LONGLONG timeout = -50000000; // jichi 9/28/2012: in 100ns, wait at most for 5 seconds + NtWaitForSingleObject(hmMutex, 0, (PLARGE_INTEGER)&timeout); + DWORD l = hp.hook_len; + //with_seh({ // jichi 9/17/2013: might crash >< + // jichi 12/25/2013: Actually, __try cannot catch such kind of exception + ITH_TRY { + NtWriteVirtualMemory(NtCurrentProcess(), (LPVOID)hp.address, original, hp.recover_len, &l); + NtFlushInstructionCache(NtCurrentProcess(), (LPVOID)hp.address, hp.recover_len); + } ITH_EXCEPT {} + //}); + hp.hook_len = 0; + IthReleaseMutex(hmMutex); + ConsoleOutput("vnrcli:RemoveHook: leave"); + return yes; +} + +int TextHook::ClearHook() +{ + NtWaitForSingleObject(hmMutex, 0, 0); + int err = RemoveHook(); + if (hook_name) { + delete[] hook_name; + hook_name = nullptr; + } + memset(this, 0, sizeof(TextHook)); // jichi 11/30/2013: This is the original code of ITH + //if (current_available>this) + // current_available = this; + current_hook--; + IthReleaseMutex(hmMutex); + return err; +} + +int TextHook::ModifyHook(const HookParam &hp) +{ + //WCHAR name[0x40]; + DWORD len = 0; + if (hook_name) + len = ::strlen(hook_name); + LPSTR name = 0; + if (len) { + name = new char[len + 1]; + //ITH_MEMSET_HEAP(name, 0, sizeof(wchar_t) * (len + 1)); // jichi 9/26/2013: zero memory + strcpy(name, hook_name); + } + ClearHook(); + InitHook(hp, name); + InsertHook(); + if (name) + delete[] name; + return 0; +} + +int TextHook::RecoverHook() +{ + if (hp.address) { + // jichi 9/28/2013: Only enable TextOutA to debug Cross Channel + //if (hp.address == (DWORD)TextOutA) + InsertHook(); + return 1; + } + return 0; +} + +int TextHook::SetHookName(LPCSTR name) +{ + name_length = strlen(name) + 1; + if (hook_name) + delete[] hook_name; + hook_name = new char[name_length]; + //ITH_MEMSET_HEAP(hook_name, 0, sizeof(wchar_t) * name_length); // jichi 9/26/2013: zero memory + strcpy(hook_name, name); + return 0; +} + +int TextHook::GetLength(DWORD base, DWORD in) +{ + if (base == 0) + return 0; + int len; + switch (hp.length_offset) { + default: // jichi 12/26/2013: I should not put this default branch to the end + len = *((int *)base + hp.length_offset); + if (len >= 0) { + if (hp.type & USING_UNICODE) + len <<= 1; + break; + } + else if (len != -1) + break; + //len == -1 then continue to case 0. + case 0: + if (hp.type & USING_UNICODE) + len = wcslen((const wchar_t *)in) << 1; + else + len = strlen((const char *)in); + break; + case 1: + if (hp.type & USING_UNICODE) + len = 2; + else { + if (hp.type & BIG_ENDIAN) + in >>= 8; + len = LeadByteTable[in & 0xff]; //Slightly faster than IsDBCSLeadByte + } + break; + } + // jichi 12/25/2013: This function originally return -1 if failed + //return len; + return max(0, len); +} + +// EOF + +//typedef void (*DataFun)(DWORD, const HookParam*, DWORD*, DWORD*, DWORD*); + +/* +DWORD recv_esp, recv_addr; +EXCEPTION_DISPOSITION ExceptHandler(EXCEPTION_RECORD *ExceptionRecord, + void *EstablisherFrame, CONTEXT *ContextRecord, void *DispatcherContext) +{ + //WCHAR str[0x40], + // name[0x100]; + //ConsoleOutput(L"Exception raised during hook processing."); + //swprintf(str, L"Exception code: 0x%.8X", ExceptionRecord->ExceptionCode); + //ConsoleOutput(str); + //MEMORY_BASIC_INFORMATION info; + //if (NT_SUCCESS(NtQueryVirtualMemory(NtCurrentProcess(),(PVOID)ContextRecord->Eip, + // MemoryBasicInformation,&info,sizeof(info),0)) && + // NT_SUCCESS(NtQueryVirtualMemory(NtCurrentProcess(),(PVOID)ContextRecord->Eip, + // MemorySectionName,name,0x200,0))) { + // swprintf(str, L"Exception offset: 0x%.8X:%s", + // ContextRecord->Eip-(DWORD)info.AllocationBase, + // wcsrchr(name,L'\\')+1); + // ConsoleOutput(str); + //} + ContextRecord->Esp = recv_esp; + ContextRecord->Eip = recv_addr; + return ExceptionContinueExecution; +} + + +//typedef void (*DataFun)(DWORD, const HookParam*, DWORD*, DWORD*, DWORD*); + +DWORD recv_esp, recv_addr; +EXCEPTION_DISPOSITION ExceptHandler(EXCEPTION_RECORD *ExceptionRecord, + void *EstablisherFrame, CONTEXT *ContextRecord, void *DispatcherContext) +{ + //WCHAR str[0x40], + // name[0x100]; + //ConsoleOutput(L"Exception raised during hook processing."); + //swprintf(str, L"Exception code: 0x%.8X", ExceptionRecord->ExceptionCode); + //ConsoleOutput(str); + //MEMORY_BASIC_INFORMATION info; + //if (NT_SUCCESS(NtQueryVirtualMemory(NtCurrentProcess(),(PVOID)ContextRecord->Eip, + // MemoryBasicInformation,&info,sizeof(info),0)) && + // NT_SUCCESS(NtQueryVirtualMemory(NtCurrentProcess(),(PVOID)ContextRecord->Eip, + // MemorySectionName,name,0x200,0))) { + // swprintf(str, L"Exception offset: 0x%.8X:%s", + // ContextRecord->Eip-(DWORD)info.AllocationBase, + // wcsrchr(name,L'\\')+1); + // ConsoleOutput(str); + //} + ContextRecord->Esp = recv_esp; + ContextRecord->Eip = recv_addr; + return ExceptionContinueExecution; +} + +__declspec(naked) // jichi 10/2/2013: No prolog and epilog +int ProcessHook(DWORD dwDataBase, DWORD dwRetn, TextHook *hook) // Use SEH to ensure normal execution even bad hook inserted. +{ + __asm + { + mov eax,seh_recover + mov recv_addr,eax + push ExceptHandler + push fs:[0] + mov recv_esp,esp + mov fs:[0],esp + push esi + push edx + call TextHook::Send + test eax,eax + jz seh_recover + mov ecx,SafeExit + mov [esp + 0x8], ecx // change exit point +seh_recover: + pop dword ptr fs:[0] + pop ecx + retn + } +} +*/ diff --git a/vnr/vnrhook/src/hijack/texthook.h b/vnr/vnrhook/src/hijack/texthook.h new file mode 100644 index 0000000..8b25948 --- /dev/null +++ b/vnr/vnrhook/src/hijack/texthook.h @@ -0,0 +1,98 @@ +#pragma once + +// texthook.h +// 8/24/2013 jichi +// Branch: IHF_DLL/IHF_CLIENT.h, rev 133 +// +// 8/24/2013 TODO: +// - Clean up this file +// - Reduce global variables. Use namespaces or singleton classes instead. + +#include "src/tree/avl.h" +#include "include/types.h" +#include + +// jichi 12/25/2013: Header in each message sent to vnrsrv +// There are totally three elements +// - 0x0 dwAddr hook address +// - 0x4 dwRetn return address +// - 0x8 dwSplit split value +#define HEADER_SIZE 0xc + +extern int current_hook; +extern WCHAR dll_mutex[]; +//extern WCHAR dll_name[]; +extern DWORD trigger; +//extern DWORD current_process_id; + +// jichi 6/3/2014: Get memory range of the current module +extern DWORD processStartAddress, + processStopAddress; + +template +class AVLTree; +struct FunctionInfo { + DWORD addr; + DWORD module; + DWORD size; + LPWSTR name; +}; +struct SCMP; +struct SCPY; +struct SLEN; +extern AVLTree *tree; + +void InitFilterTable(); + +// jichi 9/25/2013: This class will be used by NtMapViewOfSectionfor +// interprocedure communication, where constructor/destructor will NOT work. +class TextHook : public Hook +{ + int UnsafeInsertHookCode(); + DWORD UnsafeSend(DWORD dwDataBase, DWORD dwRetn); +public: + int InsertHook(); + int InsertHookCode(); + int InitHook(const HookParam &hp, LPCSTR name = 0, WORD set_flag = 0); + int InitHook(LPVOID addr, DWORD data, DWORD data_ind, + DWORD split_off, DWORD split_ind, WORD type, DWORD len_off = 0); + DWORD Send(DWORD dwDataBase, DWORD dwRetn); + int RecoverHook(); + int RemoveHook(); + int ClearHook(); + int ModifyHook(const HookParam&); + int SetHookName(LPCSTR name); + int GetLength(DWORD base, DWORD in); // jichi 12/25/2013: Return 0 if failed + void CoolDown(); // jichi 9/28/2013: flush instruction cache on wine +}; + +extern TextHook *hookman, + *current_available; + +//void InitDefaultHook(); + +struct FilterRange { DWORD lower, upper; }; +extern FilterRange *filter; + +extern bool running, + live; + +extern HANDLE hPipe, + hmMutex; + +DWORD WINAPI WaitForPipe(LPVOID lpThreadParameter); +DWORD WINAPI CommandPipe(LPVOID lpThreadParameter); + +//void RequestRefreshProfile(); + +//typedef DWORD (*InsertHookFun)(DWORD); +//typedef DWORD (*IdentifyEngineFun)(); +//typedef DWORD (*InsertDynamicHookFun)(LPVOID addr, DWORD frame, DWORD stack); +//extern IdentifyEngineFun IdentifyEngine; +//extern InsertDynamicHookFun InsertDynamicHook; + +// jichi 9/28/2013: Protect pipeline in wine +void CliLockPipe(); +void CliUnlockPipe(); + +// EOF diff --git a/vnr/vnrhook/src/main.cc b/vnr/vnrhook/src/main.cc new file mode 100644 index 0000000..ffd51b1 --- /dev/null +++ b/vnr/vnrhook/src/main.cc @@ -0,0 +1,418 @@ +// main.cc +// 8/24/2013 jichi +// Branch: ITH_DLL/main.cpp, rev 128 +// 8/24/2013 TODO: Clean up this file + +#ifdef _MSC_VER +# pragma warning (disable:4100) // C4100: unreference formal parameter +//# pragma warning (disable:4733) // C4733: Inline asm assigning to 'FS:0' : handler not registered as safe handler +#endif // _MSC_VER + +#include "src/main.h" +#include "src/tree/avl.h" +#include "src/engine/match.h" +#include "src/hijack/texthook.h" +#include "src/util/growl.h" +#include "src/except.h" +#include "include/const.h" +#include "include/defs.h" +#include "ithsys/ithsys.h" +#include "ccutil/ccmacro.h" +#include // for swprintf +//#include "ntinspect/ntinspect.h" +//#include "winseh/winseh.h" +//#include +//#include "md5.h" +//#include +//#include + +// Global variables + +// jichi 6/3/2014: memory range of the current module +DWORD processStartAddress, + processStopAddress; + +namespace { // unnamed +wchar_t processName[MAX_PATH]; + +inline void GetProcessName(wchar_t *name) +{ + //assert(name); + PLDR_DATA_TABLE_ENTRY it; + __asm + { + mov eax,fs:[0x30] + mov eax,[eax+0xc] + mov eax,[eax+0xc] + mov it,eax + } + wcscpy(name, it->BaseDllName.Buffer); +} +} // unmaed namespace + +enum { HOOK_BUFFER_SIZE = MAX_HOOK * sizeof(TextHook) }; +//#define MAX_HOOK (HOOK_BUFFER_SIZE/sizeof(TextHook)) +DWORD hook_buff_len = HOOK_BUFFER_SIZE; + +namespace { FilterRange _filter[IHF_FILTER_CAPACITY]; } +FilterRange *filter = _filter; + +WCHAR hm_section[0x100]; +HINSTANCE hDLL; +HANDLE hSection; +bool running, + live = false; +int current_hook = 0, + user_hook_count = 0; +DWORD trigger = 0; +HANDLE + hFile, + hMutex, + hmMutex; +//DWORD current_process_id; +extern DWORD enter_count; +//extern LPWSTR current_dir; +extern DWORD engine_type; +extern DWORD module_base; +AVLTree *tree; + +namespace { // unnamed + +void AddModule(DWORD hModule, DWORD size, LPWSTR name) +{ + FunctionInfo info = {0, hModule, size, name}; + IMAGE_DOS_HEADER *DosHdr = (IMAGE_DOS_HEADER *)hModule; + if (IMAGE_DOS_SIGNATURE == DosHdr->e_magic) { + DWORD dwReadAddr = hModule + DosHdr->e_lfanew; + IMAGE_NT_HEADERS *NtHdr = (IMAGE_NT_HEADERS *)dwReadAddr; + if (IMAGE_NT_SIGNATURE == NtHdr->Signature) { + DWORD dwExportAddr = NtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (dwExportAddr == 0) + return; + dwExportAddr += hModule; + IMAGE_EXPORT_DIRECTORY *ExtDir = (IMAGE_EXPORT_DIRECTORY*)dwExportAddr; + dwExportAddr = hModule+ExtDir->AddressOfNames; + for (UINT uj = 0; uj < ExtDir->NumberOfNames; uj++) { + DWORD dwFuncName = *(DWORD *)dwExportAddr; + char *pcBuffer = (char *)(hModule + dwFuncName); + char *pcFuncPtr = (char *)(hModule + (DWORD)ExtDir->AddressOfNameOrdinals+(uj * sizeof(WORD))); + WORD word = *(WORD *)pcFuncPtr; + pcFuncPtr = (char *)(hModule + (DWORD)ExtDir->AddressOfFunctions+(word * sizeof(DWORD))); + info.addr = hModule + *(DWORD *)pcFuncPtr; + ::tree->Insert(pcBuffer, info); + dwExportAddr += sizeof(DWORD); + } + } + } +} + +void AddAllModules() +{ + // jichi 9/26/2013: AVLTree is already zero + PPEB ppeb; + __asm { + mov eax, fs:[0x30] + mov ppeb, eax + } + DWORD temp = *(DWORD *)(&ppeb->Ldr->InLoadOrderModuleList); + PLDR_DATA_TABLE_ENTRY it = (PLDR_DATA_TABLE_ENTRY)temp; + while (it->SizeOfImage) { + AddModule((DWORD)it->DllBase, it->SizeOfImage, it->BaseDllName.Buffer); + it = (PLDR_DATA_TABLE_ENTRY)it->InLoadOrderModuleList.Flink; + if (*(DWORD *)it == temp) + break; + } +} + +void RequestRefreshProfile() +{ + if (::live) { + BYTE buffer[0x80] = {}; // 11/14/2013: reset to zero. Shouldn't it be 0x8 instead of 0x80? + *(DWORD *)buffer = -1; + *(DWORD *)(buffer + 4) = 1; + *(DWORD *)(buffer + 8) = 0; + IO_STATUS_BLOCK ios; + CliLockPipe(); + NtWriteFile(hPipe, 0, 0, 0, &ios, buffer, HEADER_SIZE, 0, 0); + CliUnlockPipe(); + } +} + +} // unnamed namespace + +DWORD GetFunctionAddr(const char *name, DWORD *addr, DWORD *base, DWORD *size, LPWSTR *base_name) +{ + TreeNode *node = ::tree->Search(name); + if (node) { + if (addr) *addr = node->data.addr; + if (base) *base = node->data.module; + if (size) *size = node->data.size; + if (base_name) *base_name = node->data.name; + return TRUE; + } + else + return FALSE; +} + +BOOL WINAPI DllMain(HINSTANCE hModule, DWORD fdwReason, LPVOID lpReserved) +{ + static HANDLE hSendThread, + hCmdThread; + + CC_UNUSED(lpReserved); + + //static WCHAR dll_exist[] = L"ITH_DLL_RUNNING"; + static WCHAR dll_exist[] = ITH_CLIENT_MUTEX; + static HANDLE hDllExist; + + // jichi 9/23/2013: wine deficenciy on mapping sections + // Whe set to false, do not map sections. + //static bool ith_has_section = true; + + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + { + static bool attached_ = false; + if (attached_) // already attached + return TRUE; + attached_ = true; + + LdrDisableThreadCalloutsForDll(hModule); + + //IthBreak(); + ::module_base = (DWORD)hModule; + + //if (!IthInitSystemService()) { + // GROWL_WARN(L"Initialization failed.\nAre you running game on a network drive?"); + // return FALSE; + //} + // No longer checking if SystemService fails, which could happen on non-Japanese OS + IthInitSystemService(); + + swprintf(hm_section, ITH_SECTION_ L"%d", current_process_id); + + // jichi 9/25/2013: Interprocedural communication with vnrsrv. + hSection = IthCreateSection(hm_section, HOOK_SECTION_SIZE, PAGE_EXECUTE_READWRITE); + ::hookman = nullptr; + NtMapViewOfSection(hSection, NtCurrentProcess(), + (LPVOID *)&::hookman, 0, hook_buff_len, 0, &hook_buff_len, ViewUnmap, 0, + PAGE_EXECUTE_READWRITE); + //PAGE_EXECUTE_READWRITE); + + GetProcessName(::processName); + FillRange(::processName, &::processStartAddress, &::processStopAddress); + //NtInspect::getProcessMemoryRange(&::processStartAddress, &::processStopAddress); + + //if (!::hookman) { + // ith_has_section = false; + // ::hookman = new TextHook[MAX_HOOK]; + // memset(::hookman, 0, MAX_HOOK * sizeof(TextHook)); + //} + + { + wchar_t hm_mutex[0x100]; + swprintf(hm_mutex, ITH_HOOKMAN_MUTEX_ L"%d", current_process_id); + ::hmMutex = IthCreateMutex(hm_mutex, FALSE); + } + { + wchar_t dll_mutex[0x100]; + swprintf(dll_mutex, ITH_PROCESS_MUTEX_ L"%d", current_process_id); + DWORD exists; + ::hMutex = IthCreateMutex(dll_mutex, TRUE, &exists); // jichi 9/18/2013: own is true, make sure the injected dll is singleton + if (exists) + return FALSE; + } + + hDllExist = IthCreateMutex(dll_exist, 0); + hDLL = hModule; + ::running = true; + ::current_available = ::hookman; + ::tree = new AVLTree; + AddAllModules(); + InitFilterTable(); + + hSendThread = IthCreateThread(WaitForPipe, 0); + hCmdThread = IthCreateThread(CommandPipe, 0); + } break; + case DLL_PROCESS_DETACH: + { + static bool detached_ = false; + if (detached_) // already detached + return TRUE; + detached_ = true; + + // jichi 10/2/2103: Cannot use __try in functions that require object unwinding + //ITH_TRY { + ::running = false; + ::live = false; + + const LONGLONG timeout = -50000000; // in nanoseconds = 5 seconds + + Engine::terminate(); + + if (hSendThread) { + NtWaitForSingleObject(hSendThread, 0, (PLARGE_INTEGER)&timeout); + NtClose(hSendThread); + } + + if (hCmdThread) { + NtWaitForSingleObject(hCmdThread, 0, (PLARGE_INTEGER)&timeout); + NtClose(hCmdThread); + } + + for (TextHook *man = ::hookman; man->RemoveHook(); man++); + //LARGE_INTEGER lint = {-10000, -1}; + while (::enter_count) + IthSleep(1); // jichi 9/28/2013: sleep for 1 ms + //NtDelayExecution(0, &lint); + for (TextHook *man = ::hookman; man < ::hookman + MAX_HOOK; man++) + man->ClearHook(); + //if (ith_has_section) + NtUnmapViewOfSection(NtCurrentProcess(), ::hookman); + //else + // delete[] ::hookman; + NtClose(hSection); + NtClose(hMutex); + + delete ::tree; + IthCloseSystemService(); + NtClose(hmMutex); + NtClose(hDllExist); + //} ITH_EXCEPT {} + } break; + } + return TRUE; +} + +//extern "C" { +DWORD NewHook(const HookParam &hp, LPCSTR name, DWORD flag) +{ + CHAR str[128]; + int current = ::current_available - ::hookman; + if (current < MAX_HOOK) { + //flag &= 0xffff; + //if ((flag & HOOK_AUXILIARY) == 0) + flag |= HOOK_ADDITIONAL; + if (name == NULL || name[0] == '\0') + { + sprintf(str, "UserHook%d", user_hook_count++); + } + else + { + strcpy(str, name); + } + + ConsoleOutput("vnrcli:NewHook: try inserting hook"); + + // jichi 7/13/2014: This function would raise when too many hooks added + ::hookman[current].InitHook(hp, str, flag & 0xffff); + + if (::hookman[current].InsertHook() == 0) { + ConsoleOutput("vnrcli:NewHook: hook inserted"); + //ConsoleOutputW(name); + //swprintf(str,L"Insert address 0x%.8X.", hookman[current].Address()); + RequestRefreshProfile(); + } else + ConsoleOutput("vnrcli:NewHook:WARNING: failed to insert hook"); + } + return 0; +} +DWORD RemoveHook(DWORD addr) +{ + for (int i = 0; i < MAX_HOOK; i++) + if (::hookman[i].Address ()== addr) { + ::hookman[i].ClearHook(); + return 0; + } + return 0; +} + +DWORD SwitchTrigger(DWORD t) +{ + trigger = t; + return 0; +} + +//} // extern "C" + + +namespace { // unnamed + +BOOL SafeFillRange(LPCWSTR dll, DWORD *lower, DWORD *upper) +{ + BOOL ret = FALSE; + ITH_WITH_SEH(ret = FillRange(dll, lower, upper)); + return ret; +} + +} // unnamed namespace + +// jichi 12/13/2013 +// Use listdlls from SystemInternals +void InitFilterTable() +{ + LPCWSTR l[] = { IHF_FILTER_DLL_LIST }; + enum { capacity = sizeof(l)/sizeof(*l) }; + + size_t count = 0; + //for (auto p : l) + for (size_t i = 0; i < capacity; i++) + if (SafeFillRange(l[i], &::filter[count].lower, &::filter[count].upper)) + count++; +} + +// EOF +/* + +static DWORD recv_esp, recv_addr; +static CONTEXT recover_context; +static __declspec(naked) void MySEH() +{ + __asm{ + mov eax, [esp+0xC] + mov edi,eax + mov ecx,0xB3 + mov esi, offset recover_context + rep movs + mov ecx, [recv_esp] + mov [eax+0xC4],ecx + mov edx, [recv_addr] + mov [eax+0xB8],edx + xor eax,eax + retn + } +} + +EXCEPTION_DISPOSITION ExceptHandler( + EXCEPTION_RECORD *ExceptionRecord, + void * EstablisherFrame, + CONTEXT *ContextRecord, + void * DispatcherContext ) +{ + ContextRecord->Esp=recv_esp; + ContextRecord->Eip=recv_addr; + return ExceptionContinueExecution; +} +int GuardRange(LPWSTR module, DWORD *a, DWORD *b) +{ + int flag=0; + __asm + { + mov eax,seh_recover + mov recv_addr,eax + push ExceptHandler + push fs:[0] + mov recv_esp,esp + mov fs:[0],esp + } + flag = FillRange(module, a, b); + __asm + { +seh_recover: + mov eax,[esp] + mov fs:[0],eax + add esp,8 + } + return flag; +} +*/ diff --git a/vnr/vnrhook/src/main.h b/vnr/vnrhook/src/main.h new file mode 100644 index 0000000..fa5267e --- /dev/null +++ b/vnr/vnrhook/src/main.h @@ -0,0 +1,25 @@ +#pragma once + +// main.h +// 8/23/2013 jichi +// Branch: ITH/IHF_DLL.h, rev 66 + +#include "include/const.h" +#include "include/types.h" + +void ConsoleOutput(LPCSTR text); // jichi 12/25/2013: Used to return length of sent text +DWORD NotifyHookInsert(DWORD addr); +DWORD NewHook(const HookParam &hp, LPCSTR name, DWORD flag = HOOK_ENGINE); +DWORD RemoveHook(DWORD addr); +DWORD SwitchTrigger(DWORD on); +DWORD GetFunctionAddr(const char *name, DWORD *addr, DWORD *base, DWORD *size, LPWSTR *base_name); + +// 10/14/2014 jichi: disable GDI hooks +void EnableGDIHooks(); +void EnableGDIPlusHooks(); +void DisableGDIHooks(); +void DisableGDIPlusHooks(); +bool GDIHooksEnabled(); +bool GDIPlusHooksEnabled(); + +// EOF diff --git a/vnr/vnrhook/src/pipe.cc b/vnr/vnrhook/src/pipe.cc new file mode 100644 index 0000000..59a2a59 --- /dev/null +++ b/vnr/vnrhook/src/pipe.cc @@ -0,0 +1,353 @@ +// pipe.cc +// 8/24/2013 jichi +// Branch: ITH_DLL/pipe.cpp, rev 66 +// 8/24/2013 TODO: Clean up this file + +#ifdef _MSC_VER +# pragma warning (disable:4100) // C4100: unreference formal parameter +#endif // _MSC_VER + +#include "src/hijack/texthook.h" +#include "src/engine/match.h" +#include "src/util/util.h" +#include "src/main.h" +#include "include/defs.h" +//#include "src/util/growl.h" +#include "ithsys/ithsys.h" +#include "ccutil/ccmacro.h" +#include // for swprintf + +//#include +//#include +WCHAR detach_mutex[0x20]; +//WCHAR write_event[0x20]; +//WCHAR engine_event[0x20]; + +//WCHAR recv_pipe[] = L"\\??\\pipe\\ITH_PIPE"; +//WCHAR command[] = L"\\??\\pipe\\ITH_COMMAND"; +wchar_t recv_pipe[] = ITH_TEXT_PIPE; +wchar_t command[] = ITH_COMMAND_PIPE; + +LARGE_INTEGER wait_time = {-100*10000, -1}; +LARGE_INTEGER sleep_time = {-20*10000, -1}; + +DWORD engine_type; +DWORD module_base; + +HANDLE hPipe, + hCommand, + hDetach; //,hLose; +//InsertHookFun InsertHook; +//IdentifyEngineFun IdentifyEngine; +//InsertDynamicHookFun InsertDynamicHook; + +// jichi 9/28/2013: protect pipe on wine +// Put the definition in this file so that it might be inlined +void CliUnlockPipe() +{ + if (IthIsWine()) + IthReleaseMutex(::hmMutex); +} + +void CliLockPipe() +{ + if (IthIsWine()) { + const LONGLONG timeout = -50000000; // in nanoseconds = 5 seconds + NtWaitForSingleObject(hmMutex, 0, (PLARGE_INTEGER)&timeout); + } +} + +HANDLE IthOpenPipe(LPWSTR name, ACCESS_MASK direction) +{ + UNICODE_STRING us; + RtlInitUnicodeString(&us,name); + SECURITY_DESCRIPTOR sd = {1}; + OBJECT_ATTRIBUTES oa = {sizeof(oa), 0, &us, OBJ_CASE_INSENSITIVE, &sd, 0}; + HANDLE hFile; + IO_STATUS_BLOCK isb; + if (NT_SUCCESS(NtCreateFile(&hFile, direction, &oa, &isb, 0, 0, FILE_SHARE_READ, FILE_OPEN, 0, 0, 0))) + return hFile; + else + return INVALID_HANDLE_VALUE; +} + +DWORD WINAPI WaitForPipe(LPVOID lpThreadParameter) // Dynamically detect ITH main module status. +{ + CC_UNUSED(lpThreadParameter); + // jichi 7/2/2015:This must be consistent with the struct declared in vnrhost/pipe.cc + struct { + DWORD pid; + DWORD module; + TextHook *man; + //DWORD engine; + } u; + + //swprintf(engine_event,L"ITH_ENGINE_%d",current_process_id); + swprintf(::detach_mutex, ITH_DETACH_MUTEX_ L"%d", current_process_id); + //swprintf(lose_event,L"ITH_LOSEPIPE_%d",current_process_id); + //hEngine=IthCreateEvent(engine_event); + //NtWaitForSingleObject(hEngine,0,0); + //NtClose(hEngine); + + //while (!engine_registered) + // NtDelayExecution(0, &wait_time); + + //LoadEngine(L"ITH_Engine.dll"); + u.module = module_base; + u.pid = current_process_id; + u.man = hookman; + //u.engine = engine_base; // jichi 10/19/2014: disable the second dll + HANDLE hPipeExist = IthOpenEvent(ITH_PIPEEXISTS_EVENT); + IO_STATUS_BLOCK ios; + //hLose=IthCreateEvent(lose_event,0,0); + if (hPipeExist != INVALID_HANDLE_VALUE) + while (::running) { + ::hPipe = INVALID_HANDLE_VALUE; + hCommand = INVALID_HANDLE_VALUE; + while (NtWaitForSingleObject(hPipeExist, 0, &wait_time) == WAIT_TIMEOUT) + if (!::running) + goto _release; + HANDLE hMutex = IthCreateMutex(ITH_GRANTPIPE_MUTEX, 0); + NtWaitForSingleObject(hMutex, 0, 0); + while (::hPipe == INVALID_HANDLE_VALUE|| + hCommand == INVALID_HANDLE_VALUE) { + NtDelayExecution(0, &sleep_time); + if (::hPipe == INVALID_HANDLE_VALUE) + ::hPipe = IthOpenPipe(recv_pipe, GENERIC_WRITE); + if (hCommand == INVALID_HANDLE_VALUE) + hCommand = IthOpenPipe(command, GENERIC_READ); + } + //NtClearEvent(hLose); + CliLockPipe(); + NtWriteFile(::hPipe, 0, 0, 0, &ios, &u, sizeof(u), 0, 0); + CliUnlockPipe(); + for (int i = 0, count = 0; count < ::current_hook; i++) + if (hookman[i].RecoverHook()) // jichi 9/27/2013: This is the place where built-in hooks like TextOutA are inserted + count++; + //ConsoleOutput(dll_name); + //OutputDWORD(tree->Count()); + NtReleaseMutant(hMutex,0); + NtClose(hMutex); + + + ::live = true; + + // jichi 7/17/2014: Always hijack by default or I have to wait for it is ready + Engine::hijack(); + ConsoleOutput("vnrcli:WaitForPipe: pipe connected"); + + ::hDetach = IthCreateMutex(::detach_mutex,1); + while (::running && NtWaitForSingleObject(hPipeExist, 0, &sleep_time) == WAIT_OBJECT_0) + NtDelayExecution(0, &sleep_time); + ::live = false; + + for (int i = 0, count = 0; count < ::current_hook; i++) + if (hookman[i].RemoveHook()) + count++; + if (!::running) { + IthCoolDown(); // jichi 9/28/2013: Use cooldown instead of lock pipe to prevent from hanging on exit + //CliLockPipe(); + //NtWriteFile(::hPipe, 0, 0, 0, &ios, man, 4, 0, 0); + NtWriteFile(::hPipe, 0, 0, 0, &ios, hookman, 4, 0, 0); + //CliUnlockPipe(); + IthReleaseMutex(::hDetach); + } + NtClose(::hDetach); + NtClose(::hPipe); + } +_release: + //NtClose(hLose); + NtClose(hPipeExist); + return 0; +} + +DWORD WINAPI CommandPipe(LPVOID lpThreadParameter) +{ + CC_UNUSED(lpThreadParameter); + DWORD command; + BYTE buff[0x400] = {}; + HANDLE hPipeExist; + hPipeExist = IthOpenEvent(ITH_PIPEEXISTS_EVENT); + IO_STATUS_BLOCK ios={}; + + if (hPipeExist != INVALID_HANDLE_VALUE) + while (::running) { + while (!::live) { + if (!::running) + goto _detach; + NtDelayExecution(0, &sleep_time); + } + // jichi 9/27/2013: Why 0x200 not 0x400? wchar_t? + switch (NtReadFile(hCommand, 0, 0, 0, &ios, buff, 0x200, 0, 0)) { + case STATUS_PIPE_BROKEN: + case STATUS_PIPE_DISCONNECTED: + NtClearEvent(hPipeExist); + continue; + case STATUS_PENDING: + NtWaitForSingleObject(hCommand, 0, 0); + switch (ios.Status) { + case STATUS_PIPE_BROKEN: + case STATUS_PIPE_DISCONNECTED: + NtClearEvent(hPipeExist); + continue; + case 0: break; + default: + if (NtWaitForSingleObject(::hDetach, 0, &wait_time) == WAIT_OBJECT_0) + goto _detach; + } + } + if (ios.uInformation && ::live) { + command = *(DWORD *)buff; + switch(command) { + case HOST_COMMAND_NEW_HOOK: + //IthBreak(); + buff[ios.uInformation] = 0; + //buff[ios.uInformation + 1] = 0; + NewHook(*(HookParam *)(buff + 4), (LPSTR)(buff + 4 + sizeof(HookParam)), 0); + break; + case HOST_COMMAND_REMOVE_HOOK: + { + DWORD rm_addr = *(DWORD *)(buff+4); + HANDLE hRemoved = IthOpenEvent(ITH_REMOVEHOOK_EVENT); + + TextHook *in = hookman; + for (int i = 0; i < current_hook; in++) { + if (in->Address()) i++; + if (in->Address() == rm_addr) break; + } + if (in->Address()) + in->ClearHook(); + IthSetEvent(hRemoved); + NtClose(hRemoved); + } break; +#if 0 // Temporarily disabled as these operations are not used by VNR + case HOST_COMMAND_MODIFY_HOOK: + { + DWORD rm_addr = *(DWORD *)(buff + 4); + HANDLE hModify = IthOpenEvent(ITH_MODIFYHOOK_EVENT); + TextHook *in = hookman; + for (int i = 0; i < current_hook; in++) { + if (in->Address()) + i++; + if (in->Address() == rm_addr) + break; + } + if (in->Address()) + in->ModifyHook(*(HookParam *)(buff + 4)); + IthSetEvent(hModify); + NtClose(hModify); + } break; + case HOST_COMMAND_HIJACK_PROCESS: + Engine::hijack(); + break; +#endif // 0 + case HOST_COMMAND_DETACH: + ::running = false; + ::live = false; + goto _detach; + } + } + } +_detach: + NtClose(hPipeExist); + NtClose(hCommand); + Util::unloadCurrentModule(); // jichi: this is not always needed + return 0; +} +//extern "C" { +void ConsoleOutput(LPCSTR text) +{ // jichi 12/25/2013: Rewrite the implementation + if (!live || !text) + return; + enum { buf_size = 0x50 }; + BYTE buf[buf_size]; // buffer is needed to append the message header + size_t text_size = strlen(text) + 1; + size_t data_size = text_size + 8; + + BYTE *data = (data_size <= buf_size) ? buf : new BYTE[data_size]; + *(DWORD *)data = HOST_NOTIFICATION; //cmd + *(DWORD *)(data + 4) = HOST_NOTIFICATION_TEXT; //console + memcpy(data + 8, text, text_size); + + IO_STATUS_BLOCK ios; + NtWriteFile(hPipe, 0, 0, 0, &ios, data, data_size, 0, 0); + if (data != buf) + delete[] data; +} + //if (str) { + // int t, len, sum; + // BYTE buffer[0x80]; + // BYTE *buff; + // len = wcslen(str) << 1; + // t = swprintf((LPWSTR)(buffer + 8),L"%d: ",current_process_id) << 1; + // sum = len + t + 8; + // if (sum > 0x80) { + // buff = new BYTE[sum]; + // memset(buff, 0, sum); // jichi 9/25/2013: zero memory + // memcpy(buff + 8, buffer + 8, t); + // } + // else + // buff = buffer; + // *(DWORD *)buff = HOST_NOTIFICATION; //cmd + // *(DWORD *)(buff + 4) = HOST_NOTIFICATION_TEXT; //console + // memcpy(buff + t + 8, str, len); + // IO_STATUS_BLOCK ios; + // NtWriteFile(hPipe,0,0,0,&ios,buff,sum,0,0); + // if (buff != buffer) + // delete[] buff; + // return len; + //} + +//DWORD IOutputDWORD(DWORD d) +//{ +// WCHAR str[0x10]; +// swprintf(str,L"%.8X",d); +// ConsoleOutput(str); +// return 0; +//} +//DWORD IOutputRegister(DWORD *base) +//{ +// WCHAR str[0x40]; +// swprintf(str,L"EAX:%.8X",base[0]); +// ConsoleOutput(str); +// swprintf(str,L"ECX:%.8X",base[-1]); +// ConsoleOutput(str); +// swprintf(str,L"EDX:%.8X",base[-2]); +// ConsoleOutput(str); +// swprintf(str,L"EBX:%.8X",base[-3]); +// ConsoleOutput(str); +// swprintf(str,L"ESP:%.8X",base[-4]); +// ConsoleOutput(str); +// swprintf(str,L"EBP:%.8X",base[-5]); +// ConsoleOutput(str); +// swprintf(str,L"ESI:%.8X",base[-6]); +// ConsoleOutput(str); +// swprintf(str,L"EDI:%.8X",base[-7]); +// ConsoleOutput(str); +// return 0; +//} +//DWORD IRegisterEngineModule(DWORD idEngine, DWORD dnHook) +//{ +// ::IdentifyEngine = (IdentifyEngineFun)idEngine; +// ::InsertDynamicHook = (InsertDynamicHookFun)dnHook; +// ::engine_registered = true; +// return 0; +//} +DWORD NotifyHookInsert(DWORD addr) +{ + if (live) { + BYTE buffer[0x10]; + *(DWORD *)buffer = HOST_NOTIFICATION; + *(DWORD *)(buffer + 4) = HOST_NOTIFICATION_NEWHOOK; + *(DWORD *)(buffer + 8) = addr; + *(DWORD *)(buffer + 0xc) = 0; + IO_STATUS_BLOCK ios; + CliLockPipe(); + NtWriteFile(hPipe,0,0,0,&ios,buffer,0x10,0,0); + CliUnlockPipe(); + } + return 0; +} +//} // extern "C" + +// EOF diff --git a/vnr/vnrhook/src/tree/avl.h b/vnr/vnrhook/src/tree/avl.h new file mode 100644 index 0000000..8b27c57 --- /dev/null +++ b/vnr/vnrhook/src/tree/avl.h @@ -0,0 +1,589 @@ +#pragma once + +// avl.h +// 8/23/2013 jichi +// Branch: ITH/AVL.h, rev 133 +// 8/24/2013 TODO: Clean up this file +#include + +enum { STACK_SIZE = 32 }; + +//#ifndef ITH_STACK +//#define ITH_STACK + +template +class MyStack +{ + int index; + T s[stack_size]; + +public: + MyStack(): index(0) + { ::memset(s, 0, sizeof(s)); } // jichi 9/21/2013: assume T is atomic type + + T &back() { return s[index-1]; } + int size() { return index; } + + void push_back(const T &e) + { + if (index < stack_size) + s[index++]=e; + } + + void pop_back() { index--; } + + T &operator[](int i) { return s[i]; } +}; +//#endif // ITH_STACK + +// jichi 9/22/2013: T must be a pointer type which can be deleted +template +struct TreeNode +{ + //typedef TreeNode Self; + TreeNode() : + Left(nullptr), Right(nullptr), Parent(nullptr) + , rank(1) + , factor('\0'), reserve('\0') + //, key() + //, data() + { + ::memset(&key, 0, sizeof(key)); // jichi 9/26/2013: zero memory + ::memset(&data, 0, sizeof(data)); // jichi 9/26/2013: zero memory + } + + TreeNode(const T &k, const D &d) : + Left(nullptr), Right(nullptr), Parent(nullptr) + , rank(1) + , factor('\0'), reserve('\0') // jichi 9/21/2013: zero reserve + , key(k) + , data(d) + {} + + TreeNode *Successor() + { + TreeNode *Node, + *ParentNode; + Node = Right; + if (!Node) { + Node = this; + for (;;) { + ParentNode = Node->Parent; + if (!ParentNode) + return nullptr; + if (ParentNode->Left == Node) + break; + Node = ParentNode; + } + return ParentNode; + } + else + while (Node->Left) + Node = Node->Left; + return Node; + } + TreeNode *Predecessor() + { + TreeNode *Node, + *ParentNode; + Node = Left; + if (!Node) { + Node = this; + for(;;) { + ParentNode = Node->Parent; + if (!ParentNode) + return nullptr; + if (ParentNode->Right == Node) + break; + Node = ParentNode; + } + return ParentNode; + } + else + while (Node->Right) + Node = Node->Right; + return Node; + } + int height() + { + if (!this) // jichi 9/26/2013: what?! + return 0; + int l = Left->height(), + r = Right->height(), + f = factor; + if (l - r + f != 0) + __debugbreak(); + f = l > r ? l : r; + return f + 1; + } + TreeNode *Left, + *Right, + *Parent; + unsigned short rank; + char factor, + reserve; + T key; + D data; +}; + +template +struct NodePath +{ + NodePath() { ::memset(this, 0, sizeof(NodePath)); } // jichi 11/30/2013: This is the original code in ITH + NodePath(TreeNode *n, int f): Node(n), fact(f) {} + TreeNode *Node; + union { char factor; int fact; }; +}; + +template +class AVLTree +{ + fComp fCmp; + fCopy fCpy; + fLength fLen; + +protected: + TreeNode head; + +public: + // - Construction - + AVLTree() {} + + virtual ~AVLTree() { DeleteAll(); } + + // - Properties - + + TreeNode *TreeRoot() const { return head.Left; } + + // - Actions - + + void DeleteAll() + { + while (head.Left) + DeleteRoot(); + } + + TreeNode *Insert(const T *key, const D &data) + { + if (head.Left) { + MyStack *,STACK_SIZE> path; + TreeNode *DownNode, *ParentNode, *BalanceNode, *TryNode, *NewNode; //P,T,S,Q + ParentNode = &head; + path.push_back(ParentNode); + char factor,f; + BalanceNode = DownNode = head.Left; + for (;;) { //The first part of AVL tree insert. Just do as binary tree insert routine and record some nodes. + factor = fCmp(key,DownNode->key); + if (factor == 0) + return DownNode; //Duplicate key. Return and do nothing. + TryNode = _FactorLink(DownNode, factor); + if (factor == -1) + path.push_back(DownNode); + if (TryNode) { //DownNode has a child. + if (TryNode->factor != 0) { //Keep track of unbalance node and its parent. + ParentNode = DownNode; + BalanceNode = TryNode; + } + DownNode = TryNode; + } + else + break; //Finished binary tree search; + } + while (path.size()) { + path.back()->rank++; + path.pop_back(); + } + size_t sz = fLen(key) + 1; + T *new_key = new T[sz]; + ::memset(new_key, 0, sz * sizeof(T)); // jichi 9/26/2013: Zero memory + fCpy(new_key, key); + TryNode = new TreeNode(new_key, data); + _FactorLink(DownNode, factor) = TryNode; + TryNode->Parent = DownNode; + NewNode = TryNode; + //Finished binary tree insert. Next to do is to modify balance factors between + //BalanceNode and the new node. + TreeNode *ModifyNode; + factor = fCmp(key, BalanceNode->key); + //factor=keykey ? factor=-1:1; //Determine the balance factor at BalanceNode. + ModifyNode = DownNode = _FactorLink(BalanceNode,factor); + //ModifyNode will be the 1st child. + //DownNode will travel from here to the recent inserted node (TryNode). + while (DownNode != TryNode) { //Check if we reach the bottom. + f = fCmp(key,DownNode->key); + //f=_FactorCompare(key,DownNode->key); + DownNode->factor = f; + DownNode = _FactorLink(DownNode, f);//Modify balance factor and travels down. + } + //Finshed modifying balance factor. + //Next to do is check the tree if it's unbalance and recover balance. + if (BalanceNode->factor == 0) { //Tree has grown higher. + BalanceNode->factor = factor; + _IncreaseHeight(); //Modify balance factor and increase the height. + return NewNode; + } + if (BalanceNode->factor + factor == 0) { //Tree has gotten more balanced. + BalanceNode->factor = 0; //Set balance factor to 0. + return NewNode; + } + //Tree has gotten out of balance. + if (ModifyNode->factor == factor) //A node and its child has same factor. Single rotation. + DownNode = _SingleRotation(BalanceNode, ModifyNode, factor); + else //A node and its child has converse factor. Double rotation. + DownNode = _DoubleRotation(BalanceNode, ModifyNode, factor); + //Finished the balancing work. Set child field to the root of the new child tree. + if (BalanceNode == ParentNode->Left) + ParentNode->Left = DownNode; + else + ParentNode->Right = DownNode; + return NewNode; + } + else { //root null? + size_t sz = fLen(key) + 1; + T *new_key = new T[sz]; + ::memset(new_key, 0, sz * sizeof(T)); // jichi 9/26/2013: Zero memory + fCpy(new_key, key); + head.Left = new TreeNode(new_key, data); + head.rank++; + _IncreaseHeight(); + return head.Left; + } + } + bool Delete(T *key) + { + NodePath PathNode; + MyStack,STACK_SIZE> path; //Use to record a path to the destination node. + path.push_back(NodePath(&head,-1)); + TreeNode *TryNode,*ChildNode,*BalanceNode,*SuccNode; + TryNode=head.Left; + char factor; + for (;;) { //Search for the + if (TryNode == 0) + return false; //Not found. + factor = fCmp(key, TryNode->key); + if (factor == 0) + break; //Key found, continue to delete. + //factor = _FactorCompare( key, TryNode->key ); + path.push_back(NodePath(TryNode,factor)); + TryNode = _FactorLink(TryNode,factor); //Move to left. + } + SuccNode = TryNode->Right; //Find a successor. + factor = 1; + if (SuccNode == 0) { + SuccNode = TryNode->Left; + factor = -1; + } + path.push_back(NodePath(TryNode,factor)); + while (SuccNode) { + path.push_back(NodePath(SuccNode, -factor)); + SuccNode = _FactorLink(SuccNode,-factor); + } + PathNode = path.back(); + delete[] TryNode->key; // jichi 9/22/2013: key is supposed to be an array + TryNode->key = PathNode.Node->key; //Replace key and data field with the successor or predecessor. + PathNode.Node->key = nullptr; + TryNode->data = PathNode.Node->data; + path.pop_back(); + _FactorLink(path.back().Node,path.back().factor) = _FactorLink(PathNode.Node,-PathNode.factor); + delete PathNode.Node; //Remove the successor from the tree and release memory. + PathNode = path.back(); + for (int i=0; irank--; + for (;;) { //Rebalance the tree along the path back to the root. + if (path.size()==1) { + _DecreaseHeight(); + break; + } + BalanceNode = PathNode.Node; + if (BalanceNode->factor == 0) { // A balance node, just need to adjust the factor. Don't have to recurve since subtree height stays. + BalanceNode->factor=-PathNode.factor; + break; + } + if (BalanceNode->factor == PathNode.factor) { // Node get more balance. Subtree height decrease, need to recurve. + BalanceNode->factor = 0; + path.pop_back(); + PathNode = path.back(); + continue; + } + //Node get out of balance. Here raises 3 cases. + ChildNode = _FactorLink(BalanceNode, -PathNode.factor); + if (ChildNode->factor == 0) { // New case different to insert operation. + TryNode = _SingleRotation2( BalanceNode, ChildNode, BalanceNode->factor ); + path.pop_back(); + PathNode = path.back(); + _FactorLink(PathNode.Node, PathNode.factor) = TryNode; + break; + } + else { + if (ChildNode->factor == BalanceNode->factor) // Analogous to insert operation case 1. + TryNode = _SingleRotation( BalanceNode, ChildNode, BalanceNode->factor ); + else if (ChildNode->factor + BalanceNode->factor == 0) // Analogous to insert operation case 2. + TryNode = _DoubleRotation( BalanceNode, ChildNode, BalanceNode->factor ); + } + path.pop_back(); //Recurse back along the path. + PathNode = path.back(); + _FactorLink(PathNode.Node, PathNode.factor) = TryNode; + } + return true; + } + + D &operator [](T *key) + { return (Insert(key,D())->data); } + + TreeNode *Search(const T *key) + { + TreeNode *Find=head.Left; + char k; + while (Find != 0) {//&&Find->key!=key) + k=fCmp(key, Find->key); + if (k==0) break; + Find = _FactorLink(Find, k); + } + return Find; + } + + TreeNode *SearchIndex(unsigned int rank) + { + unsigned int r = head.rank; + if (rank == -1) + return 0; + if (++rank>=r) + return 0; + TreeNode *n=&head; + while (r!=rank) { + if (rank>r) { + n=n->Right; + rank-=r; + r=n->rank; + } else { + n=n->Left; + r=n->rank; + } + } + return n; + } + + TreeNode *Begin() + { + TreeNode *Node = head.Left; + if (Node) + while (Node->Left) Node = Node->Left; + return Node; + } + + TreeNode *End() + { + TreeNode *Node=head.Left; + if (Node) + while (Node->Right) Node = Node->Right; + return Node; + } + unsigned int Count() const { return head.rank - 1; } + + template + Fn TraverseTree(Fn &f) + { return TraverseTreeNode(head.Left,f); } + +protected: + bool DeleteRoot() + { + NodePath PathNode; + MyStack,STACK_SIZE> path; //Use to record a path to the destination node. + path.push_back(NodePath(&head,-1)); + TreeNode *TryNode,*ChildNode,*BalanceNode,*SuccNode; + TryNode=head.Left; + char factor; + SuccNode=TryNode->Right; //Find a successor. + factor=1; + if (SuccNode==0) + { + SuccNode=TryNode->Left; + factor=-1; + } + path.push_back(NodePath(TryNode,factor)); + while (SuccNode) { + path.push_back(NodePath(SuccNode,-factor)); + SuccNode=_FactorLink(SuccNode,-factor); + } + PathNode=path.back(); + delete[] TryNode->key; // jichi 9/22/2013: key is supposed to be an array + TryNode->key=PathNode.Node->key; //Replace key and data field with the successor. + PathNode.Node->key = nullptr; + TryNode->data=PathNode.Node->data; + path.pop_back(); + _FactorLink(path.back().Node,path.back().factor) = _FactorLink(PathNode.Node,-PathNode.factor); + delete PathNode.Node; //Remove the successor from the tree and release memory. + PathNode=path.back(); + for (int i=0;irank--; + for (;;) { //Rebalance the tree along the path back to the root. + if (path.size() == 1) { + _DecreaseHeight(); + break; + } + + BalanceNode = PathNode.Node; + if (BalanceNode->factor == 0) { // A balance node, just need to adjust the factor. Don't have to recurse since subtree height not changed. + BalanceNode->factor=-PathNode.factor; + break; + } + if (BalanceNode->factor==PathNode.factor) { // Node get more balance. Subtree height decrease, need to recurse. + BalanceNode->factor=0; + path.pop_back(); + PathNode=path.back(); + continue; + } + //Node get out of balance. Here raises 3 cases. + ChildNode = _FactorLink(BalanceNode, -PathNode.factor); + if (ChildNode->factor == 0) { // New case different to insert operation. + TryNode = _SingleRotation2( BalanceNode, ChildNode, BalanceNode->factor ); + path.pop_back(); + PathNode=path.back(); + _FactorLink(PathNode.Node, PathNode.factor) = TryNode; + break; + } else { + if (ChildNode->factor == BalanceNode->factor) // Analogous to insert operation case 1. + TryNode = _SingleRotation( BalanceNode, ChildNode, BalanceNode->factor ); + else if (ChildNode->factor + BalanceNode->factor == 0) // Analogous to insert operation case 2. + TryNode = _DoubleRotation( BalanceNode, ChildNode, BalanceNode->factor ); + } + path.pop_back(); // Recurve back along the path. + PathNode=path.back(); + _FactorLink(PathNode.Node, PathNode.factor) = TryNode; + } + return true; + } + template + Fn TraverseTreeNode(TreeNode *Node, Fn &f) + { + if (Node) { + if (Node->Left) + TraverseTreeNode(Node->Left,f); + f(Node); + if (Node->Right) + TraverseTreeNode(Node->Right,f); + } + return f; + } + TreeNode *_SingleRotation(TreeNode *BalanceNode, TreeNode *ModifyNode, char factor) + { + TreeNode *Node = _FactorLink(ModifyNode, -factor); + _FactorLink(BalanceNode, factor) = Node; + _FactorLink(ModifyNode, -factor) = BalanceNode; + if (Node) + Node->Parent = BalanceNode; + ModifyNode->Parent = BalanceNode->Parent; + BalanceNode->Parent = ModifyNode; + BalanceNode->factor = ModifyNode->factor = 0; //After single rotation, set all factor of 3 node to 0. + if (factor == 1) + ModifyNode->rank += BalanceNode->rank; + else + BalanceNode->rank -= ModifyNode->rank; + return ModifyNode; + } + TreeNode *_SingleRotation2(TreeNode *BalanceNode, TreeNode *ModifyNode, char factor) + { + TreeNode *Node = _FactorLink(ModifyNode, -factor); + _FactorLink(BalanceNode, factor) = Node; + _FactorLink(ModifyNode, -factor) = BalanceNode; + if (Node) Node->Parent = BalanceNode; + ModifyNode->Parent = BalanceNode->Parent; + BalanceNode->Parent = ModifyNode; + ModifyNode->factor = -factor; + if (factor == 1) + ModifyNode->rank+=BalanceNode->rank; + else + BalanceNode->rank-=ModifyNode->rank; + return ModifyNode; + } + TreeNode *_DoubleRotation(TreeNode *BalanceNode, TreeNode *ModifyNode, char factor) + { + TreeNode *DownNode = _FactorLink(ModifyNode, -factor); + TreeNode *Node1, *Node2; + Node1 = _FactorLink(DownNode, factor); + Node2 = _FactorLink(DownNode, -factor); + _FactorLink(ModifyNode, -factor) = Node1; + _FactorLink(DownNode, factor) = ModifyNode; + _FactorLink(BalanceNode, factor) = Node2; + _FactorLink(DownNode, -factor) = BalanceNode; + if (Node1) + Node1->Parent = ModifyNode; + if (Node2) + Node2->Parent = BalanceNode; + DownNode->Parent = BalanceNode->Parent; + BalanceNode->Parent = DownNode; + ModifyNode->Parent = DownNode; + //Set factor according to the result. + if (DownNode->factor == factor) { + BalanceNode->factor = -factor; + ModifyNode->factor = 0; + } else if (DownNode->factor == 0) + BalanceNode->factor = ModifyNode->factor = 0; + else { + BalanceNode->factor = 0; + ModifyNode->factor = factor; + } + DownNode->factor = 0; + if (factor==1) { + ModifyNode->rank -= DownNode->rank; + DownNode->rank += BalanceNode->rank; + } else { + DownNode->rank += ModifyNode->rank; + BalanceNode->rank -= DownNode->rank; + } + return DownNode; + } + + TreeNode* &__fastcall _FactorLink(TreeNode *Node, char factor) + //Private helper method to retrieve child according to factor. + //Return right child if factor>0 and left child otherwise. + { return factor>0? Node->Right : Node->Left; } + + void Check() + { + unsigned int k = (unsigned int)head.Right; + unsigned int t = head.Left->height(); + if (k != t) + __debugbreak(); + } + + void _IncreaseHeight() + { + unsigned int k = (unsigned int)head.Right; + head.Right = (TreeNode*)++k; + } + + void _DecreaseHeight() + { + unsigned int k = (unsigned int)head.Right; + head.Right = (TreeNode*)--k; + } +}; + +struct SCMP +{ + char operator()(const char *s1,const char *s2) + { + int t = _stricmp(s1, s2); + return t == 0 ? 0 : t > 0 ? 1 :-1; + } +}; + +struct SCPY { char *operator()(char *dest, const char *src) { return strcpy(dest, src); } }; +struct SLEN { int operator()(const char *str) { return strlen(str); } }; + +struct WCMP +{ + char operator()(const wchar_t *s1,const wchar_t *s2) + { + int t =_wcsicmp(s1, s2); + return t == 0 ? 0 : t > 0 ? 1 : -1; + } +}; + +struct WCPY { wchar_t *operator()(wchar_t *dest, const wchar_t *src) { return wcscpy(dest,src); } }; +struct WLEN { int operator()(const wchar_t *str) { return wcslen(str); } }; + +// EOF diff --git a/vnr/vnrhook/src/util/growl.h b/vnr/vnrhook/src/util/growl.h new file mode 100644 index 0000000..a99b5b2 --- /dev/null +++ b/vnr/vnrhook/src/util/growl.h @@ -0,0 +1,85 @@ +#pragma once + +// growl.h +// 9/17/2013 jichi + +//#ifdef GROWL_HAS_GROWL + +#include +#include + +#define GROWL_MSG_A(_msg) MessageBoxA(nullptr, _msg, "VNR Message", MB_OK) +#define GROWL_MSG(_msg) MessageBoxW(nullptr, _msg, L"VNR Message", MB_OK) +#define GROWL_WARN(_msg) MessageBoxW(nullptr, _msg, L"VNR Warning", MB_OK) +#define GROWL_ERROR(_msg) MessageBoxW(nullptr, _msg, L"VNR Error", MB_OK) + +inline void GROWL_DWORD(DWORD value) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD: %x", value); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD2(DWORD v, DWORD v2) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD2: %x,%x", v, v2); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD3(DWORD v, DWORD v2, DWORD v3) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD3: %x,%x,%x", v, v2, v3); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD4(DWORD v, DWORD v2, DWORD v3, DWORD v4) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD4: %x,%x,%x,%x", v, v2, v3, v4); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD5(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD5: %x,%x,%x,%x,%x", v, v2, v3, v4, v5); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD6(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5, DWORD v6) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD6: %x,%x,%x,%x,%x,%x", v, v2, v3, v4, v5, v6); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD7(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5, DWORD v6, DWORD v7) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD7: %x,%x,%x,%x,%x,%x,%x", v, v2, v3, v4, v5, v6, v7); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD8(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5, DWORD v6, DWORD v7, DWORD v8) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD8: %x,%x,%x,%x,%x,%x,%x,%x", v, v2, v3, v4, v5, v6, v7, v8); + GROWL_MSG(buf); +} + +inline void GROWL_DWORD9(DWORD v, DWORD v2, DWORD v3, DWORD v4, DWORD v5, DWORD v6, DWORD v7, DWORD v8, DWORD v9) +{ + WCHAR buf[100]; + swprintf(buf, L"DWORD9: %x,%x,%x,%x,%x,%x,%x,%x,%x", v, v2, v3, v4, v5, v6, v7, v8, v9); + GROWL_MSG(buf); +} + +inline void GROWL(DWORD v) { GROWL_DWORD(v); } +inline void GROWL(LPCWSTR v) { GROWL_MSG(v); } +inline void GROWL(LPCSTR v) { GROWL_MSG_A(v); } + +//#endif // GROWL_HAS_GROWL + +// EOF diff --git a/vnr/vnrhook/src/util/util.cc b/vnr/vnrhook/src/util/util.cc new file mode 100644 index 0000000..def0a24 --- /dev/null +++ b/vnr/vnrhook/src/util/util.cc @@ -0,0 +1,326 @@ +// util/util.cc +// 8/23/2013 jichi +// Branch: ITH_Engine/engine.cpp, revision 133 +// See: http://ja.wikipedia.org/wiki/プロジェクト:美少女ゲーム系/ゲームエンジン + +#include "src/util/util.h" +#include "ithsys/ithsys.h" + +namespace { // unnamed + +// jichi 4/19/2014: Return the integer that can mask the signature +DWORD SigMask(DWORD sig) +{ + __asm + { + xor ecx,ecx + mov eax,sig +_mask: + shr eax,8 + inc ecx + test eax,eax + jnz _mask + sub ecx,4 + neg ecx + or eax,-1 + shl ecx,3 + shr eax,cl + } +} + +} // namespace unnamed + +// jichi 8/24/2013: binary search? +DWORD Util::GetCodeRange(DWORD hModule,DWORD *low, DWORD *high) +{ + IMAGE_DOS_HEADER *DosHdr; + IMAGE_NT_HEADERS *NtHdr; + DWORD dwReadAddr; + IMAGE_SECTION_HEADER *shdr; + DosHdr = (IMAGE_DOS_HEADER *)hModule; + if (IMAGE_DOS_SIGNATURE == DosHdr->e_magic) { + dwReadAddr = hModule + DosHdr->e_lfanew; + NtHdr = (IMAGE_NT_HEADERS *)dwReadAddr; + if (IMAGE_NT_SIGNATURE == NtHdr->Signature) { + shdr = (PIMAGE_SECTION_HEADER)((DWORD)(&NtHdr->OptionalHeader) + NtHdr->FileHeader.SizeOfOptionalHeader); + while ((shdr->Characteristics & IMAGE_SCN_CNT_CODE) == 0) + shdr++; + *low = hModule + shdr->VirtualAddress; + *high = *low + (shdr->Misc.VirtualSize & 0xfffff000) + 0x1000; + } + } + return 0; +} + +DWORD Util::FindCallAndEntryBoth(DWORD fun, DWORD size, DWORD pt, DWORD sig) +{ + //WCHAR str[0x40]; + enum { reverse_length = 0x800 }; + DWORD t, l; + DWORD mask = SigMask(sig); + bool flag2; + for (DWORD i = 0x1000; i < size-4; i++) { + bool flag1 = false; + if (*(BYTE *)(pt + i) == 0xe8) { + flag1 = flag2 = true; + t = *(DWORD *)(pt + i + 1); + } else if (*(WORD *)(pt + i) == 0x15ff) { + flag1 = true; + flag2 = false; + t = *(DWORD *)(pt + i + 2); + } + if (flag1) { + if (flag2) { + flag1 = (pt + i + 5 + t == fun); + l = 5; + } else if (t >= pt && t <= pt + size - 4) { + flag1 = fun == *(DWORD *)t; + l = 6; + } else + flag1 = false; + if (flag1) + //swprintf(str,L"CALL addr: 0x%.8X",pt + i); + //OutputConsole(str); + for (DWORD j = i; j > i - reverse_length; j--) + if ((*(WORD *)(pt + j)) == (sig & mask)) //Fun entry 1. + //swprintf(str,L"Entry: 0x%.8X",pt + j); + //OutputConsole(str); + return pt + j; + else + i += l; + } + } + //OutputConsole(L"Find call and entry failed."); + return 0; +} + +DWORD Util::FindCallOrJmpRel(DWORD fun, DWORD size, DWORD pt, bool jmp) +{ + BYTE sig = (jmp) ? 0xe9 : 0xe8; + for (DWORD i = 0x1000; i < size - 4; i++) + if (sig == *(BYTE *)(pt + i)) { + DWORD t = *(DWORD *)(pt + i + 1); + if(fun == pt + i + 5 + t) + //OutputDWORD(pt + i); + return pt + i; + else + i += 5; + } + return 0; +} + +DWORD Util::FindCallOrJmpAbs(DWORD fun, DWORD size, DWORD pt, bool jmp) +{ + WORD sig = jmp ? 0x25ff : 0x15ff; + for (DWORD i = 0x1000; i < size - 4; i++) + if (sig == *(WORD *)(pt + i)) { + DWORD t = *(DWORD *)(pt + i + 2); + if (t > pt && t < pt + size) { + if (fun == *(DWORD *)t) + return pt + i; + else + i += 5; + } + } + return 0; +} + +DWORD Util::FindCallBoth(DWORD fun, DWORD size, DWORD pt) +{ + for (DWORD i = 0x1000; i < size - 4; i++) { + if (*(BYTE *)(pt + i) == 0xe8) { + DWORD t = *(DWORD *)(pt + i + 1) + pt + i + 5; + if (t == fun) + return i; + } + if (*(WORD *)(pt + i) == 0x15ff) { + DWORD t = *(DWORD *)(pt + i + 2); + if (t >= pt && t <= pt + size - 4) { + if (*(DWORD *)t == fun) + return i; + else + i += 6; + } + } + } + return 0; +} + +DWORD Util::FindCallAndEntryAbs(DWORD fun, DWORD size, DWORD pt, DWORD sig) +{ + //WCHAR str[0x40]; + enum { reverse_length = 0x800 }; + DWORD mask = SigMask(sig); + for (DWORD i = 0x1000; i < size - 4; i++) + if (*(WORD *)(pt + i) == 0x15ff) { + DWORD t = *(DWORD *)(pt + i + 2); + if (t >= pt && t <= pt + size - 4) { + if (*(DWORD *)t == fun) + //swprintf(str,L"CALL addr: 0x%.8X",pt + i); + //OutputConsole(str); + for (DWORD j = i ; j > i - reverse_length; j--) + if ((*(DWORD *)(pt + j) & mask) == sig) // Fun entry 1. + //swprintf(str,L"Entry: 0x%.8X",pt + j); + //OutputConsole(str); + return pt + j; + + } else + i += 6; + } + //OutputConsole(L"Find call and entry failed."); + return 0; +} + +DWORD Util::FindCallAndEntryRel(DWORD fun, DWORD size, DWORD pt, DWORD sig) +{ + //WCHAR str[0x40]; + enum { reverse_length = 0x800 }; + if (DWORD i = FindCallOrJmpRel(fun, size, pt, false)) { + DWORD mask = SigMask(sig); + for (DWORD j = i; j > i - reverse_length; j--) + if (((*(DWORD *)j) & mask) == sig) //Fun entry 1. + //swprintf(str,L"Entry: 0x%.8X",j); + //OutputConsole(str); + return j; + //OutputConsole(L"Find call and entry failed."); + } + return 0; +} +DWORD Util::FindEntryAligned(DWORD start, DWORD back_range) +{ + start &= ~0xf; + for (DWORD i = start, j = start - back_range; i > j; i-=0x10) { + DWORD k = *(DWORD *)(i-4); + if (k == 0xcccccccc + || k == 0x90909090 + || k == 0xccccccc3 + || k == 0x909090c3 + ) + return i; + DWORD t = k & 0xff0000ff; + if (t == 0xcc0000c2 || t == 0x900000c2) + return i; + k >>= 8; + if (k == 0xccccc3 || k == 0x9090c3) + return i; + t = k & 0xff; + if (t == 0xc2) + return i; + k >>= 8; + if (k == 0xccc3 || k == 0x90c3) + return i; + k >>= 8; + if (k == 0xc3) + return i; + } + return 0; +} + +DWORD Util::FindImportEntry(DWORD hModule, DWORD fun) +{ + IMAGE_DOS_HEADER *DosHdr; + IMAGE_NT_HEADERS *NtHdr; + DWORD IAT, end, pt, addr; + DosHdr = (IMAGE_DOS_HEADER *)hModule; + if (IMAGE_DOS_SIGNATURE == DosHdr->e_magic) { + NtHdr = (IMAGE_NT_HEADERS *)(hModule + DosHdr->e_lfanew); + if (IMAGE_NT_SIGNATURE == NtHdr->Signature) { + IAT = NtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress; + end = NtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size; + IAT += hModule; + end += IAT; + for (pt = IAT; pt < end; pt += 4) { + addr = *(DWORD *)pt; + if (addr == fun) + return pt; + } + } + } + return 0; +} + +// Search string in rsrc section. This section usually contains version and copyright info. +bool Util::SearchResourceString(LPCWSTR str) +{ + DWORD hModule = Util::GetModuleBase(); + IMAGE_DOS_HEADER *DosHdr; + IMAGE_NT_HEADERS *NtHdr; + DosHdr = (IMAGE_DOS_HEADER *)hModule; + DWORD rsrc, size; + //__asm int 3 + if (IMAGE_DOS_SIGNATURE == DosHdr->e_magic) { + NtHdr = (IMAGE_NT_HEADERS *)(hModule + DosHdr->e_lfanew); + if (IMAGE_NT_SIGNATURE == NtHdr->Signature) { + rsrc = NtHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress; + if (rsrc) { + rsrc += hModule; + if (IthGetMemoryRange((LPVOID)rsrc, &rsrc ,&size) && + SearchPattern(rsrc, size - 4, str, wcslen(str) << 1)) + return true; + } + } + } + return false; +} + +// jichi 4/15/2014: Copied from GetModuleBase in ITH CLI, for debugging purpose +DWORD Util::FindModuleBase(DWORD hash) +{ + __asm + { + mov eax,fs:[0x30] + mov eax,[eax+0xc] + mov esi,[eax+0x14] + mov edi,_wcslwr +listfind: + mov edx,[esi+0x28] + test edx,edx + jz notfound + push edx + call edi + pop edx + xor eax,eax +calc: + movzx ecx, word ptr [edx] + test cl,cl + jz fin + ror eax,7 + add eax,ecx + add edx,2 + jmp calc +fin: + cmp eax,[hash] + je found + mov esi,[esi] + jmp listfind +notfound: + xor eax,eax + jmp termin +found: + mov eax,[esi+0x10] +termin: + } +} + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; +// See: http://stackoverflow.com/questions/3410130/dll-unloading-itself +bool Util::unloadCurrentModule() +{ + auto fun = ::FreeLibrary; + //auto fun = ::LdrUnloadDll; + if (HANDLE h = ::IthCreateThread(fun, (DWORD)&__ImageBase)) { + //const LONGLONG timeout = -50000000; // in nanoseconds = 5 seconds + //NtWaitForSingleObject(h, 0, (PLARGE_INTEGER)&timeout); + NtClose(h); + return true; + } + + // CreateThread does not always work on Windows XP. Use IthCreateThread (i.e. CreateRemoteThread under the water) instead. + //if (HANDLE h = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)fun, &__ImageBase, 0, NULL)) { + // ::CloseHandle(h); + // return true; + //} + return false; +} + +// EOF diff --git a/vnr/vnrhook/src/util/util.h b/vnr/vnrhook/src/util/util.h new file mode 100644 index 0000000..7b712d2 --- /dev/null +++ b/vnr/vnrhook/src/util/util.h @@ -0,0 +1,78 @@ +#pragma once + +// util.h +// 8/23/2013 jichi + +#include "ntdll/ntdll.h" + +namespace Util { + +bool unloadCurrentModule(); + +DWORD GetCodeRange(DWORD hModule,DWORD *low, DWORD *high); +DWORD FindCallAndEntryBoth(DWORD fun, DWORD size, DWORD pt, DWORD sig); +DWORD FindCallOrJmpRel(DWORD fun, DWORD size, DWORD pt, bool jmp); +DWORD FindCallOrJmpAbs(DWORD fun, DWORD size, DWORD pt, bool jmp); +DWORD FindCallBoth(DWORD fun, DWORD size, DWORD pt); +DWORD FindCallAndEntryAbs(DWORD fun, DWORD size, DWORD pt, DWORD sig); +DWORD FindCallAndEntryRel(DWORD fun, DWORD size, DWORD pt, DWORD sig); +DWORD FindEntryAligned(DWORD start, DWORD back_range); +DWORD FindImportEntry(DWORD hModule, DWORD fun); + +// jichi 4/15/2014: Copied from ITH CLI, for debugging purpose +DWORD FindModuleBase(DWORD hash); + +bool SearchResourceString(LPCWSTR str); + +/** + * @param name process name without path deliminator + */ +inline void GetProcessName(wchar_t *name) +{ + //assert(name); + PLDR_DATA_TABLE_ENTRY it; + __asm + { + mov eax,fs:[0x30] + mov eax,[eax+0xc] + mov eax,[eax+0xc] + mov it,eax + } + ::wcscpy(name, it->BaseDllName.Buffer); +} + +/** + * @param path with process name and directy name + */ +inline void GetProcessPath(wchar_t *path) +{ + //assert(path); + PLDR_DATA_TABLE_ENTRY it; + __asm + { + mov eax,fs:[0x30] + mov eax,[eax+0xc] + mov eax,[eax+0xc] + mov it,eax + } + ::wcscpy(path, it->FullDllName.Buffer); +} + +/** + * @return HANDLE module handle + */ +inline DWORD GetModuleBase() +{ + __asm + { + mov eax,fs:[0x18] + mov eax,[eax+0x30] + mov eax,[eax+0xc] + mov eax,[eax+0xc] + mov eax,[eax+0x18] + } +} + +} // namespace Util + +// EOF diff --git a/vnr/vnrhook/vnrhook.pri b/vnr/vnrhook/vnrhook.pri new file mode 100644 index 0000000..12b189a --- /dev/null +++ b/vnr/vnrhook/vnrhook.pri @@ -0,0 +1,12 @@ +# vnrhook.pri +# 8/21/2013 jichi + +DEFINES += WITH_LIB_VNRHOOK +DEPENDPATH += $$PWD/include + +HEADERS += \ + $$PWD/include/const.h \ + $$PWD/include/defs.h \ + $$PWD/include/types.h + +# EOF diff --git a/vnr/vnrhook/vnrhook.pro b/vnr/vnrhook/vnrhook.pro new file mode 100644 index 0000000..da60a3d --- /dev/null +++ b/vnr/vnrhook/vnrhook.pro @@ -0,0 +1,80 @@ +# hook.pro +# 8/9/2013 jichi +# Build vnrhook.dll for Windows 7+ + +# Exception handler to catch all exceptions +CONFIG += dll noqt eh eha # noeh nosafeseh + +#CONFIG += noeh # msvcrt on Windows XP does not has exception handler +include(../../../config.pri) +include($$PLUGINDIR/ithsys/ithsys.pri) +include($$LIBDIR/disasm/disasm.pri) +include($$LIBDIR/memdbg/memdbg.pri) +include($$LIBDIR/ntdll/ntdll.pri) +include($$LIBDIR/ntinspect/ntinspect.pri) +include($$LIBDIR/winkey/winkey.pri) +#include($$LIBDIR/winseh/winseh_safe.pri) +include($$LIBDIR/winversion/winversion.pri) + +# 9/27/2013: disable ITH this game engine, only for debugging purpose +#DEFINES += ITH_DISABLE_ENGINE + +# jichi 9/22/2013: When ITH is on wine, mutex is needed to protect NtWriteFile +#DEFINES += ITH_WINE +#DEFINES += ITH_SYNC_PIPE + +DEFINES += ITH_HAS_CRT ITH_HAS_SEH +DEFINES += MEMDBG_NO_STL NTINSPECT_NO_STL # disabled as not used + +# jichi 11/24/2013: Disable manual heap +DEFINES -= ITH_HAS_HEAP + +# jichi 11/13/2011: disable swprinf warning +DEFINES += _CRT_NON_CONFORMING_SWPRINTFS + +## Libraries + +#LIBS += -L$$WDK7_HOME/lib/wxp/i386 -lntdll +#LIBS += $$WDK7_HOME/lib/crt/i386/msvcrt.lib # Override msvcrt10 + +LIBS += -lkernel32 -luser32 -lgdi32 #-lgdiplus + +## Sources + +TEMPLATE = lib +TARGET = vnrhook + +#CONFIG += staticlib + +HEADERS += \ + include/const.h \ + include/defs.h \ + include/types.h \ + src/except.h \ + src/main.h \ + src/util/growl.h \ + src/util/util.h \ + src/tree/avl.h \ + src/hijack/texthook.h \ + src/engine/engine.h \ + src/engine/hookdefs.h \ + src/engine/match.h \ + src/engine/pchooks.h \ + src/engine/mono/funcinfo.h \ + src/engine/ppsspp/funcinfo.h + +SOURCES += \ + src/main.cc \ + src/pipe.cc \ + src/util/util.cc \ + src/hijack/texthook.cc \ + src/engine/engine.cc \ + src/engine/match.cc \ + src/engine/pchooks.cc + +#RC_FILE += vnrhook.rc +#OTHER_FILES += vnrhook.rc + +OTHER_FILES += vnrhook.pri + +# EOF diff --git a/vnr/windbg/hijack.cc b/vnr/windbg/hijack.cc new file mode 100644 index 0000000..dafe371 --- /dev/null +++ b/vnr/windbg/hijack.cc @@ -0,0 +1,89 @@ +// hijack.cc +// 1/27/2013 jichi +#include "windbg/hijack.h" +#include "windbg/windbg_p.h" + +#ifdef _MSC_VER +# pragma warning (disable:4996) // C4996: use POSIX function (stricmp) +#endif // _MSC_VER + +//#define DEBUG "winsec" +#include "sakurakit/skdebug.h" + +WINDBG_BEGIN_NAMESPACE + +// - Inline Hook - +// See: http://asdf.wkeya.com/code/apihook6.html +PVOID overrideFunctionA(HMODULE stealFrom, LPCSTR oldFunctionModule, LPCSTR functionName, LPCVOID newFunction) +{ + if (!stealFrom) + return nullptr; + //HMODULE oldModule = GetModuleHandleA(oldFunctionModule); + //if (!oldModule) + // return nullptr; + //void *originalAddress = GetProcAddress(oldModule, functionName); + LPVOID originalAddress = details::getModuleFunctionAddressA(functionName, oldFunctionModule); + if (!originalAddress) + return nullptr; + IMAGE_DOS_HEADER *dosHeader = reinterpret_cast(stealFrom); + char *base = reinterpret_cast(stealFrom); + if (::IsBadReadPtr(dosHeader, sizeof(IMAGE_DOS_HEADER)) || dosHeader->e_magic != IMAGE_DOS_SIGNATURE) + return nullptr; + IMAGE_NT_HEADERS *ntHeader = + reinterpret_cast(base + dosHeader->e_lfanew); + if (::IsBadReadPtr(ntHeader, sizeof(IMAGE_NT_HEADERS)) || ntHeader->Signature != IMAGE_NT_SIGNATURE) + return nullptr; + if (!ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress) + return nullptr; + // See: http://msdn.microsoft.com/en-us/magazine/cc301808.aspx + IMAGE_IMPORT_DESCRIPTOR *import = + reinterpret_cast(base + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + + // scan memory + // TODO: add a maximum loop counter here! + while (import->Name) { + char *name = base + import->Name; + if (!::stricmp(name, oldFunctionModule)) + break; + import++; + } + if (!import->Name) + return nullptr; + IMAGE_THUNK_DATA *thunk = reinterpret_cast(base + import->FirstThunk); + while (thunk->u1.Function) { + if ((ULONG_PTR)thunk->u1.Function == (ULONG_PTR)originalAddress) { + ULONG_PTR *addr = reinterpret_cast(&thunk->u1.Function); + + // See: http://asdf.wkeya.com/code/apihook6.html + // Inline hook mechanism: + // + // LPVOID InlineHook3( PUINT8 mem, DWORD dwLen, PUINT8 pfOld, PUINT8 pfNew ) + // { + // DWORD dwOldProtect; + // VirtualProtect( ( PUINT8 )( pfOld ), dwLen, PAGE_READWRITE, &dwOldProtect ); + // // 関数のエントリーから指定したbyte数をメモリの前方にコピー + // // メモリの数byte後方からオリジナルへのジャンプを作成 + // // 指定の関数アドレスから5byteをフックへのjmp命令に書き換え + // VirtualProtect( ( PUINT8 )( pfOld ), dwLen, dwOldProtect, &dwOldProtect ); + // return ( PVOID )mem; + // } + + MEMORY_BASIC_INFORMATION mbi; + if (::VirtualQuery((LPVOID)addr, &mbi, sizeof(mbi)) == sizeof(mbi)) { + DWORD dwOldProtect; + if (::VirtualProtect(mbi.BaseAddress, ((ULONG_PTR)addr + 8)-(ULONG_PTR)mbi.BaseAddress, PAGE_EXECUTE_READWRITE, &dwOldProtect)) { + *addr = (ULONG_PTR)newFunction; + ::VirtualProtect(mbi.BaseAddress, ((ULONG_PTR)addr + 8)-(ULONG_PTR)mbi.BaseAddress, dwOldProtect, &dwOldProtect); + return originalAddress; + } + } + + } + thunk++; + } + return nullptr; +} + +WINDBG_END_NAMESPACE + +// EOF diff --git a/vnr/windbg/hijack.h b/vnr/windbg/hijack.h new file mode 100644 index 0000000..7896195 --- /dev/null +++ b/vnr/windbg/hijack.h @@ -0,0 +1,25 @@ +#pragma once + +// hijack.h +// 1/27/2013 jichi + +#include "windbg/windbg.h" +#include + +WINDBG_BEGIN_NAMESPACE + +/** + * Replace the named function entry with the new one. + * @param stealFrom instance of target module + * @param oldFunctionModule TODO + * @param functionName name of the target function + * @return the orignal address if succeed, else nullptr + * + * See: http://www.codeproject.com/KB/DLL/DLL_Injection_tutorial.aspx + */ +PVOID overrideFunctionA(_In_ HMODULE stealFrom, _In_ LPCSTR oldFunctionModule, + _In_ LPCSTR functionName, _In_ LPCVOID newFunction); + +WINDBG_END_NAMESPACE + +// EOF diff --git a/vnr/windbg/inject.cc b/vnr/windbg/inject.cc new file mode 100644 index 0000000..c9e9078 --- /dev/null +++ b/vnr/windbg/inject.cc @@ -0,0 +1,167 @@ +// inject.cc +// 1/27/2013 jichi +#include "windbg/inject.h" +#include "windbg/windbg_p.h" +#include // for wcslen + +//#define DEBUG "windbg::inject" +#include "sakurakit/skdebug.h" + +WINDBG_BEGIN_NAMESPACE + +// - Remote Injection - + +BOOL InjectFunction1(LPCVOID addr, LPCVOID data, SIZE_T dataSize, DWORD pid, HANDLE hProcess, INT timeout) +{ + DOUT("enter: pid =" << pid); + if (hProcess == INVALID_HANDLE_VALUE && pid) { + hProcess = ::OpenProcess(PROCESS_INJECT_ACCESS, FALSE, pid); + // TODO: Privilege elevation is not implemented. See: skwinsec.py. + //if (!hProcess) { + // priv = SkProcessElevator('SeDebugPrivilege') + // if not priv.isEmpty(): + // handle = win32api.OpenProcess(PROCESS_INJECT_ACCESS, 0, pid) + //} + } + if (hProcess == INVALID_HANDLE_VALUE) { + DOUT("exit: error: failed to get process handle"); + return FALSE; + } + + BOOL ret = FALSE; + if (LPVOID remoteData = ::VirtualAllocEx(hProcess, nullptr, dataSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE)) { + if (::WriteProcessMemory(hProcess, remoteData, data, dataSize, nullptr)) + if (HANDLE hThread = ::CreateRemoteThread( + hProcess, + nullptr, 0, + reinterpret_cast(addr), + remoteData, + 0, nullptr)) { + ::WaitForSingleObject(hThread, timeout); + ::CloseHandle(hThread); + ret = TRUE; + } + ::VirtualFreeEx(hProcess, remoteData, dataSize, MEM_RELEASE); + } + ::CloseHandle(hProcess); + DOUT("exit: ret =" << ret); + return ret; +} + +BOOL injectDllW(LPCWSTR dllPath, DWORD pid, HANDLE hProcess, INT timeout) +{ + DOUT("enter: pid =" << pid); + LPCVOID fun = details::getModuleFunctionAddressA("LoadLibraryW", "kernel32.dll"); + if (!fun) { + DOUT("exit error: cannot find function"); + return FALSE; + } + LPCVOID data = dllPath; + SIZE_T dataSize = ::wcslen(dllPath) * 2 + 2; // L'\0' + BOOL ok = InjectFunction1(fun, data, dataSize, pid, hProcess, timeout); + DOUT("exit: ret =" << ok); + return ok; +} + +BOOL ejectDll(HANDLE hDll, DWORD pid, HANDLE hProcess, INT timeout) +{ + DOUT("enter: pid =" << pid); + LPCVOID fun = details::getModuleFunctionAddressA("FreeLibrary", "kernel32.dll"); + if (!fun) { + DOUT("exit error: cannot find function"); + return FALSE; + } + LPCVOID data = &hDll; + SIZE_T dataSize = sizeof(hDll); + BOOL ok = InjectFunction1(fun, data, dataSize, pid, hProcess, timeout); + DOUT("exit: ret =" << ok); + return ok; +} + +WINDBG_END_NAMESPACE + +// EOF + +/* +enum { CREATE_THREAD_ACCESS = (PROCESS_CREATE_THREAD | + PROCESS_QUERY_INFORMATION | + PROCESS_VM_OPERATION | + PROCESS_VM_WRITE | + PROCESS_VM_READ ) }; + + +int InjectDll(HANDLE hProcess, HINSTANCE hInst) { + HANDLE hThread; + + wchar_t dllFile[2*MAX_PATH]; + if (GetModuleFileNameW(hInst, dllFile, sizeof(dllFile)/2) > sizeof(dllFile)/2) return 0; + + void *loadLibraryW = GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW"); + if (!loadLibraryW) return 0; + + wchar_t *name; + if (!(name = wcsrchr(dllFile, '\\'))) return 0; + name ++; + wcscpy(name, DLL_NAME); + if (GetFileAttributes(dllFile) == INVALID_FILE_ATTRIBUTES) return 0; + + size_t len = sizeof(wchar_t)*(1+wcslen(dllFile)); + void *remoteString = (LPVOID)VirtualAllocEx(hProcess, + NULL, + len, + MEM_RESERVE|MEM_COMMIT, + PAGE_READWRITE + ); + if (remoteString) { + if (WriteProcessMemory(hProcess, (LPVOID)remoteString, dllFile, len, NULL)) { + if (hThread = CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE)loadLibraryW, (LPVOID)remoteString, 0,0)) { + WaitForSingleObject(hThread, 3000); + CloseHandle(hThread); + VirtualFreeEx(hProcess, remoteString, len, MEM_FREE); + // Make sure it's injected before resuming. + return 1; + } + } + VirtualFreeEx(hProcess, remoteString, len, MEM_FREE); + } + return 0; +} + +int getPriv(const char * name) { + HANDLE hToken; + LUID seValue; + TOKEN_PRIVILEGES tkp; + + if (!LookupPrivilegeValueA(NULL, name, &seValue) || + !OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { + return 0; + } + + tkp.PrivilegeCount = 1; + tkp.Privileges[0].Luid = seValue; + tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + int res = AdjustTokenPrivileges(hToken, 0, &tkp, sizeof(tkp), NULL, NULL); + + CloseHandle(hToken); + return res; +} + +inline int getDebugPriv() { + return getPriv("SeDebugPrivilege"); +} + +int InjectIntoProcess(int pid) { + HANDLE hProcess = OpenProcess(CREATE_THREAD_ACCESS, 0, pid); + if (hProcess == 0) { + getDebugPriv(); + hProcess = OpenProcess(CREATE_THREAD_ACCESS, 0, pid); + if (!hProcess) return 0; + } + + int out = InjectDll(hProcess); + + CloseHandle(hProcess); + return out; +} +*/ diff --git a/vnr/windbg/inject.h b/vnr/windbg/inject.h new file mode 100644 index 0000000..46878c7 --- /dev/null +++ b/vnr/windbg/inject.h @@ -0,0 +1,56 @@ +#pragma once + +// inject.h +// 1/27/2013 jichi + +#include "windbg/windbg.h" + +#include + +WINDBG_BEGIN_NAMESPACE + +enum { PROCESS_INJECT_ACCESS = PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_WRITE|PROCESS_VM_READ }; +enum { INJECT_TIMEOUT = 3000 }; // wait at most 3 seconds for creating remote threads + +/** + * Inject function with 1 argument + * Either pid or the process handle should be specified + * @param addr LONG function memory address + * @param arg LPVOID arg1 data + * @param argSize int arg1 data size + * @param pid process id + * @param hProcess process handle + * @param timeout msec + * @return BOOL + */ +BOOL injectFunction1(_In_ LPCVOID addr, _In_ LPCVOID arg, _In_ SIZE_T argSize, + _In_ DWORD pid = 0, _In_ HANDLE hProcess = INVALID_HANDLE_VALUE, + _In_ INT timeout = INJECT_TIMEOUT); + +/** + * Either pid or the process handle should be specified + * @param dllpath ABSOLUTE path to dll + * @param pid process id + * @param hProcess process handle + * @param timeout msec + * @return BOOL + */ +BOOL injectDllW(_In_ LPCWSTR dllPath, + _In_ DWORD pid = 0, _In_ HANDLE hProcess = INVALID_HANDLE_VALUE, + _In_ INT timeout = INJECT_TIMEOUT); + +/** + * Either pid or the process handle should be specified + * @param hDll dll module handle + * @param pid process id + * @param hProcess process handle + * @param timeout msec + * @return BOOL + */ +BOOL ejectDll(_In_ HANDLE hDll, + _In_ DWORD pid = 0, _In_ HANDLE hProcess = INVALID_HANDLE_VALUE, + _In_ INT timeout = INJECT_TIMEOUT); + +WINDBG_END_NAMESPACE + +// EOF diff --git a/vnr/windbg/unload.cc b/vnr/windbg/unload.cc new file mode 100644 index 0000000..a09ecb2 --- /dev/null +++ b/vnr/windbg/unload.cc @@ -0,0 +1,22 @@ +// unload.cc +// 5/2/2014 jichi +#include "windbg/unload.h" + +WINDBG_BEGIN_NAMESPACE + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; +// See: http://stackoverflow.com/questions/3410130/dll-unloading-itself +BOOL unloadCurrentModule() +{ + auto fun = ::FreeLibrary; + //auto fun = ::LdrUnloadDll; + if (HANDLE h = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)fun, &__ImageBase, 0, NULL)) { + ::CloseHandle(h); + return TRUE; + } + return FALSE; +} + +WINDBG_END_NAMESPACE + +// EOF diff --git a/vnr/windbg/unload.h b/vnr/windbg/unload.h new file mode 100644 index 0000000..9a90055 --- /dev/null +++ b/vnr/windbg/unload.h @@ -0,0 +1,19 @@ +#pragma once + +// unload.h +// 5/2/2014 jichi + +#include "windbg/windbg.h" +#include + +WINDBG_BEGIN_NAMESPACE + +/** + * Unload current injected DLL. + * @return BOOL + */ +BOOL unloadCurrentModule(); + +WINDBG_END_NAMESPACE + +// EOF diff --git a/vnr/windbg/util.cc b/vnr/windbg/util.cc new file mode 100644 index 0000000..43a2dbe --- /dev/null +++ b/vnr/windbg/util.cc @@ -0,0 +1,61 @@ +// windbg/util.cc +// 1/27/2013 jichi +#include "windbg/util.h" +#include +#include +#include + +WINDBG_BEGIN_NAMESPACE + +class ThreadsSuspenderPrivate +{ +public: + std::list threads; +}; + +ThreadsSuspender::ThreadsSuspender(bool autoSuspend) + : d_(new D) +{ if (autoSuspend) suspend(); } + +ThreadsSuspender::~ThreadsSuspender() +{ + resume(); + delete d_; +} + +void ThreadsSuspender::suspend() +{ + HANDLE hSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (hSnap == INVALID_HANDLE_VALUE) + return; + THREADENTRY32 entry; + entry.dwSize = sizeof(entry); + DWORD pid = ::GetCurrentProcessId(); + DWORD tid = ::GetCurrentThreadId(); + if (::Thread32First(hSnap, &entry)) + do if (entry.dwSize >= 4 * sizeof(DWORD) && entry.th32OwnerProcessID == pid && entry.th32ThreadID != tid) { + if (HANDLE hThread = ::OpenThread(THREAD_SUSPEND_RESUME, 0, entry.th32ThreadID)) { + if (::SuspendThread(hThread) != DWORD(-1)) + d_->threads.push_back(hThread); + else + ::CloseHandle(hThread); + } + entry.dwSize = sizeof(entry); + } while (::Thread32Next(hSnap, &entry)); + ::CloseHandle(hSnap); +} + +void ThreadsSuspender::resume() +{ + if (!d_->threads.empty()) { + BOOST_FOREACH (HANDLE hThread, d_->threads) { + ::ResumeThread(hThread); + ::CloseHandle(hThread); + } + d_->threads.clear(); + } +} + +WINDBG_END_NAMESPACE + +// EOF diff --git a/vnr/windbg/util.h b/vnr/windbg/util.h new file mode 100644 index 0000000..bf512fd --- /dev/null +++ b/vnr/windbg/util.h @@ -0,0 +1,34 @@ +#pragma once + +// windbg/util.h +// 1/27/2013 jichi + +#include "windbg/windbg.h" +#include "sakurakit/skglobal.h" + +#include + +WINDBG_BEGIN_NAMESPACE + +class ThreadsSuspenderPrivate; +/** + * When created, automatically suspends all threads in the current process. + * When destroyed, resume suspended threads. + */ +class ThreadsSuspender +{ + SK_CLASS(ThreadsSuspender) + SK_DISABLE_COPY(ThreadsSuspender) + SK_DECLARE_PRIVATE(ThreadsSuspenderPrivate) + +public: + explicit ThreadsSuspender(bool autoSuspend = true); + ~ThreadsSuspender(); + + void resume(); ///< Manually resume all threads + void suspend(); ///< Manually suspend all threads +}; + +WINDBG_END_NAMESPACE + +// EOF diff --git a/vnr/windbg/windbg.h b/vnr/windbg/windbg.h new file mode 100644 index 0000000..0bb771c --- /dev/null +++ b/vnr/windbg/windbg.h @@ -0,0 +1,16 @@ +#pragma once + +// windbg.h +// 1/27/2013 jichi + +#ifndef WINDBG_BEGIN_NAMESPACE +# define WINDBG_BEGIN_NAMESPACE namespace WinDbg { +#endif +#ifndef WINDBG_END_NAMESPACE +# define WINDBG_END_NAMESPACE } // namespace WinDbg +#endif + +WINDBG_BEGIN_NAMESPACE +WINDBG_END_NAMESPACE + +// EOF diff --git a/vnr/windbg/windbg.pri b/vnr/windbg/windbg.pri new file mode 100644 index 0000000..2673d6b --- /dev/null +++ b/vnr/windbg/windbg.pri @@ -0,0 +1,26 @@ +# windbg.pri +# 4/21/2014 jichi +win32 { + +DEFINES += WITH_LIB_WINDBG + +LIBS += -lkernel32 -luser32 + +DEPENDPATH += $$PWD + +HEADERS += \ + $$PWD/hijack.h \ + $$PWD/inject.h \ + $$PWD/util.h \ + $$PWD/unload.h \ + $$PWD/windbg_p.h \ + $$PWD/windbg.h + +SOURCES += \ + $$PWD/hijack.cc \ + $$PWD/inject.cc \ + $$PWD/util.cc \ + $$PWD/unload.cc +} + +# EOF diff --git a/vnr/windbg/windbg_p.h b/vnr/windbg/windbg_p.h new file mode 100644 index 0000000..a534e58 --- /dev/null +++ b/vnr/windbg/windbg_p.h @@ -0,0 +1,24 @@ +#pragma once + +// windbg_p.h +// 1/27/2013 jichi + +#include "windbg/windbg.h" +#include + +WINDBG_BEGIN_NAMESPACE + +namespace details { // unnamed + +/// Return the address of func in module. +inline FARPROC getModuleFunctionAddressA(LPCSTR func, LPCSTR module = nullptr) +{ return ::GetProcAddress(::GetModuleHandleA(module), func); } + +inline FARPROC getModuleFunctionAddressW(LPCSTR func, LPCWSTR module = nullptr) +{ return ::GetProcAddress(::GetModuleHandleW(module), func); } + +} // unamed namespace details + +WINDBG_END_NAMESPACE + +// EOF diff --git a/vnr/winkey/winkey.h b/vnr/winkey/winkey.h new file mode 100644 index 0000000..d179c30 --- /dev/null +++ b/vnr/winkey/winkey.h @@ -0,0 +1,32 @@ +#pragma once + +// winkey.h +// 7/21/2011 + +#include + +#ifndef WINKEY_BEGIN_NAMESPACE +# define WINKEY_BEGIN_NAMESPACE namespace WinKey { +#endif +#ifndef WINKEY_END_NAMESPACE +# define WINKEY_END_NAMESPACE } // namespace WinKey +#endif + + +WINKEY_BEGIN_NAMESPACE + +inline bool isKeyPressed(int vk) { return ::GetKeyState(vk) & 0xf0; } +inline bool isKeyToggled(int vk) { return ::GetKeyState(vk) & 0x0f; } + +inline bool isKeyReturnPressed() { return isKeyPressed(VK_RETURN); } +inline bool isKeyControlPressed() { return isKeyPressed(VK_CONTROL); } +inline bool isKeyShiftPressed() { return isKeyPressed(VK_SHIFT); } +inline bool isKeyAltPressed() { return isKeyPressed(VK_MENU); } +//inline bool sKeyCapslockToggled() { return isKeyToggled(VK_CAPITAL); } +inline bool isKeyWinPressed() { return isKeyPressed(VK_LWIN) || isKeyPressed(VK_RWIN); } + +inline bool isMouseLeftButtonPressed() { return isKeyPressed(VK_LBUTTON); } +inline bool isMouseMiddleButtonPressed() { return isKeyPressed(VK_MBUTTON); } +inline bool isMouseRightButtonPressed() { return isKeyPressed(VK_RBUTTON); } + +WINKEY_END_NAMESPACE diff --git a/vnr/winkey/winkey.pri b/vnr/winkey/winkey.pri new file mode 100644 index 0000000..cd7f534 --- /dev/null +++ b/vnr/winkey/winkey.pri @@ -0,0 +1,15 @@ +# winkey.pri +# 7/20/2011 jichi +win32 { + +DEFINES += WITH_LIB_WINKEY + +LIBS += -luser32 + +DEPENDPATH += $$PWD + +HEADERS += $$PWD/winkey.h +#SOURCES += $$PWD/winkey.cc +} + +# EOF diff --git a/vnr/winmutex/winmutex.h b/vnr/winmutex/winmutex.h index 8856f88..2e6d4cf 100644 --- a/vnr/winmutex/winmutex.h +++ b/vnr/winmutex/winmutex.h @@ -31,7 +31,7 @@ template native_handle_type native_handle() { return _M_mutex.native_handle(); } void unlock() { _M_mutex.unlock(); _M_locked = false; } void lock() { _M_mutex.lock(); _M_locked = true; } - bool tryLock() { return _M_locked = _M_mutex.tryLock(); } + bool try_lock() { return _M_locked = _M_mutex.try_lock(); } }; // Mutex diff --git a/vnr/winseh/Makefile b/vnr/winseh/Makefile index 7c3667e..1c876ae 100644 --- a/vnr/winseh/Makefile +++ b/vnr/winseh/Makefile @@ -3,7 +3,7 @@ # This file is for Windows only. # Compile SAFESEH table from the ASM file. # See: http://stackoverflow.com/questions/19722308/exception-handler-not-called-in-c -# See: ::http://stackoverflow.com/questions/12019689/custom-seh-handler-with-safeseh +# See: http://stackoverflow.com/questions/12019689/custom-seh-handler-with-safeseh # See: http://msdn.microsoft.com/en-us/library/16aexws6.aspx BUILDDIR = ../../../build diff --git a/vnr/winseh/safeseh.asm b/vnr/winseh/safeseh.asm index 7f55086..d672999 100644 --- a/vnr/winseh/safeseh.asm +++ b/vnr/winseh/safeseh.asm @@ -1,5 +1,5 @@ ; safeseh.asm -; 12/13/2013 jichi +; 12/13/2013 jichi ; see: http://stackoverflow.com/questions/12019689/custom-seh-handler-with-safeseh ; see: http://code.metager.de/source/xref/WebKit/Source/WebCore/platform/win/makesafeseh.asm ; see: http://jpassing.com/2008/05/20/fun-with-low-level-seh/ diff --git a/vnr/winseh/winseh.cc b/vnr/winseh/winseh.cc index c8e0796..275589f 100644 --- a/vnr/winseh/winseh.cc +++ b/vnr/winseh/winseh.cc @@ -5,7 +5,7 @@ #include "ntdll/ntdll.h" //#include -// - Global variables - +// - Define global variables - seh_dword_t seh_esp[seh_capacity], seh_eip[seh_capacity], @@ -29,16 +29,16 @@ seh_dword_t seh_count; // EXCEPTION_DISPOSITION // NTAPI // EXCEPTION_ROUTINE ( -// _Inout_ struct _EXCEPTION_RECORD *ExceptionRecord, -// _In_ PVOID EstablisherFrame, -// _In_ struct _CONTEXT *ContextRecord, -// _In_ PVOID DispatcherContext -// ); +// _Inout_ struct _EXCEPTION_RECORD *ExceptionRecord, +// _In_ PVOID EstablisherFrame, +// _In_ struct _CONTEXT *ContextRecord, +// _In_ PVOID DispatcherContext +// ); extern "C" EXCEPTION_DISPOSITION _seh_handler( // extern C is needed to avoid name hashing in C++ _In_ PEXCEPTION_RECORD ExceptionRecord, - _In_ PVOID EstablisherFrame, // does not work if I use ULONG64 + _In_ PVOID EstablisherFrame, // do not work if I use ULONG64 _Inout_ PCONTEXT ContextRecord, - _In_ PVOID DispatcherContext) // PDISPATCHER_CONTEXT is not declared in windows.h + _In_ PVOID DispatcherContext) // PDISPATCHER_CONTEXT is not declared in windows.h, use PVOID instead { //assert(::seh_count > 0); ContextRecord->Esp = ::seh_esp[::seh_count - 1]; diff --git a/vnr/winseh/winseh.h b/vnr/winseh/winseh.h index c7ff236..72997f3 100644 --- a/vnr/winseh/winseh.h +++ b/vnr/winseh/winseh.h @@ -23,11 +23,13 @@ typedef unsigned long seh_dword_t; // DWORD in // 12/13/2013 jichi // The list implementation is not thread-safe -extern seh_dword_t seh_esp[seh_capacity], // LPVOID, current stack - seh_eip[seh_capacity], // LPVOID, current IP address - seh_eh[seh_capacity]; // EXCEPTION_ROUTINE, current exception handler function address -extern seh_dword_t seh_count; // current number of exception handlers -extern seh_dword_t seh_handler; //extern PEXCEPTION_ROUTINE seh_handler; +extern seh_dword_t + seh_count // current number of exception handlers + , seh_handler // extern PEXCEPTION_ROUTINE seh_handler; + , seh_esp[seh_capacity] // LPVOID, current stack + , seh_eip[seh_capacity] // LPVOID, current IP address + , seh_eh[seh_capacity] // EXCEPTION_ROUTINE, current exception handler function address +; /** * Push SEH handler @@ -49,21 +51,22 @@ extern seh_dword_t seh_handler; //extern PEXCEPTION_ROUTINE seh_handler; * * EPB and ESP * http://stackoverflow.com/questions/1395591/what-is-exactly-the-base-pointer-and-stack-pointer-to-what-do-they-point + * + * TODO: get sizeof dword instead of hardcode 4 */ #define seh_push_(_label, _eh, _r1, _r2) \ { \ - __asm mov _r1, _eh /* move new handler address */ \ - __asm mov _r2, seh_count /* get current seh counter */ \ - __asm mov dword ptr seh_eh[_r2*4], _r1 /* set recover exception hander */ \ - __asm mov _r1, _label /* move jump label address */ \ + __asm mov _r1, _eh /* move new handler address */ \ + __asm mov _r2, seh_count /* get current seh counter */ \ + __asm mov dword ptr seh_eh[_r2*4], _r1 /* set recover exception hander */ \ + __asm mov _r1, _label /* move jump label address */ \ __asm mov dword ptr seh_eip[_r2*4], _r1 /* set recover eip as the jump label */ \ - __asm push seh_handler /* push new safe seh handler */ \ - __asm push fs:[0] /* push old fs:0 */ \ + __asm push seh_handler /* push new safe seh handler */ \ + __asm push fs:[0] /* push old fs:0 */ \ __asm mov dword ptr seh_esp[_r2*4], esp /* safe current stack address */ \ - __asm mov fs:[0], esp /* change fs:0 to the current stack */ \ - __asm inc seh_count /* increase number of seh */ \ + __asm mov fs:[0], esp /* change fs:0 to the current stack */ \ + __asm inc seh_count /* increase number of seh */ \ } - //TODO: get sizeof dword instead of hardcode 4 /** * Restore old SEH handler @@ -71,12 +74,13 @@ extern seh_dword_t seh_handler; //extern PEXCEPTION_ROUTINE seh_handler; */ #define seh_pop_(_label) \ { \ - __asm _label: /* the exception recover label */ \ - __asm pop dword ptr fs:[0] /* restore old fs:0 */ \ - __asm add esp, 4 /* pop seh_handler */ \ - __asm dec seh_count /* decrease number of seh */ \ + __asm _label: /* the exception recover label */ \ + __asm pop dword ptr fs:[0] /* restore old fs:0 */ \ + __asm add esp, 4 /* pop seh_handler */ \ + __asm dec seh_count /* decrease number of seh */ \ } +// Define seh_exit as the shared exit label #define seh_pop() seh_pop_(seh_exit) #define seh_push() seh_push_(seh_exit, 0, eax, ecx) // use ecx as counter better than ebx @@ -93,7 +97,7 @@ extern seh_dword_t seh_handler; //extern PEXCEPTION_ROUTINE seh_handler; { \ seh_push() \ __VA_ARGS__ \ - ; \ + ; /* allow __VA_ARGS__ to be an expression */ \ seh_pop() \ } @@ -106,7 +110,7 @@ extern seh_dword_t seh_handler; //extern PEXCEPTION_ROUTINE seh_handler; { \ seh_push_eh(_eh) \ __VA_ARGS__ \ - ; \ + ; /* allow __VA_ARGS__ to be an expression */ \ seh_pop() \ } diff --git a/vnr/wintimer/wintimer.cc b/vnr/wintimer/wintimer.cc new file mode 100644 index 0000000..b8c22e0 --- /dev/null +++ b/vnr/wintimer/wintimer.cc @@ -0,0 +1,49 @@ +// wintimer.cc +// 6/6/2012 jichi + +#include "wintimer/wintimer.h" + +//#define DEBUG "wintimer.cc" +#include "sakurakit/skdebug.h" +#include +WINTIMER_BEGIN_NAMESPACE + +void WinTimer::singleShot(int msecs, const function_type &f, WId parent) +{ + Self *t = new Self(parent); + t->setInterval(msecs); + t->setSingleShot(true); + t->setFunction([=] { // Copy function f instead of pass by reference. + f(); + delete t; + }); + t->start(); +} + +WINTIMER_END_NAMESPACE + +// EOF + +/* +// - Single shot - + +namespace { // unnamed + +class apply_delete +{ + typedef WinTimer::function_type function_type; + function_type f_; + WinTimer *t_; +public: + apply_delete(const function_type &f, WinTimer *t) + : f_(f), t_(t) { Q_ASSERT(t); } + + void operator()() + { + f_(); + delete t_; + } +}; + +} // unnamed namespace +*/ diff --git a/vnr/wintimer/wintimer.h b/vnr/wintimer/wintimer.h new file mode 100644 index 0000000..8605854 --- /dev/null +++ b/vnr/wintimer/wintimer.h @@ -0,0 +1,93 @@ +#pragma once + +// wintimer.h +// 6/6/2012 jichi +// +// A light-weighted native windows timer as a replacement of QTimer from Qt. +// Implementation is based on Windows Messaging. A visible parent hwnd is required. +// +// This timer is critical where QTimer or event loop are not available, or need to +// warp to different event loop. Some usage cases follow: +// - Used by texthook as a replacement of QTimer in non-QThread +// - Used by qapplicationloader to implement pseudo event loop +// - Used by winhook to synchronize with window event loop across threads + +#include "wintimer/wintimerbase.h" +#include + +/** + * @brief A light-weighted native windows timer as a replacement of QTimer. + * + * Needed when in a thread where event loop is not accessible. + * Implemented using extensive inlining over pimp, so that the entire class + * could be put on the stack without heap. + * + * Each timer requires an valid visible window's handle to synchronize with. + * Either specify the window handle with the parent window or a global window. + */ +class WinTimer : protected WinTimerBase +{ + SK_EXTEND_CLASS(WinTimer, WinTimerBase) + SK_DISABLE_COPY(WinTimer) + + // - Construction - +public: + //typedef std::function function_type; + using Base::function_type; ///< std::function + + /// Default parent window of all timers. + static WId globalWindow() { return Base::globalWindow; } + static void setGlobalWindow(WId winId) { Base::globalWindow = winId; } + + //static WId createHiddenWindow(); + +public: + /// Construct a timer with the parent window handle. + explicit WinTimer(WId parentWindow = 0) { setParentWindow(parentWindow); } + + static void singleShot(int msecs, const function_type &f, WId parent = 0); + + // - Properties - +public: + using Base::isActive; + using Base::isSingleShot; + + void setSingleShot(bool t) { Base::singleShot = t; } + + //bool isEmpty() const { return Base::function.empty(); } + + WId parentWindow() const { return Base::parentWindow; } + void setParentWindow(WId winId) { Base::parentWindow = winId ? winId : Base::globalWindow; } + + int interval() const { return Base::interval; } + void setInterval(int msecs) { Base::interval = msecs; } + + /// Timeout callback when trigger. + void setFunction(const function_type &f) { Base::function = f; } + + /// @overload Set callback to a class method + template + void setMethod(Class *obj, Member mfunc) + { setFunction(boost::bind(mfunc, obj)); } + + /// @overload Set callback to a const class method + template + void setMethod(const Class *obj, Member mfunc) + { setFunction(boost::bind(mfunc, obj)); } + + // - Actions - +public: + /// Start TimerProc + using Base::start; + + /// Stop TimerProc + using Base::stop; + + /// Reset interval and start TimerProc + void start(int interval) { setInterval(interval); start(); } + + /// Invoke the callback. This function is the callback of the underlying TimerProc + using Base::trigger; +}; + +WINTIMER_END_NAMESPACE diff --git a/vnr/wintimer/wintimer.pri b/vnr/wintimer/wintimer.pri new file mode 100644 index 0000000..e1296d3 --- /dev/null +++ b/vnr/wintimer/wintimer.pri @@ -0,0 +1,20 @@ +# wintimer.pri +# 7/20/2011 jichi +win32 { + +DEFINES += WITH_LIB_WINTIMER + +LIBS += -lkernel32 -luser32 -lwintimer + +DEPENDPATH += $$PWD + +HEADERS += \ + $$PWD/wintimer.h \ + $$PWD/wintimerbase.h + +#SOURCES += \ +# $$PWD/wintimer.cc \ +# $$PWD/wintimerbase.cc +} + +# EOF diff --git a/vnr/wintimer/wintimer.pro b/vnr/wintimer/wintimer.pro new file mode 100644 index 0000000..69f2ff4 --- /dev/null +++ b/vnr/wintimer/wintimer.pro @@ -0,0 +1,22 @@ +# sys.pro +# 8/21/2013 jichi +# Build ITH_engine.dll + +#CONFIG += noqt noeh staticlib +CONFIG += staticlib +include(../../../config.pri) + +## Sources + +TEMPLATE = lib +TARGET = wintimer + +HEADERS += \ + wintimer.h \ + wintimerbase.h + +SOURCES += \ + wintimer.cc \ + wintimerbase.cc + +# EOF diff --git a/vnr/wintimer/wintimerbase.cc b/vnr/wintimer/wintimerbase.cc new file mode 100644 index 0000000..ac4fd95 --- /dev/null +++ b/vnr/wintimer/wintimerbase.cc @@ -0,0 +1,73 @@ +// wintimerbase.cc +// 6/6/2012 jichi + +#include "wintimer/wintimerbase.h" +#ifdef QT_CORE_LIB +# include +#else +# include +#endif // QT_CORE_LIB +#include "ccutil/ccmacro.h" + +//#define DEBUG "wintimerbase.cc" +#include "sakurakit/skdebug.h" + +static VOID CALLBACK WinTimerProc( + HWND hwnd, // ウィンドウのハンドル + UINT uMsg, // WM_TIMER メッセージ + UINT_PTR idEvent, // Timer ID + DWORD dwTime // 現在のシステム時刻 +) +{ + Q_UNUSED(hwnd) + Q_UNUSED(dwTime) + Q_UNUSED(uMsg) + Q_ASSERT(idEvent); + if (CC_UNLIKELY(!idEvent)) + return; + DOUT("enter"); + WinTimerBase *t = reinterpret_cast(idEvent); + + if (t->isSingleShot() && t->isActive()) + t->stop(); + t->trigger(); + DOUT("leave"); +} + +WINTIMER_BEGIN_NAMESPACE + +// - Construction - + +WId WinTimerBase::globalWindow; + +//WId WinTimer::createHiddenWindow() +//{ +// DOUT("enter: warning: hidden window used"); +// QWidget *w = new QWidget; +// w->resize(QSize()); +// w->show(); +// DOUT("leave"); +// return w->winId(); +//} + +// - Timer - + +void WinTimerBase::start() +{ + DOUT("enter: active =" << active << ", interval =" << interval); + active = true; + ::SetTimer(parentWindow, reinterpret_cast(this), interval, WinTimerProc); + DOUT("leave"); +} + +void WinTimerBase::stop() +{ + DOUT("enter: active =" << active); + active = false; + ::KillTimer(parentWindow, reinterpret_cast(this)); + DOUT("leave"); +} + +WINTIMER_END_NAMESPACE + +// EOF diff --git a/vnr/wintimer/wintimerbase.h b/vnr/wintimer/wintimerbase.h new file mode 100644 index 0000000..b1999f7 --- /dev/null +++ b/vnr/wintimer/wintimerbase.h @@ -0,0 +1,68 @@ +#pragma once + +// wintimerbase.h +// 6/6/2012 jichi +// +// Internal header for wintimer base class. + +#include "sakurakit/skglobal.h" +#include + +#ifdef QT_CORE_LIB +# include +#else +# include +#endif // QT_CORE_LIB + +#ifndef WINTIMER_BEGIN_NAMESPACE +# define WINTIMER_BEGIN_NAMESPACE +#endif +#ifndef WINTIMER_END_NAMESPACE +# define WINTIMER_END_NAMESPACE +#endif + +WINTIMER_BEGIN_NAMESPACE + +/// Internal base class for WinTimer +class WinTimerBase +{ + SK_CLASS(WinTimerBase) + SK_DISABLE_COPY(WinTimerBase) + + // - Types - +public: + typedef std::function function_type; +#ifndef QT_CORE_LIB + typedef HWND WId; +#endif // QT_CORE_LIB + + // - Methods - +public: + /// Construct a timer with the parent window handle. + WinTimerBase() + : parentWindow(0), // use 0 instead of nullptr to be consistent with Qt5 + interval(0), singleShot(false), active(false) {} + + bool isSingleShot() const { return singleShot; } + bool isActive() const { return active; } + + /// Start TimerProc + void start(); + /// Stop TimerProc + void stop(); + /// Invoke the callback. This function is the callback of the underlying TimerProc + void trigger() { function(); } + + // - Fields - +protected: + static WId globalWindow; + + WId parentWindow; + int interval; + bool singleShot; + bool active; + function_type function; + +}; + +WINTIMER_END_NAMESPACE