├── .gitignore ├── CMake ├── Default.cmake ├── DefaultCXX.cmake ├── DefaultFortran.cmake └── Utils.cmake ├── CMakeLists.txt ├── Charon.sln ├── Charon.vcxproj ├── Charon.vcxproj.filters ├── Charon.vcxproj.user ├── dllmain.cpp ├── features ├── AltBehaviour.cpp ├── AlwaysD3D.cpp ├── CustomFlyingTextPacket.cpp ├── CustomPacket.cpp ├── DebugMode.cpp ├── Dialog.cpp ├── DrawAllStates.cpp ├── Drawing.cpp ├── ExperienceMod.cpp ├── GameLog.cpp ├── GameLoop.cpp ├── Input.cpp ├── ItemQoL.cpp ├── MPQLoader.cpp ├── MapReveal.cpp ├── MapUnits.cpp ├── Misc.cpp ├── Omnivision.cpp ├── PacketsClient.cpp ├── PreventSockets.cpp ├── RoomInit.cpp ├── Settings.cpp ├── Swatch.cpp ├── Template.cpp ├── TxtOverride.cpp ├── Ubers.cpp └── Weather.cpp ├── framework ├── feature.cpp ├── ghidra.extensions.cpp ├── hook.cpp └── utilities.cpp ├── headers ├── D2Structs.h ├── common.h ├── dialog.h ├── feature.h ├── ghidra.h ├── ghidra │ ├── enums.h │ ├── main.h │ ├── naked.h │ ├── readme.md │ └── user.extensions.h ├── hook.h ├── remote.h └── utilities.h └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.vs 2 | /Debug 3 | /Release 4 | /features/LocalTest.cpp 5 | node_modules -------------------------------------------------------------------------------- /CMake/Default.cmake: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Command for variable_watch. This command issues error message, if a variable 3 | # is changed. If variable PROPERTY_READER_GUARD_DISABLED is TRUE nothing happens 4 | # variable_watch( property_reader_guard) 5 | ################################################################################ 6 | function(property_reader_guard VARIABLE ACCESS VALUE CURRENT_LIST_FILE STACK) 7 | if("${PROPERTY_READER_GUARD_DISABLED}") 8 | return() 9 | endif() 10 | 11 | if("${ACCESS}" STREQUAL "MODIFIED_ACCESS") 12 | message(FATAL_ERROR 13 | " Variable ${VARIABLE} is not supposed to be changed.\n" 14 | " It is used only for reading target property ${VARIABLE}.\n" 15 | " Use\n" 16 | " set_target_properties(\"\" PROPERTIES \"${VARIABLE}\" \"\")\n" 17 | " or\n" 18 | " set_target_properties(\"\" PROPERTIES \"${VARIABLE}_\" \"\")\n" 19 | " instead.\n") 20 | endif() 21 | endfunction() 22 | 23 | ################################################################################ 24 | # Create variable with generator expression that expands to value of 25 | # target property _. If property is empty or not set then property 26 | # is used instead. Variable has watcher property_reader_guard that 27 | # doesn't allow to edit it. 28 | # create_property_reader() 29 | # Input: 30 | # name - Name of watched property and output variable 31 | ################################################################################ 32 | function(create_property_reader NAME) 33 | set(PROPERTY_READER_GUARD_DISABLED TRUE) 34 | set(CONFIG_VALUE "$>>>") 35 | set(IS_CONFIG_VALUE_EMPTY "$") 36 | set(GENERAL_VALUE "$>") 37 | set("${NAME}" "$" PARENT_SCOPE) 38 | variable_watch("${NAME}" property_reader_guard) 39 | endfunction() 40 | 41 | ################################################################################ 42 | # Set property $_${PROPS_CONFIG_U} of ${PROPS_TARGET} to 43 | # set_config_specific_property( ) 44 | # Input: 45 | # name - Prefix of property name 46 | # value - New value 47 | ################################################################################ 48 | function(set_config_specific_property NAME VALUE) 49 | set_target_properties("${PROPS_TARGET}" PROPERTIES "${NAME}_${PROPS_CONFIG_U}" "${VALUE}") 50 | endfunction() 51 | 52 | ################################################################################ 53 | 54 | create_property_reader("TARGET_NAME") 55 | create_property_reader("OUTPUT_DIRECTORY") 56 | 57 | set_config_specific_property("TARGET_NAME" "${PROPS_TARGET}") 58 | set_config_specific_property("OUTPUT_NAME" "${TARGET_NAME}") 59 | set_config_specific_property("ARCHIVE_OUTPUT_NAME" "${TARGET_NAME}") 60 | set_config_specific_property("LIBRARY_OUTPUT_NAME" "${TARGET_NAME}") 61 | set_config_specific_property("RUNTIME_OUTPUT_NAME" "${TARGET_NAME}") 62 | 63 | set_config_specific_property("ARCHIVE_OUTPUT_DIRECTORY" "${OUTPUT_DIRECTORY}") 64 | set_config_specific_property("LIBRARY_OUTPUT_DIRECTORY" "${OUTPUT_DIRECTORY}") 65 | set_config_specific_property("RUNTIME_OUTPUT_DIRECTORY" "${OUTPUT_DIRECTORY}") -------------------------------------------------------------------------------- /CMake/DefaultCXX.cmake: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/Default.cmake") 2 | 3 | set_config_specific_property("OUTPUT_DIRECTORY" "${CMAKE_SOURCE_DIR}$<$>:/${CMAKE_VS_PLATFORM_NAME}>/${PROPS_CONFIG}") 4 | 5 | if(MSVC) 6 | create_property_reader("DEFAULT_CXX_EXCEPTION_HANDLING") 7 | create_property_reader("DEFAULT_CXX_DEBUG_INFORMATION_FORMAT") 8 | 9 | set_target_properties("${PROPS_TARGET}" PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") 10 | set_config_specific_property("DEFAULT_CXX_EXCEPTION_HANDLING" "/EHsc") 11 | set_config_specific_property("DEFAULT_CXX_DEBUG_INFORMATION_FORMAT" "/Zi") 12 | endif() 13 | -------------------------------------------------------------------------------- /CMake/DefaultFortran.cmake: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/Default.cmake") 2 | 3 | set_config_specific_property("OUTPUT_DIRECTORY" "${CMAKE_CURRENT_SOURCE_DIR}$<$>:/${CMAKE_VS_PLATFORM_NAME}>/${PROPS_CONFIG}") 4 | 5 | get_target_property(${PROPS_TARGET}_BINARY_DIR ${PROPS_TARGET} BINARY_DIR) 6 | set(DEFAULT_FORTRAN_MODULES_DIR "${${PROPS_TARGET}_BINARY_DIR}/${PROPS_TARGET}.Modules.dir") 7 | set_target_properties(${PROPS_TARGET} PROPERTIES Fortran_MODULE_DIRECTORY ${DEFAULT_FORTRAN_MODULES_DIR}) 8 | 9 | if(${CMAKE_GENERATOR} MATCHES "Visual Studio") 10 | # Hack for visual studio generator (https://gitlab.kitware.com/cmake/cmake/issues/19552) 11 | add_custom_command(TARGET ${PROPS_TARGET} PRE_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory $/${CMAKE_CFG_INTDIR}) 12 | endif() -------------------------------------------------------------------------------- /CMake/Utils.cmake: -------------------------------------------------------------------------------- 1 | # utils file for projects came from visual studio solution with cmake-converter. 2 | 3 | ################################################################################ 4 | # Wrap each token of the command with condition 5 | ################################################################################ 6 | cmake_policy(PUSH) 7 | cmake_policy(SET CMP0054 NEW) 8 | macro(prepare_commands) 9 | unset(TOKEN_ROLE) 10 | unset(COMMANDS) 11 | foreach(TOKEN ${ARG_COMMANDS}) 12 | if("${TOKEN}" STREQUAL "COMMAND") 13 | set(TOKEN_ROLE "KEYWORD") 14 | elseif("${TOKEN_ROLE}" STREQUAL "KEYWORD") 15 | set(TOKEN_ROLE "CONDITION") 16 | elseif("${TOKEN_ROLE}" STREQUAL "CONDITION") 17 | set(TOKEN_ROLE "COMMAND") 18 | elseif("${TOKEN_ROLE}" STREQUAL "COMMAND") 19 | set(TOKEN_ROLE "ARG") 20 | endif() 21 | 22 | if("${TOKEN_ROLE}" STREQUAL "KEYWORD") 23 | list(APPEND COMMANDS "${TOKEN}") 24 | elseif("${TOKEN_ROLE}" STREQUAL "CONDITION") 25 | set(CONDITION ${TOKEN}) 26 | elseif("${TOKEN_ROLE}" STREQUAL "COMMAND") 27 | list(APPEND COMMANDS "$<$:${DUMMY}>$<${CONDITION}:${TOKEN}>") 28 | elseif("${TOKEN_ROLE}" STREQUAL "ARG") 29 | list(APPEND COMMANDS "$<${CONDITION}:${TOKEN}>") 30 | endif() 31 | endforeach() 32 | endmacro() 33 | cmake_policy(POP) 34 | 35 | ################################################################################ 36 | # Transform all the tokens to absolute paths 37 | ################################################################################ 38 | macro(prepare_output) 39 | unset(OUTPUT) 40 | foreach(TOKEN ${ARG_OUTPUT}) 41 | if(IS_ABSOLUTE ${TOKEN}) 42 | list(APPEND OUTPUT "${TOKEN}") 43 | else() 44 | list(APPEND OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/${TOKEN}") 45 | endif() 46 | endforeach() 47 | endmacro() 48 | 49 | ################################################################################ 50 | # Parse add_custom_command_if args. 51 | # 52 | # Input: 53 | # PRE_BUILD - Pre build event option 54 | # PRE_LINK - Pre link event option 55 | # POST_BUILD - Post build event option 56 | # TARGET - Target 57 | # OUTPUT - List of output files 58 | # DEPENDS - List of files on which the command depends 59 | # COMMANDS - List of commands(COMMAND condition1 commannd1 args1 COMMAND 60 | # condition2 commannd2 args2 ...) 61 | # Output: 62 | # OUTPUT - Output files 63 | # DEPENDS - Files on which the command depends 64 | # COMMENT - Comment 65 | # PRE_BUILD - TRUE/FALSE 66 | # PRE_LINK - TRUE/FALSE 67 | # POST_BUILD - TRUE/FALSE 68 | # TARGET - Target name 69 | # COMMANDS - Prepared commands(every token is wrapped in CONDITION) 70 | # NAME - Unique name for custom target 71 | # STEP - PRE_BUILD/PRE_LINK/POST_BUILD 72 | ################################################################################ 73 | function(add_custom_command_if_parse_arguments) 74 | cmake_parse_arguments("ARG" "PRE_BUILD;PRE_LINK;POST_BUILD" "TARGET;COMMENT" "DEPENDS;OUTPUT;COMMANDS" ${ARGN}) 75 | 76 | if(WIN32) 77 | set(DUMMY "cd.") 78 | elseif(UNIX) 79 | set(DUMMY "true") 80 | endif() 81 | 82 | prepare_commands() 83 | prepare_output() 84 | 85 | set(DEPENDS "${ARG_DEPENDS}") 86 | set(COMMENT "${ARG_COMMENT}") 87 | set(PRE_BUILD "${ARG_PRE_BUILD}") 88 | set(PRE_LINK "${ARG_PRE_LINK}") 89 | set(POST_BUILD "${ARG_POST_BUILD}") 90 | set(TARGET "${ARG_TARGET}") 91 | if(PRE_BUILD) 92 | set(STEP "PRE_BUILD") 93 | elseif(PRE_LINK) 94 | set(STEP "PRE_LINK") 95 | elseif(POST_BUILD) 96 | set(STEP "POST_BUILD") 97 | endif() 98 | set(NAME "${TARGET}_${STEP}") 99 | 100 | set(OUTPUT "${OUTPUT}" PARENT_SCOPE) 101 | set(DEPENDS "${DEPENDS}" PARENT_SCOPE) 102 | set(COMMENT "${COMMENT}" PARENT_SCOPE) 103 | set(PRE_BUILD "${PRE_BUILD}" PARENT_SCOPE) 104 | set(PRE_LINK "${PRE_LINK}" PARENT_SCOPE) 105 | set(POST_BUILD "${POST_BUILD}" PARENT_SCOPE) 106 | set(TARGET "${TARGET}" PARENT_SCOPE) 107 | set(COMMANDS "${COMMANDS}" PARENT_SCOPE) 108 | set(STEP "${STEP}" PARENT_SCOPE) 109 | set(NAME "${NAME}" PARENT_SCOPE) 110 | endfunction() 111 | 112 | ################################################################################ 113 | # Add conditional custom command 114 | # 115 | # Generating Files 116 | # The first signature is for adding a custom command to produce an output: 117 | # add_custom_command_if( 118 | # 119 | # 120 | # 121 | # [COMMAND condition command2 [args2...]] 122 | # [DEPENDS [depends...]] 123 | # [COMMENT comment] 124 | # 125 | # Build Events 126 | # add_custom_command_if( 127 | # 128 | # 129 | # 130 | # [COMMAND condition command2 [args2...]] 131 | # [COMMENT comment] 132 | # 133 | # Input: 134 | # output - Output files the command is expected to produce 135 | # condition - Generator expression for wrapping the command 136 | # command - Command-line(s) to execute at build time. 137 | # args - Command`s args 138 | # depends - Files on which the command depends 139 | # comment - Display the given message before the commands are executed at 140 | # build time. 141 | # PRE_BUILD - Run before any other rules are executed within the target 142 | # PRE_LINK - Run after sources have been compiled but before linking the 143 | # binary 144 | # POST_BUILD - Run after all other rules within the target have been 145 | # executed 146 | ################################################################################ 147 | function(add_custom_command_if) 148 | add_custom_command_if_parse_arguments(${ARGN}) 149 | 150 | if(OUTPUT AND TARGET) 151 | message(FATAL_ERROR "Wrong syntax. A TARGET and OUTPUT can not both be specified.") 152 | endif() 153 | 154 | if(OUTPUT) 155 | add_custom_command(OUTPUT ${OUTPUT} 156 | ${COMMANDS} 157 | DEPENDS ${DEPENDS} 158 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 159 | COMMENT ${COMMENT}) 160 | elseif(TARGET) 161 | if(PRE_BUILD AND NOT ${CMAKE_GENERATOR} MATCHES "Visual Studio") 162 | add_custom_target( 163 | ${NAME} 164 | ${COMMANDS} 165 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 166 | COMMENT ${COMMENT}) 167 | add_dependencies(${TARGET} ${NAME}) 168 | else() 169 | add_custom_command( 170 | TARGET ${TARGET} 171 | ${STEP} 172 | ${COMMANDS} 173 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 174 | COMMENT ${COMMENT}) 175 | endif() 176 | else() 177 | message(FATAL_ERROR "Wrong syntax. A TARGET or OUTPUT must be specified.") 178 | endif() 179 | endfunction() 180 | 181 | ################################################################################ 182 | # Use props file for a target and configs 183 | # use_props( ) 184 | # Inside there are following variables: 185 | # PROPS_TARGET - 186 | # PROPS_CONFIG - One of 187 | # PROPS_CONFIG_U - Uppercase PROPS_CONFIG 188 | # Input: 189 | # target - Target to apply props file 190 | # configs - Build configurations to apply props file 191 | # props_file - CMake script 192 | ################################################################################ 193 | macro(use_props TARGET CONFIGS PROPS_FILE) 194 | set(PROPS_TARGET "${TARGET}") 195 | foreach(PROPS_CONFIG ${CONFIGS}) 196 | string(TOUPPER "${PROPS_CONFIG}" PROPS_CONFIG_U) 197 | 198 | get_filename_component(ABSOLUTE_PROPS_FILE "${PROPS_FILE}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_LIST_DIR}") 199 | if(EXISTS "${ABSOLUTE_PROPS_FILE}") 200 | include("${ABSOLUTE_PROPS_FILE}") 201 | else() 202 | message(WARNING "Corresponding cmake file from props \"${ABSOLUTE_PROPS_FILE}\" doesn't exist") 203 | endif() 204 | endforeach() 205 | endmacro() 206 | 207 | ################################################################################ 208 | # Function for MSVC precompiled headers 209 | # add_precompiled_header( ) 210 | # Input: 211 | # target - Target to which add precompiled header 212 | # precompiled_header - Name of precompiled header 213 | # precompiled_source - Name of precompiled source file 214 | ################################################################################ 215 | function(add_precompiled_header TARGET PRECOMPILED_HEADER PRECOMPILED_SOURCE) 216 | get_target_property(SOURCES "${TARGET}" SOURCES) 217 | list(REMOVE_ITEM SOURCES "${PRECOMPILED_SOURCE}") 218 | 219 | if(MSVC) 220 | set(PRECOMPILED_BINARY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${PROJECT_NAME}.pch") 221 | 222 | set_source_files_properties( 223 | "${PRECOMPILED_SOURCE}" 224 | PROPERTIES 225 | COMPILE_OPTIONS "/Yc${PRECOMPILED_HEADER};/Fp${PRECOMPILED_BINARY}" 226 | OBJECT_OUTPUTS "${PRECOMPILED_BINARY}") 227 | 228 | set_source_files_properties( 229 | ${SOURCES} 230 | PROPERTIES 231 | COMPILE_OPTIONS "$<$,$>:/Yu${PRECOMPILED_HEADER};/Fp${PRECOMPILED_BINARY}>" 232 | OBJECT_DEPENDS "${PRECOMPILED_BINARY}") 233 | endif() 234 | 235 | list(INSERT SOURCES 0 "${PRECOMPILED_SOURCE}") 236 | set_target_properties("${TARGET}" PROPERTIES SOURCES "${SOURCES}") 237 | endfunction() 238 | 239 | ################################################################################ 240 | # Add compile options to source file 241 | # source_file_compile_options( [compile_options...]) 242 | # Input: 243 | # source_file - Source file 244 | # compile_options - Options to add to COMPILE_FLAGS property 245 | ################################################################################ 246 | function(source_file_compile_options SOURCE_FILE) 247 | if("${ARGC}" LESS_EQUAL "1") 248 | return() 249 | endif() 250 | 251 | get_source_file_property(COMPILE_OPTIONS "${SOURCE_FILE}" COMPILE_OPTIONS) 252 | 253 | if(COMPILE_OPTIONS) 254 | list(APPEND COMPILE_OPTIONS ${ARGN}) 255 | else() 256 | set(COMPILE_OPTIONS "${ARGN}") 257 | endif() 258 | 259 | set_source_files_properties("${SOURCE_FILE}" PROPERTIES COMPILE_OPTIONS "${COMPILE_OPTIONS}") 260 | endfunction() 261 | 262 | ################################################################################ 263 | # Default properties of visual studio projects 264 | ################################################################################ 265 | set(DEFAULT_CXX_PROPS "${CMAKE_CURRENT_LIST_DIR}/DefaultCXX.cmake") 266 | set(DEFAULT_Fortran_PROPS "${CMAKE_CURRENT_LIST_DIR}/DefaultFortran.cmake") 267 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15.0 FATAL_ERROR) 2 | 3 | set(CMAKE_SYSTEM_VERSION 10.0 CACHE STRING "" FORCE) 4 | 5 | project(Charon CXX) 6 | 7 | 8 | ################################################################################ 9 | # Set target arch type if empty. Visual studio solution generator provides it. 10 | ################################################################################ 11 | if(NOT CMAKE_VS_PLATFORM_NAME) 12 | set(CMAKE_VS_PLATFORM_NAME "x86") 13 | endif() 14 | message("${CMAKE_VS_PLATFORM_NAME} architecture in use") 15 | 16 | if(NOT ("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x86")) 17 | message(FATAL_ERROR "${CMAKE_VS_PLATFORM_NAME} arch is not supported!") 18 | endif() 19 | 20 | ################################################################################ 21 | # Global configuration types 22 | ################################################################################ 23 | set(CMAKE_CONFIGURATION_TYPES 24 | "Debug" 25 | "Release" 26 | CACHE STRING "" FORCE 27 | ) 28 | 29 | ################################################################################ 30 | # Global compiler options 31 | ################################################################################ 32 | if(MSVC) 33 | # remove default flags provided with CMake for MSVC 34 | set(CMAKE_CXX_FLAGS "") 35 | set(CMAKE_CXX_FLAGS_DEBUG "") 36 | set(CMAKE_CXX_FLAGS_RELEASE "") 37 | endif() 38 | 39 | ################################################################################ 40 | # Global linker options 41 | ################################################################################ 42 | if(MSVC) 43 | # remove default flags provided with CMake for MSVC 44 | set(CMAKE_EXE_LINKER_FLAGS "") 45 | set(CMAKE_MODULE_LINKER_FLAGS "") 46 | set(CMAKE_SHARED_LINKER_FLAGS "") 47 | set(CMAKE_STATIC_LINKER_FLAGS "") 48 | set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS}") 49 | set(CMAKE_MODULE_LINKER_FLAGS_DEBUG "${CMAKE_MODULE_LINKER_FLAGS}") 50 | set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS}") 51 | set(CMAKE_STATIC_LINKER_FLAGS_DEBUG "${CMAKE_STATIC_LINKER_FLAGS}") 52 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS}") 53 | set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS}") 54 | set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS}") 55 | set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS}") 56 | endif() 57 | 58 | ################################################################################ 59 | # Nuget packages function stub. 60 | ################################################################################ 61 | function(use_package TARGET PACKAGE VERSION) 62 | message(WARNING "No implementation of use_package. Create yours. " 63 | "Package \"${PACKAGE}\" with version \"${VERSION}\" " 64 | "for target \"${TARGET}\" is ignored!") 65 | endfunction() 66 | 67 | ################################################################################ 68 | # Common utils 69 | ################################################################################ 70 | include(CMake/Utils.cmake) 71 | 72 | ################################################################################ 73 | # Additional Global Settings(add specific info there) 74 | ################################################################################ 75 | include(CMake/GlobalSettingsInclude.cmake OPTIONAL) 76 | 77 | ################################################################################ 78 | # Use solution folders feature 79 | ################################################################################ 80 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 81 | 82 | ################################################################################ 83 | # Sub-projects 84 | ################################################################################ 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | set(PROJECT_NAME Charon) 113 | 114 | ################################################################################ 115 | # Source groups 116 | ################################################################################ 117 | #set(no_group_source_files 118 | # "features/*.cpp" 119 | # "framework/*.cpp" 120 | # "headers/*.h" 121 | #) 122 | 123 | file(GLOB_RECURSE no_group_source_files RELATIVE ${CMAKE_SOURCE_DIR} "features/*.cpp" "framework/*.cpp") 124 | source_group("" FILES ${no_group_source_files}) 125 | 126 | set(Source_Files 127 | "dllmain.cpp" 128 | ) 129 | source_group("Source Files" FILES ${Source_Files}) 130 | 131 | set(ALL_FILES 132 | ${no_group_source_files} 133 | ${Source_Files} 134 | ) 135 | 136 | ################################################################################ 137 | # Target 138 | ################################################################################ 139 | add_library(${PROJECT_NAME} SHARED ${ALL_FILES}) 140 | target_link_libraries(${PROJECT_NAME} Crypt32.lib) 141 | 142 | use_props(${PROJECT_NAME} "${CMAKE_CONFIGURATION_TYPES}" "${DEFAULT_CXX_PROPS}") 143 | set(ROOT_NAMESPACE Charon) 144 | 145 | set_target_properties(${PROJECT_NAME} PROPERTIES 146 | VS_GLOBAL_KEYWORD "Win32Proj" 147 | ) 148 | set_target_properties(${PROJECT_NAME} PROPERTIES 149 | INTERPROCEDURAL_OPTIMIZATION_RELEASE "TRUE" 150 | ) 151 | ################################################################################ 152 | # MSVC runtime library 153 | ################################################################################ 154 | get_property(MSVC_RUNTIME_LIBRARY_DEFAULT TARGET ${PROJECT_NAME} PROPERTY MSVC_RUNTIME_LIBRARY) 155 | string(CONCAT "MSVC_RUNTIME_LIBRARY_STR" 156 | $<$: 157 | MultiThreadedDebug 158 | > 159 | $<$: 160 | MultiThreaded 161 | > 162 | $<$,$>>:${MSVC_RUNTIME_LIBRARY_DEFAULT}> 163 | ) 164 | set_target_properties(${PROJECT_NAME} PROPERTIES MSVC_RUNTIME_LIBRARY ${MSVC_RUNTIME_LIBRARY_STR}) 165 | 166 | ################################################################################ 167 | # Include directories 168 | ################################################################################ 169 | target_include_directories(${PROJECT_NAME} PUBLIC 170 | "${CMAKE_CURRENT_SOURCE_DIR}/." 171 | ) 172 | 173 | ################################################################################ 174 | # Compile definitions 175 | ################################################################################ 176 | target_compile_definitions(${PROJECT_NAME} PRIVATE 177 | "$<$:" 178 | "_DEBUG" 179 | ">" 180 | "$<$:" 181 | "NDEBUG" 182 | ">" 183 | "WIN32;" 184 | "CHARON_EXPORTS;" 185 | "_WINDOWS;" 186 | "_USRDLL;" 187 | "UNICODE;" 188 | "_UNICODE" 189 | ) 190 | 191 | ################################################################################ 192 | # Compile and link options 193 | ################################################################################ 194 | if(MSVC) 195 | target_compile_options(${PROJECT_NAME} PRIVATE 196 | $<$: 197 | /Gm- 198 | > 199 | $<$: 200 | /Oi; 201 | /Gy 202 | > 203 | /permissive-; 204 | /std:c++17; 205 | /sdl; 206 | /W4; 207 | ${DEFAULT_CXX_DEBUG_INFORMATION_FORMAT}; 208 | /FAcs; 209 | ${DEFAULT_CXX_EXCEPTION_HANDLING}; 210 | /Y- 211 | ) 212 | target_link_options(${PROJECT_NAME} PRIVATE 213 | $<$: 214 | /INCREMENTAL 215 | > 216 | $<$: 217 | /OPT:REF; 218 | /OPT:ICF; 219 | /INCREMENTAL:NO 220 | > 221 | /DEBUG; 222 | /SUBSYSTEM:WINDOWS 223 | ) 224 | endif() 225 | 226 | -------------------------------------------------------------------------------- /Charon.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30011.22 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Charon", "Charon.vcxproj", "{EDE17BA5-789C-4972-9A38-89BCE6AB2ECE}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x86 = Debug|x86 11 | Release|x86 = Release|x86 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {EDE17BA5-789C-4972-9A38-89BCE6AB2ECE}.Debug|x86.ActiveCfg = Debug|Win32 15 | {EDE17BA5-789C-4972-9A38-89BCE6AB2ECE}.Debug|x86.Build.0 = Debug|Win32 16 | {EDE17BA5-789C-4972-9A38-89BCE6AB2ECE}.Release|x86.ActiveCfg = Release|Win32 17 | {EDE17BA5-789C-4972-9A38-89BCE6AB2ECE}.Release|x86.Build.0 = Release|Win32 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {D0C40506-F234-4BAF-AB20-3D97C461FD5C} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Charon.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | 16.0 15 | {EDE17BA5-789C-4972-9A38-89BCE6AB2ECE} 16 | Win32Proj 17 | Charon 18 | 10.0 19 | 20 | 21 | 22 | DynamicLibrary 23 | true 24 | v142 25 | Unicode 26 | 27 | 28 | DynamicLibrary 29 | false 30 | v142 31 | false 32 | Unicode 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | false 48 | $(IncludePath) 49 | true 50 | 51 | 52 | false 53 | true 54 | 55 | 56 | 57 | $(ProjectDir);%(AdditionalIncludeDirectories) 58 | 59 | 60 | Level4 61 | true 62 | WIN32;_DEBUG;CHARON_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 63 | true 64 | stdcpp17 65 | MultiThreadedDebug 66 | false 67 | All 68 | 69 | 70 | Windows 71 | true 72 | false 73 | false 74 | false 75 | 76 | 77 | 78 | 79 | $(ProjectDir);%(AdditionalIncludeDirectories) 80 | 81 | 82 | Level4 83 | true 84 | true 85 | true 86 | WIN32;NDEBUG;CHARON_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 87 | true 88 | MultiThreaded 89 | stdcpp17 90 | All 91 | Disabled 92 | 93 | 94 | Windows 95 | false 96 | false 97 | true 98 | false 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Charon.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Source Files 47 | 48 | 49 | Source Files 50 | 51 | 52 | Source Files 53 | 54 | 55 | Source Files 56 | 57 | 58 | Source Files 59 | 60 | 61 | Source Files 62 | 63 | 64 | Source Files 65 | 66 | 67 | Source Files 68 | 69 | 70 | Source Files 71 | 72 | 73 | Source Files 74 | 75 | 76 | Source Files 77 | 78 | 79 | Source Files 80 | 81 | 82 | Source Files 83 | 84 | 85 | Source Files 86 | 87 | 88 | Source Files 89 | 90 | 91 | Source Files 92 | 93 | 94 | Source Files 95 | 96 | 97 | Source Files 98 | 99 | 100 | Source Files 101 | 102 | 103 | Source Files 104 | 105 | 106 | Source Files 107 | 108 | 109 | Source Files 110 | 111 | 112 | Source Files 113 | 114 | 115 | Source Files 116 | 117 | 118 | Source Files 119 | 120 | 121 | Source Files 122 | 123 | 124 | Source Files 125 | 126 | 127 | 128 | 129 | Header Files 130 | 131 | 132 | Header Files 133 | 134 | 135 | Header Files 136 | 137 | 138 | Header Files 139 | 140 | 141 | Header Files 142 | 143 | 144 | Header Files 145 | 146 | 147 | Header Files 148 | 149 | 150 | -------------------------------------------------------------------------------- /Charon.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | WindowsLocalDebugger 7 | 8 | 9 | 10 | 11 | $(DIABLO2_PATH)\Game.exe 12 | WindowsLocalDebugger 13 | $(DIABLO2_PATH) 14 | -loaddll "$(TargetPath)" -w 15 | 16 | 17 | $(DIABLO2_PATH)\Game.exe 18 | WindowsLocalDebugger 19 | $(DIABLO2_PATH) 20 | -loaddll "$(TargetPath)" -w 21 | 22 | 23 | 24 | 25 | WindowsLocalDebugger 26 | 27 | 28 | 29 | 30 | true 31 | 32 | -------------------------------------------------------------------------------- /dllmain.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Charon - For the finer life in Diablo 2. 3 | */ 4 | 5 | 6 | #define DEFINE_REMOTE_REFERENCES true 7 | 8 | #include 9 | #include "headers/common.h" 10 | #include "headers/feature.h" 11 | #include "headers/remote.h" 12 | #include "headers/hook.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | HINSTANCE hInst = nullptr; 19 | extern wchar_t saveDir[512] = { 0 }; 20 | 21 | BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { 22 | hInst = (HINSTANCE)hModule; 23 | PWSTR roamingAppDataPath; 24 | HRESULT ret; 25 | size_t c; 26 | 27 | switch (ul_reason_for_call) 28 | { 29 | case DLL_PROCESS_ATTACH: 30 | ret = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &roamingAppDataPath); 31 | 32 | if (ret == S_OK) { 33 | swprintf_s(saveDir, L"%ws\\Charon", roamingAppDataPath); 34 | } 35 | else { 36 | GetModuleFileName(hModule, saveDir, 512); 37 | for (c = 0; c < 512; c++) { 38 | if (!saveDir[c]) { 39 | break; 40 | } 41 | } 42 | 43 | while (--c >= 0 && saveDir[c] != L'\\'); 44 | 45 | if (c >= 0 && c < 512) { 46 | saveDir[c] = 0; 47 | } 48 | } 49 | 50 | swprintf_s(settingsPath, L"%ws\\settings.txt", saveDir); 51 | CoTaskMemFree(roamingAppDataPath); 52 | CreateDirectoryW(saveDir, NULL); 53 | LoadSettings(); 54 | 55 | DisableThreadLibraryCalls(hModule); 56 | for (Feature* f = Features; f; f = f->next) { 57 | f->init(); 58 | } 59 | 60 | gamelog << COLOR(2) << "Charon loaded." << std::endl << COLOR(3) << "Press F11 to edit settings." << std::endl; 61 | break; 62 | case DLL_PROCESS_DETACH: 63 | for (Feature* f = Features; f; f = f->next) { 64 | f->deinit(); 65 | } 66 | for (std::pair element : OriginalBytes) { 67 | RevertBytes(element.first, 1); 68 | } 69 | break; 70 | } 71 | 72 | return TRUE; 73 | } 74 | -------------------------------------------------------------------------------- /features/AltBehaviour.cpp: -------------------------------------------------------------------------------- 1 | #include "headers/feature.h" 2 | #include "headers/common.h" 3 | #include "headers/hook.h" 4 | #include "headers/remote.h" 5 | 6 | namespace AltBehavior { 7 | 8 | bool drawItemsOnFloor = false; 9 | bool unitUnderCursor = false; 10 | 11 | void __stdcall setSelectedUnitHook(D2::Types::UnitAny* unit) { 12 | static bool lastState; 13 | 14 | unitUnderCursor = unit != nullptr && unit->dwType != 4; 15 | } 16 | 17 | bool isNewBehavior() { 18 | return Settings["altToggle"]; 19 | } 20 | 21 | __declspec(naked) void interceptSetSelectedUnit() { 22 | static ASMPTR jumpback = 0x466de9; 23 | static ASMPTR setSelectedUnitHookPtr = (DWORD) setSelectedUnitHook; 24 | __asm { 25 | 26 | pushad 27 | push eax 28 | call setSelectedUnitHookPtr 29 | popad 30 | 31 | // Original code 32 | PUSH EBP 33 | MOV EBP,ESP 34 | SUB ESP,0x200 35 | JMP jumpback 36 | } 37 | } 38 | 39 | 40 | 41 | __declspec(naked) void interceptIfIsAltShowItemsSet() { 42 | static ASMPTR continueIf = 0x457264 + 6; 43 | static ASMPTR skipDrawItems = 0x45728e; 44 | static ASMPTR checkBorders = 0x45726c; 45 | static ASMPTR isNewBehaviorPtr = (DWORD) isNewBehavior; 46 | __asm { 47 | // if we select a monster or object, use the game mechanics 48 | cmp unitUnderCursor, 0; 49 | jnz old 50 | 51 | // ask charon if change behavior is preferred 52 | call isNewBehaviorPtr; 53 | cmp eax, 0; 54 | jz old; 55 | 56 | // hooked alternative behavior 57 | cmp drawItemsOnFloor, 0 58 | jz skipDraw 59 | JMP checkBorders 60 | 61 | old: // original code 62 | CMP DWORD PTR ds:0x7a27f4, ebx; // if (GFX_bShowUIFlag.AltShowItems != 0) 63 | JMP continueIf; // jump back 64 | 65 | skipDraw: 66 | JMP skipDrawItems 67 | } 68 | } 69 | 70 | class : public Feature { 71 | bool enabled = false, inGame = false; 72 | public: 73 | void setup() { 74 | if (Settings["altToggle"] && !enabled) { 75 | MemoryPatch(0x457264) << ASM::NOP << JUMP(interceptIfIsAltShowItemsSet); 76 | MemoryPatch(0x466de0) << JUMP(interceptSetSelectedUnit) << NOP_TO(0x466de9); 77 | enabled = true; 78 | } 79 | 80 | if (!Settings["altToggle"] && enabled) { 81 | MemoryPatch(0x457264) << REVERT(6); 82 | MemoryPatch(0x466de0) << REVERT(9); 83 | enabled = false; 84 | } 85 | } 86 | 87 | void gameLoop() { 88 | inGame = true; 89 | setup(); 90 | } 91 | 92 | void oogLoop() { 93 | inGame = false; 94 | setup(); 95 | } 96 | 97 | bool keyEvent(DWORD keyCode, bool down, DWORD flags) { 98 | if (inGame && Settings["altToggle"] && keyCode == 18 /*alt*/) { 99 | drawItemsOnFloor = !drawItemsOnFloor; 100 | return false; 101 | } 102 | 103 | return true; 104 | } 105 | 106 | } feature; 107 | 108 | } 109 | -------------------------------------------------------------------------------- /features/AlwaysD3D.cpp: -------------------------------------------------------------------------------- 1 | #include "headers/feature.h" 2 | #include "headers/common.h" 3 | #include "headers/hook.h" 4 | #include "headers/remote.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | REMOTEREF(DWORD, InternalWidth, 0x7c9138); 11 | REMOTEREF(DWORD, InternalHeight, 0x7c913c); 12 | REMOTEREF(DWORD, ScreenWidth, 0x71146c); 13 | REMOTEREF(DWORD, ScreenHeight, 0x711470); 14 | REMOTEREF(DWORD, ScreenMode, 0x7a5218); 15 | REMOTEREF(DWORD, Dunno, 0x7a2858); 16 | REMOTEREF(BOOL, FixAspect, 0x72da7c); 17 | REMOTEREF(LPDIRECTDRAW7, IDirectDraw, 0x970e60); 18 | REMOTEREF(LPDIRECT3D3, IDirect3D, 0x970e64); 19 | REMOTEREF(LPDIRECTDRAWSURFACE7, D3DSurfacePrimary, 0x970e68); 20 | REMOTEREF(LPDIRECTDRAWSURFACE7, D3DSurfaceSecondary, 0x970e6c); 21 | REMOTEREF(LPDIRECT3DDEVICE3, Direct3DDevice, 0x970e78); 22 | REMOTEREF(LPDIRECT3DVIEWPORT3, Direct3DViewport, 0x970e7c); 23 | REMOTEREF(DWORD, GameType, 0x7a0610); 24 | REMOTEPTR(DWORD, unknownInBeginScene, 0x970e54); 25 | REMOTEFUNC(void, D3D_DirectDrawPostSetup, (), 0x6b8f10); 26 | 27 | LPDIRECTDRAWSURFACE7 D3DSurfaceBackground = nullptr; 28 | bool bD3DFull = false; 29 | bool repaintsides = false; 30 | DWORD dwOldStyle = 0; 31 | DWORD dwOldStyleEx = 0; 32 | 33 | void D3D_DirectDrawScreenSetup() { 34 | InternalWidth = 800; 35 | InternalHeight = 600; 36 | IDirectDraw->SetCooperativeLevel(D2::hWnd, DDSCL_NORMAL | DDSCL_NOWINDOWCHANGES); 37 | if (!dwOldStyle) { 38 | dwOldStyle = GetWindowLong(D2::hWnd, GWL_STYLE); 39 | dwOldStyleEx = GetWindowLong(D2::hWnd, GWL_EXSTYLE); 40 | dwOldStyle &= ~(WS_MAXIMIZEBOX); 41 | dwOldStyle &= ~(WS_EX_TOPMOST); 42 | SetWindowLong(D2::hWnd, GWL_STYLE, dwOldStyle); 43 | SetWindowLong(D2::hWnd, GWL_EXSTYLE, dwOldStyleEx); 44 | } 45 | } 46 | 47 | void D3D_DirectDrawPostSetup_Intercept() { 48 | D3D_DirectDrawPostSetup(); 49 | } 50 | 51 | void ScreenSizeHook() { 52 | ScreenWidth = InternalWidth; 53 | ScreenHeight = InternalHeight; 54 | ScreenMode = 1; 55 | } 56 | 57 | void __stdcall GetWindowSizeByResolutionMode(int res, long* nWidth, long* nHeight) { 58 | *nWidth = InternalWidth; 59 | *nHeight = InternalHeight; 60 | } 61 | 62 | void ToggleFullscreen() { 63 | bD3DFull = !bD3DFull; 64 | 65 | if (bD3DFull) { 66 | ShowWindow(D2::hWnd, SW_RESTORE); 67 | SetWindowLong(D2::hWnd, GWL_STYLE, dwOldStyle & ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU)); 68 | ShowWindow(D2::hWnd, SW_MAXIMIZE); 69 | } 70 | else { 71 | ShowWindow(D2::hWnd, SW_RESTORE); 72 | SetWindowLong(D2::hWnd, GWL_STYLE, dwOldStyle); 73 | } 74 | 75 | repaintsides = true; 76 | SetWindowPos(D2::hWnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); 77 | } 78 | 79 | void __cdecl LogFileDivert(char* format, ...) { 80 | int res; 81 | va_list list; 82 | va_start(list, format); 83 | res = _vfprintf_l(stdout, format, NULL, list); 84 | va_end(list); 85 | puts(""); 86 | return; 87 | } 88 | 89 | HRESULT __stdcall CreatePrimarySurfaceIntercept(IDirectDraw7* ddi, LPDDSURFACEDESC2 ddsd, LPDIRECTDRAWSURFACE7* ppSurface, IUnknown* unknown) { 90 | DDSURFACEDESC2 caps{ sizeof(DDSURFACEDESC2) }; 91 | ddsd->dwFlags = DDSD_CAPS; 92 | ddsd->dwBackBufferCount = 0; 93 | ddsd->dwDepth = 1; 94 | ddsd->ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_3DDEVICE; 95 | 96 | HRESULT ret = ddi->CreateSurface(ddsd, ppSurface, unknown); 97 | 98 | if (ret == DD_OK) { 99 | IDirectDrawClipper* Clipper; 100 | IDirectDraw->CreateClipper(0, &Clipper, NULL); 101 | Clipper->SetHWnd(0, D2::hWnd); 102 | ppSurface[0]->SetClipper(Clipper); 103 | Clipper->Release(); 104 | } 105 | 106 | if (D3DSurfaceBackground == nullptr) { 107 | DDSURFACEDESC2 caps2{ sizeof(DDSURFACEDESC2) }; 108 | caps2.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; 109 | caps2.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; 110 | caps2.dwWidth = 32; 111 | caps2.dwHeight = 32; 112 | IDirectDraw->CreateSurface(&caps2, &D3DSurfaceBackground, NULL); 113 | } 114 | 115 | return ret; 116 | } 117 | 118 | HRESULT __stdcall CreateSecondarySurfaceIntercept(IDirectDrawSurface7* dds, LPDDSCAPS2 ddsc, LPDIRECTDRAWSURFACE7* ppSurface) { 119 | DDSURFACEDESC2 caps{ sizeof(DDSURFACEDESC2) }; 120 | caps.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; 121 | caps.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_3DDEVICE; 122 | caps.dwWidth = InternalWidth; 123 | caps.dwHeight = InternalHeight; 124 | return IDirectDraw->CreateSurface(&caps, ppSurface, NULL); 125 | } 126 | 127 | void ClearScreen() { 128 | D3DRECT r{ 0, 0, (long)InternalWidth, (long)InternalHeight }; 129 | Direct3DViewport->Clear2(1, &r, D3DCLEAR_TARGET, 0xFF000000, 0, 0); 130 | } 131 | 132 | // Keeps the game at a steady framerate without using too much CPU. 133 | // D2 doesn't do a great job at it by default, so we're helping out. 134 | void throttle() { 135 | // Single player doesn't need throttling. It runs at 25 fps already. 136 | if (State["inGame"] && GameType < 1) { 137 | return; 138 | } 139 | 140 | using frameDuration = std::chrono::duration>; // Wait for 40ms (25 fps) 141 | using std::chrono::system_clock; 142 | using std::this_thread::sleep_until; 143 | static system_clock::time_point nextFrame = system_clock::now(), now; 144 | 145 | now = system_clock::now(); 146 | if (now < nextFrame) { 147 | sleep_until(nextFrame); 148 | nextFrame += frameDuration{ 1 }; 149 | } 150 | else { 151 | nextFrame = system_clock::now() + frameDuration{ 1 }; 152 | } 153 | } 154 | 155 | std::wstring filterParams[6] = { 156 | L"Default", 157 | L"Point", 158 | L"Linear", 159 | L"Flat Cubic", 160 | L"Gaussian Cubic", 161 | L"Anisotropic", 162 | }; 163 | 164 | int filterValues[6] = { 165 | 0, 166 | D3DTFG_POINT, 167 | D3DTFG_LINEAR, 168 | D3DTFG_FLATCUBIC, 169 | D3DTFG_GAUSSIANCUBIC, 170 | D3DTFG_ANISOTROPIC, 171 | }; 172 | 173 | DWORD BeginScene() { 174 | // This is the highest filtering that retains the sprite outlines properly IMO. 175 | // Gaussian Cubic seems to deform the outlines a bit. 176 | DWORD filterType = filterValues[Settings["alwaysD3DFilter"]]; 177 | Direct3DDevice->SetTextureStageState(0, D3DTSS_MAGFILTER, filterType); 178 | Direct3DDevice->SetTextureStageState(0, D3DTSS_MINFILTER, filterType); 179 | Direct3DDevice->SetTextureStageState(0, D3DTSS_MIPFILTER, filterType); 180 | 181 | return true; 182 | } 183 | 184 | BOOL __fastcall FlipSurfaces() { 185 | RECT windowrect, targetrect{ 0 }; 186 | 187 | throttle(); 188 | 189 | GetClientRect(D2::hWnd, &windowrect); 190 | MapWindowPoints(D2::hWnd, NULL, (LPPOINT)&windowrect, 2); 191 | 192 | long newWidth = (windowrect.bottom - windowrect.top) * InternalWidth / InternalHeight; 193 | targetrect.top = windowrect.top; 194 | targetrect.bottom = windowrect.bottom; 195 | targetrect.left = (windowrect.left + windowrect.right - newWidth) / 2; 196 | targetrect.right = targetrect.left + newWidth; 197 | 198 | if (repaintsides) { 199 | D3DSurfacePrimary->Blt(&windowrect, D3DSurfaceBackground, NULL, DDBLT_WAIT, NULL); 200 | repaintsides = false; 201 | } 202 | 203 | DDBLTFX fx{ 0 }; 204 | fx.dwSize = sizeof(DDBLTFX); 205 | D3DSurfacePrimary->Blt(&targetrect, D3DSurfaceSecondary, NULL, DDBLT_WAIT, &fx); 206 | 207 | return TRUE; 208 | } 209 | 210 | void __fastcall UpdateD2INI(D2::Types::IniConfigStrc* d2ini) { 211 | d2ini->bASPECT = d2ini->bD3D = true; 212 | d2ini->bWINDOW = d2ini->b3DFX = d2ini->bOPENGL = d2ini->bRAVE = false; 213 | } 214 | 215 | BOOL __stdcall SetWindowPosStub(HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags) { 216 | return true; 217 | } 218 | 219 | namespace AlwaysD3D { 220 | 221 | class : public Feature { 222 | bool startFull = false, usingAD3D = false; 223 | public: 224 | void init() { 225 | if (Settings["alwaysD3D"]) { 226 | usingAD3D = true; 227 | // Prevent this MoveWindow call since it pushes the window off the screen. 228 | MemoryPatch(0x4f5b8b) << CALL(SetWindowPosStub) << ASM::NOP; 229 | 230 | MemoryPatch(0x405cb3) 231 | << BYTESEQ{ 0xc7, 0x45, 0x08 } 232 | << DWORD(6) 233 | << BYTESEQ{ 0x8d, 0x0e } 234 | << CALL(UpdateD2INI) 235 | << NOP_TO(0x405ced); // Force this renderer 236 | MemoryPatch(0x405cf1) 237 | << BYTESEQ{ 0xb3, 0x01, ASM::NOP } // Force this window mode 238 | << NOP_TO(0x405cf7); 239 | MemoryPatch(0x4f9050) 240 | << BYTESEQ{ 0x31, 0xC0 } 241 | << ASM::RET; // Skip all videos; xor EAX and return 242 | MemoryPatch(0x6b5003) 243 | << CALL(D3D_DirectDrawScreenSetup) 244 | << NOP_TO(0x6b5046) 245 | << SKIP(10) 246 | << NOP_TO(0x6b50d4); // Override resolution, coop level, and screen mode 247 | MemoryPatch(0x6b5510) << CALL(D3D_DirectDrawPostSetup_Intercept); 248 | MemoryPatch(0x410610) << JUMP(LogFileDivert); 249 | MemoryPatch(0x6b5130) << CALL(CreatePrimarySurfaceIntercept); 250 | MemoryPatch(0x6b517d) << CALL(CreateSecondarySurfaceIntercept); 251 | MemoryPatch(0x6b1c30) << JUMP(FlipSurfaces); 252 | MemoryPatch(0x6b57be) << CALL(BeginScene) << BYTESEQ{ 0xc2, 0x08, 0x00 }; 253 | MemoryPatch(0x4f5570) << JUMP(GetWindowSizeByResolutionMode); 254 | MemoryPatch(0x44ba30) << CALL(ScreenSizeHook) << NOP_TO(0x44ba7c); 255 | MemoryPatch(0x6b2030) << ASM::RET; // Disable min/mag filters. 256 | MemoryPatch(0x44f278) << NOP_TO(0x44f280); // Disable frame skipping 257 | 258 | 259 | HotkeyCallbacks[VK_RETURN] = [&](LPARAM options) -> BOOL { 260 | if (options & 0x20000000) { 261 | ToggleFullscreen(); 262 | return FALSE; 263 | } 264 | 265 | return TRUE; 266 | }; 267 | 268 | startFull = Settings["alwaysD3DStartFull"]; 269 | } 270 | } 271 | 272 | void allFinalDraw() { 273 | if (usingAD3D && startFull) { 274 | ToggleFullscreen(); 275 | startFull = false; 276 | } 277 | } 278 | } feature; 279 | 280 | } 281 | -------------------------------------------------------------------------------- /features/CustomFlyingTextPacket.cpp: -------------------------------------------------------------------------------- 1 | // Based on code by Jaenster 2 | 3 | #define _USE_MATH_DEFINES 4 | 5 | #include "headers/feature.h" 6 | #include "headers/hook.h" 7 | #include "headers/remote.h" 8 | #include "headers/utilities.h" 9 | #include 10 | #include 11 | 12 | const DPOINT popupoffset{ -1, -1 }; 13 | const double popupSpreadArc = 60.0, flyspeed = 120.0, flyduration = 0.6, timescale = 1 / flyduration, gravityAccel = -flyspeed * flyduration; 14 | const double popupArcStart = M_PI * (180.0 - popupSpreadArc) / 360.0, popupArcEnd = M_PI * (180.0 + popupSpreadArc) / 360.0; 15 | 16 | FlyingText::FlyingText(DPOINT pos, int value, char color) { 17 | this->pos = pos; 18 | this->color = color; 19 | this->value = value; 20 | this->counter = GetTickCount64(); 21 | double angle = randDoubleInRange(popupArcStart, popupArcEnd); 22 | this->delta = DPOINT{ cos(angle), sin(angle) }; 23 | } 24 | 25 | FlyingText::FlyingText(FlyingTextPacket *packet) { 26 | if (packet->subPacketId != FLYING_TEXT_PACKET_ID) throw "Incorrect packet!"; 27 | this->pos = packet->pos; 28 | this->color = packet->color; 29 | this->value = packet->value; 30 | this->counter = GetTickCount64(); 31 | double angle = randDoubleInRange(popupArcStart, popupArcEnd); 32 | this->delta = DPOINT{ cos(angle), sin(angle) }; 33 | } 34 | 35 | std::vector FlyingTexts; 36 | 37 | namespace CustomFlyingTextPacket { 38 | class : public Feature { 39 | public: 40 | void oogDraw() { 41 | if (FlyingTexts.size()) { 42 | FlyingTexts.clear(); 43 | } 44 | } 45 | 46 | void clientGetCustomData(char* pBytes, int nSize) { 47 | if (pBytes[0] == FLYING_TEXT_PACKET_ID && Settings["infoPopups"]) { 48 | FlyingTexts.push_back(FlyingText((FlyingTextPacket*)pBytes)); 49 | } 50 | } 51 | 52 | void gameUnitPostDraw() { 53 | for (size_t i = 0; i < FlyingTexts.size(); ++i) { 54 | FlyingText* current = &FlyingTexts[i]; 55 | 56 | double secondsElapsed = (double)(GetTickCount64() - current->counter) / 1000.0; 57 | double t = secondsElapsed * timescale; 58 | double ox = current->delta.x * t * flyspeed; 59 | double oy = current->delta.y * t * flyspeed + t * t * gravityAccel; 60 | 61 | if (secondsElapsed > flyduration) { 62 | FlyingTexts.erase(FlyingTexts.begin() + (i--)); 63 | continue; 64 | } 65 | 66 | std::wstring numberStr = std::to_wstring(current->value); 67 | std::wstring symbol = current->value < 0 ? L"-" : L"+"; 68 | std::wstring currentText = symbol + numberStr; 69 | POINT pos = current->pos.toScreen(); 70 | 71 | DWORD fontno = current->font, old = D2::SetFont(fontno), width, height = D2::GetTextSize(currentText.c_str(), &width, &fontno); 72 | D2::DrawGameText(currentText.c_str(), pos.x + (int)ox - (int)(width / 2), pos.y - (int)oy - (height / 2), current->color, false); 73 | D2::SetFont(old); 74 | } 75 | } 76 | } feature; 77 | 78 | } 79 | -------------------------------------------------------------------------------- /features/CustomPacket.cpp: -------------------------------------------------------------------------------- 1 | // Based on code by Jaenster 2 | 3 | #include "headers/feature.h" 4 | #include "headers/common.h" 5 | #include "headers/remote.h" 6 | #include "headers/hook.h" 7 | #include "headers/ghidra.h" 8 | 9 | REMOTEFUNC(void __fastcall, NET_D2GS_SERVER_ProcessClientMessage_InGame, (char* pBytes, size_t nSize), 0x53f3d0) 10 | REMOTEFUNC(void __fastcall, NET_D2GS_CLIENT_PacketHandle_to0xAF, (char* pBytes, size_t nSize), 0x45f7b0) 11 | 12 | struct ServerIncomingD2GSPacketEntry { 13 | void* pHandler; 14 | DWORD unknown; 15 | }; 16 | 17 | struct ClientIncomingD2GSPacketEntry { 18 | void* pHandler; 19 | long size; 20 | void* pUnitHandler; 21 | }; 22 | 23 | struct ClientOutgoingD2GSPacketSizeEntry { 24 | long size; 25 | }; 26 | 27 | enum class eD2ServerIncomingStatus { 28 | OK = 0x0, 29 | BAD_TARGET = 0x1, 30 | BAD_DATA = 0x2, 31 | BAD_PARAMS = 0x3, 32 | }; 33 | 34 | // These are protected locations, so you need to use MemoryPatch to change them 35 | REMOTEPTR(ServerIncomingD2GSPacketEntry, ServerIncomingD2GSPacketTable, 0x6e0d18) 36 | REMOTEPTR(ClientIncomingD2GSPacketEntry, ClientIncomingD2GSPacketTable, 0x7114d0) 37 | REMOTEPTR(long, ClientIncomingD2GSPacketSizeTable, 0x730ae8) 38 | REMOTEPTR(long, ClientOutgoingD2GSPacketSizeTable, 0x730dc0) 39 | 40 | void __fastcall ServerIncomingPacketHook_InGame (char* pBytes, int nSize) { 41 | CustomPacketHeader* header = (CustomPacketHeader*)(pBytes + 4); 42 | 43 | if (header->id == CUSTOM_PACKET_ID) { 44 | int clientId = *(int*)pBytes; 45 | for (Feature* f = Features; f; f = f->next) { 46 | f->serverGetCustomData(clientId, (char*)(header + 1), nSize - sizeof(CustomPacketHeader)); 47 | } 48 | } 49 | else { 50 | NET_D2GS_SERVER_ProcessClientMessage_InGame(pBytes, nSize); 51 | } 52 | } 53 | 54 | void __fastcall ClientIncomingPacketHook_InGame(char* pBytes, int nSize) { 55 | CustomPacketHeader* header = (CustomPacketHeader*)pBytes; 56 | 57 | if (header->id == CUSTOM_PACKET_ID) { 58 | for (Feature* f = Features; f; f = f->next) { 59 | f->clientGetCustomData((char*)(header + 1), header->size - sizeof(CustomPacketHeader)); 60 | } 61 | } 62 | else { 63 | NET_D2GS_CLIENT_PacketHandle_to0xAF(pBytes, nSize); 64 | } 65 | } 66 | 67 | void SetClientPacketHandler(char packetId, long size, void* handler = nullptr, void* unitHandler = nullptr) { 68 | MemoryPatch((DWORD)&ClientIncomingD2GSPacketTable[packetId]) << DATA(ClientIncomingD2GSPacketEntry{ 69 | handler, 70 | size, 71 | unitHandler, 72 | }); 73 | MemoryPatch((DWORD)&ClientIncomingD2GSPacketSizeTable[packetId]) << size; 74 | } 75 | 76 | void SetServerPacketHandler(char packetId, long size, void* handler = nullptr) { 77 | MemoryPatch((DWORD)&ServerIncomingD2GSPacketTable[packetId]) << DATA(ServerIncomingD2GSPacketEntry{ 78 | handler, 79 | 1, 80 | }); 81 | MemoryPatch((DWORD)&ClientOutgoingD2GSPacketSizeTable[packetId]) << size; 82 | } 83 | 84 | const ASMPTR NET_D2GS_CLIENT_GetIncomingPacketSizeFromTableAndVariableSize_Original = 0x52b920, NET_D2GS_CLIENT_GetIncomingPacketSizeFromTableAndVariableSize_Rejoin = 0x52b926; 85 | 86 | __declspec(naked) size_t __fastcall NET_D2GS_CLIENT_GetIncomingPacketSizeFromTableAndVariableSize_Relocated(char* pBytes, size_t pBytesSize, size_t* pExpectedSize) { 87 | __asm { 88 | push ebp 89 | mov ebp, esp 90 | push esi 91 | mov esi, edx 92 | jmp NET_D2GS_CLIENT_GetIncomingPacketSizeFromTableAndVariableSize_Rejoin 93 | } 94 | } 95 | 96 | size_t __fastcall NET_D2GS_CLIENT_GetIncomingPacketSizeFromTableAndVariableSize_Intercept(char* pBytes, size_t pBytesSize, size_t* pExpectedSize) { 97 | CustomPacketHeader* header = (CustomPacketHeader*)pBytes; 98 | 99 | if (header->id == CUSTOM_PACKET_ID) { 100 | return *pExpectedSize = header->size; 101 | } 102 | 103 | return NET_D2GS_CLIENT_GetIncomingPacketSizeFromTableAndVariableSize_Relocated(pBytes, pBytesSize, pExpectedSize); 104 | } 105 | 106 | void ServerSendCustomData(Ghidra::D2ClientStrc* pClient, char* pBytes, size_t nSize) { 107 | const size_t MAX_SIZE = 0x200 - sizeof(CustomPacketHeader); 108 | char pWrapped[MAX_SIZE + sizeof(CustomPacketHeader)]; 109 | CustomPacketHeader* header = (CustomPacketHeader*)pWrapped; 110 | 111 | if (nSize > MAX_SIZE) { 112 | throw "Packet size too large!"; 113 | } 114 | 115 | memcpy(pWrapped + sizeof(CustomPacketHeader), pBytes, nSize); 116 | header->id = CUSTOM_PACKET_ID; 117 | header->size = (short)(nSize + sizeof(CustomPacketHeader)); 118 | serverSendPacket(pClient, pWrapped, header->size); 119 | } 120 | 121 | void ClientSendCustomData(char* pBytes, size_t nSize) { 122 | const size_t MAX_SIZE = 0x200 - sizeof(CustomPacketHeader); 123 | char pWrapped[MAX_SIZE + sizeof(CustomPacketHeader)]; 124 | CustomPacketHeader* header = (CustomPacketHeader*)pWrapped; 125 | 126 | if (nSize > MAX_SIZE) { 127 | throw "Packet size too large!"; 128 | } 129 | 130 | memcpy(pWrapped + sizeof(CustomPacketHeader), pBytes, nSize); 131 | header->id = CUSTOM_PACKET_ID; 132 | header->size = (short)(nSize + sizeof(CustomPacketHeader)); 133 | clientSendPacket(pWrapped, header->size); 134 | } 135 | 136 | namespace CustomPacket { 137 | 138 | class : public Feature { 139 | public: 140 | void init() { 141 | MemoryPatch(NET_D2GS_CLIENT_GetIncomingPacketSizeFromTableAndVariableSize_Original) << JUMP(NET_D2GS_CLIENT_GetIncomingPacketSizeFromTableAndVariableSize_Intercept) << ASM::NOP; 142 | MemoryPatch(0x52d03e) << CALL(ServerIncomingPacketHook_InGame); 143 | MemoryPatch(0x44c73b) << CALL(ClientIncomingPacketHook_InGame); 144 | SetClientPacketHandler(CUSTOM_PACKET_ID, -1); // Even if there's no handler we have to enable the packet on client side since it's unused. 145 | SetServerPacketHandler(CUSTOM_PACKET_ID, 1); // We skip the size check, so any size above zero will do. This just enables the packet for sending. 146 | } 147 | } feature; 148 | 149 | } 150 | -------------------------------------------------------------------------------- /features/DebugMode.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Enables a bunch of debug enhancements. 3 | * It's crazy! Seriously! 4 | */ 5 | 6 | #include "headers/common.h" 7 | #include "headers/feature.h" 8 | #include "headers/hook.h" 9 | #include "headers/remote.h" 10 | #include 11 | 12 | const wchar_t* align[] = { L"Hostile", L"Neutral", L"Friendly" }; 13 | const DWORD alignColors[] = { 1, 5, 2 }; 14 | 15 | ASMPTR EnableDebugPrint = 0x8846DC; 16 | REMOTEFUNC(void __fastcall, DrawFloor, (void* unknown), 0x4DED10); 17 | 18 | void __fastcall _drawFloor(void* unknown) { 19 | if (!Settings["debugMode"]) { 20 | DrawFloor(unknown); 21 | } 22 | } 23 | 24 | // This is based on the actual source for printf... uses varargs. 25 | int __stdcall printf_newline(const char* format, ...) { 26 | va_list arg; 27 | int done; 28 | 29 | va_start(arg, format); 30 | done = vfprintf(stdout, format, arg); 31 | va_end(arg); 32 | 33 | // We want to force a newline after debug prints :) 34 | puts(""); 35 | 36 | return done; 37 | } 38 | 39 | REMOTEFUNC(void __fastcall, DRAW_UnderFloor, (void* param1), 0x4df480) 40 | void __fastcall DRAW_UnderFloor_Intercept(void* param1) { 41 | 42 | // ToDo; only visiable background behind floor is in baals chamber, as it has some floating floors 43 | // It draws some weird white backgrounds in act 5, that are not visable as the floor draws over it 44 | // Should only draw when in chamber, and disable for the rest of the levels. Waste of resource 45 | 46 | if (!Settings["debugMode"]) { 47 | DRAW_UnderFloor(param1); 48 | } 49 | } 50 | 51 | BOOL ShouldDrawAncientsBackground() { 52 | return !Settings["debugMode"]; 53 | } 54 | 55 | REMOTEFUNC(void, DRAW_Background_Ancients, (), 0x476460); 56 | __declspec(naked) void DRAW_Background_Ancients_Intercept() { 57 | __asm { 58 | pushad 59 | call ShouldDrawAncientsBackground 60 | test eax, eax 61 | popad 62 | 63 | jz skip 64 | jmp DRAW_Background_Ancients 65 | 66 | skip: 67 | ret 68 | } 69 | } 70 | 71 | BOOL CanDraw() { 72 | return !Settings["debugMode"] || Settings["debugModeType"] != DebugMode::HIDDEN; 73 | } 74 | 75 | BOOL CanDrawWalls() { 76 | return !Settings["debugMode"] || Settings["debugModeWalls"]; 77 | } 78 | 79 | BOOL CanDrawShadows() { 80 | return !Settings["debugMode"]; 81 | } 82 | 83 | BOOL CanDrawRoofs() { 84 | return !Settings["disableRoofs"] && CanDrawWalls(); 85 | } 86 | 87 | __declspec(naked) void DrawUnit_intercept() { 88 | static const ASMPTR DrawUnit_rejoin = 0x471ec6; 89 | __asm { 90 | push ebp 91 | mov ebp, esp 92 | sub esp, 0x70 93 | 94 | pushad 95 | call CanDraw 96 | test eax, eax 97 | popad 98 | jz skip 99 | 100 | jmp DrawUnit_rejoin 101 | 102 | skip: 103 | mov esp, ebp 104 | pop ebp 105 | ret 0x10 106 | } 107 | } 108 | 109 | _declspec(naked) void DrawWalls_intercept() { 110 | static const ASMPTR DrawWalls_rejoin = 0x4df3aa, DrawWalls_jump = 0x4df3c3, DrawWalls_skip = 0x4df3d6; 111 | __asm { 112 | pushad 113 | call CanDrawWalls 114 | test eax, eax 115 | popad 116 | jz skip 117 | 118 | cmp bl, 0xff 119 | jz jump 120 | jmp DrawWalls_rejoin 121 | 122 | jump: 123 | jmp DrawWalls_jump 124 | 125 | skip: 126 | jmp DrawWalls_skip 127 | } 128 | } 129 | 130 | __declspec(naked) void DrawShadows_intercept() { 131 | static const ASMPTR DrawShadows_original = 0x4df510; 132 | __asm { 133 | pushad 134 | call CanDrawShadows 135 | test eax, eax 136 | popad 137 | jz skip 138 | 139 | jmp DrawShadows_original 140 | 141 | skip: 142 | ret 143 | } 144 | } 145 | 146 | __declspec(naked) void DrawRoofs_intercept() { 147 | static const ASMPTR DrawRoofs_original = 0x4dea70; 148 | __asm { 149 | pushad 150 | call CanDrawRoofs 151 | test eax, eax 152 | popad 153 | jz skip 154 | 155 | jmp DrawRoofs_original 156 | 157 | skip : 158 | ret 159 | } 160 | } 161 | 162 | namespace DebugMode { 163 | 164 | // This feature class registers itself. 165 | class : public Feature { 166 | public: 167 | void toggleDebug() { 168 | Settings["debugMode"] = !Settings["debugMode"]; 169 | SaveSettings(); 170 | 171 | if (Settings["debugMode"]) { 172 | gamelog << COLOR(2) << "Debugging on." << std::endl; 173 | } 174 | else { 175 | gamelog << COLOR(1) << "Debugging off." << std::endl; 176 | } 177 | } 178 | 179 | void init() { 180 | MemoryPatch(0x476CDC) << CALL(_drawFloor); // Allow disabling the floor. 181 | MemoryPatch(0x51A480) << JUMP(printf_newline); // Enable even more console debug prints 182 | MemoryPatch(0x471ec0) << JUMP(DrawUnit_intercept); 183 | MemoryPatch(0x4df3a5) << JUMP(DrawWalls_intercept); 184 | MemoryPatch(0x476cf3) << CALL(DrawShadows_intercept); 185 | MemoryPatch(0x476d0b) << CALL(DrawRoofs_intercept); 186 | 187 | // Redirect the drawing of the background behind the floor 188 | MemoryPatch(0x476cd5) << CALL(DRAW_UnderFloor_Intercept); 189 | 190 | // Intercept drawing ancient's background 191 | MemoryPatch(0x476c93) << CALL(DRAW_Background_Ancients_Intercept); 192 | 193 | HotkeyCallbacks[VK_F10] = [&](LPARAM options) -> BOOL { 194 | toggleDebug(); 195 | return FALSE; 196 | }; 197 | } 198 | 199 | void gameUnitPreDraw() { 200 | if (Settings["debugMode"]) { 201 | D2::Types::Room1* current = D2::PlayerUnit->getRoom1(); 202 | D2::Types::CollMap* coll = current->Coll; 203 | WORD* p = coll->pMapStart; 204 | DWORD color, x, y; 205 | 206 | for (x = 0; x < coll->dwSizeGameX; x++) { 207 | for (y = 0; y < coll->dwSizeGameY; y++) { 208 | color = coll->getCollision(x, y, 0x4) ? 0x62 : coll->getCollision(x, y, 0xC09) ? 0x4B : coll->getCollision(x, y, 0x180) ? 0x8E : coll->getCollision(x, y, 0x10) ? 0x4 : 0x18; 209 | DPOINT( (double)coll->dwPosGameX + (double)x + 0.5, (double)coll->dwPosGameY + (double)y + 0.5 ).DrawWorldX(color, 0.5); 210 | } 211 | } 212 | 213 | for (unsigned int c = 0; c < current->dwRoomsNear; c++) { 214 | coll = current->pRoomsNear[c]->Coll; 215 | p = coll->pMapStart; 216 | for (x = 0; x < coll->dwSizeGameX; x++) { 217 | for (y = 0; y < coll->dwSizeGameY; y++) { 218 | color = coll->getCollision(x, y, 0x4) ? 0x62 : coll->getCollision(x, y, 0xC09) ? 0x4B : coll->getCollision(x, y, 0x180) ? 0x8E : coll->getCollision(x, y, 0x10) ? 0x4 : 0x18; 219 | DPOINT( (double)coll->dwPosGameX + (double)x + 0.5, (double)coll->dwPosGameY + (double)y + 0.5 ).DrawWorldX(color, 0.5); 220 | } 221 | } 222 | } 223 | 224 | // Server side tracks enemies 225 | for (D2::Types::NonPlayerUnit* unit : D2::ServerSideUnits.nonplayers.all()) { 226 | if (unit->pPath && unit->unitHP() > 0) { 227 | POINT pos = unit->pos().toScreen(), target = unit->getTargetPos().toScreen(); 228 | 229 | if (pos.x >= 0 && pos.y >= 0 && pos.x < D2::ScreenWidth && pos.y < D2::ScreenHeight && target.x >= 0 && target.y >= 0 && target.x < D2::ScreenWidth && target.y < D2::ScreenHeight) { 230 | DrawLine(pos, target, 0x99); 231 | } 232 | } 233 | } 234 | 235 | // Client side tracks missiles 236 | for (D2::Types::MissileUnit* unit : D2::ClientSideUnits.missiles.all()) { 237 | unit->DrawWorldX(0x99, 0.5); 238 | POINT pos = unit->pos().toScreen(), target = unit->getTargetPos().toScreen(); 239 | 240 | if (pos.x >= 0 && pos.y >= 0 && pos.x < D2::ScreenWidth && pos.y < D2::ScreenHeight && target.x >= 0 && target.y >= 0 && target.x < D2::ScreenWidth && target.y < D2::ScreenHeight) { 241 | DrawLine(pos, target, 0x83); 242 | } 243 | } 244 | } 245 | } 246 | 247 | void gameUnitPostDraw() { 248 | if (Settings["debugMode"]) { 249 | wchar_t msg[512]; 250 | DWORD fontNum = 12, width = 0, height = 0; 251 | POINT pos; 252 | D2::SetFont(fontNum); 253 | 254 | for (D2::Types::PlayerUnit* unit : D2::ServerSideUnits.players.all()) { 255 | unit->DrawWorldX(0x9B); 256 | } 257 | 258 | // Server side tracks objects 259 | for (D2::Types::ObjectUnit* unit : D2::ServerSideUnits.objects.all()) { 260 | DPOINT dpos = unit->pos(); 261 | dpos.DrawWorldX(0x69); 262 | pos = dpos.toScreen(); 263 | swprintf_s(msg, L"%d", unit->dwTxtFileNo); 264 | height = D2::GetTextSize(msg, &width, &fontNum); 265 | D2::DrawGameText(msg, pos.x - (width >> 1) - 4, pos.y - height + 8, 8, 1); 266 | } 267 | 268 | // Server side tracks enemies 269 | for (D2::Types::NonPlayerUnit* unit : D2::ServerSideUnits.nonplayers.all()) { 270 | if (unit->pPath) { 271 | if (unit->isAttackable()) { 272 | switch (D2::GetUnitStat(unit, 172, 0)) { 273 | case 0: // hostile 274 | if (unit->pMonsterData->fUnique || unit->pMonsterData->fChamp) { 275 | unit->DrawWorldX(0x0C); 276 | } 277 | else if (unit->pMonsterData->fMinion) { 278 | unit->DrawWorldX(0x0B); 279 | } 280 | else { 281 | unit->DrawWorldX(0x0A); 282 | } 283 | break; 284 | case 2: // friendly 285 | unit->DrawWorldX(0x84); 286 | break; 287 | default: // neutral 288 | unit->DrawWorldX(0x99); 289 | } 290 | } 291 | else { 292 | unit->DrawWorldX(0x1B); 293 | } 294 | 295 | pos = unit->pos().toScreen(); 296 | 297 | if (pos.x >= 0 && pos.y >= 0 && pos.x < D2::ScreenWidth && pos.y < D2::ScreenHeight) { 298 | swprintf_s(msg, L"%d", unit->dwTxtFileNo); 299 | height = D2::GetTextSize(msg, &width, &fontNum); 300 | D2::DrawGameText(msg, pos.x - (width >> 1), pos.y + height + 2, alignColors[D2::GetUnitStat(unit, 172, 0)], 0); 301 | 302 | if (unit->pMonsterData->fSuper) { 303 | swprintf_s(msg, L"%d", unit->pMonsterData->wUniqueNo); 304 | height = D2::GetTextSize(msg, &width, &fontNum); 305 | D2::DrawGameText(msg, pos.x + 8, pos.y + 5, 3, 0); 306 | } 307 | } 308 | } 309 | } 310 | 311 | if (D2::PlayerUnit->getLevel() != nullptr) { 312 | for (D2::Types::Room2* room : D2::PlayerUnit->getLevel()->getAllRoom2()) { 313 | for (D2::Types::PresetUnit* unit : room->getAllPresetUnits()) { 314 | DPOINT dpos = unit->pos(room); 315 | dpos.DrawWorldX(0x84); 316 | POINT wpos = dpos.toScreen(); 317 | swprintf_s(msg, L"%d", unit->dwTxtFileNo); 318 | height = D2::GetTextSize(msg, &width, &fontNum); 319 | D2::DrawGameText(msg, wpos.x - (width >> 1) - 4, wpos.y + height + 2, 2, 1); 320 | } 321 | } 322 | } 323 | } 324 | } 325 | } feature; 326 | 327 | } 328 | -------------------------------------------------------------------------------- /features/Dialog.cpp: -------------------------------------------------------------------------------- 1 | #define DEFINE_DIALOG_VARS 2 | #include 3 | #include "headers/common.h" 4 | #include "headers/feature.h" 5 | #include "headers/dialog.h" 6 | #include "headers/remote.h" 7 | #include 8 | 9 | std::vector dialogs; 10 | 11 | bool inRect(int x, int y, RECT r) { 12 | return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom; 13 | } 14 | 15 | Element::Element() {} 16 | 17 | Element::~Element() { 18 | for (Element* child : children) { 19 | delete child; 20 | } 21 | } 22 | 23 | RECT Element::getRect() { 24 | return { pos.left, pos.top, pos.right, pos.bottom }; 25 | } 26 | 27 | RECT Element::getRect(int ox, int oy) { 28 | return { ox + pos.left, oy + pos.top, ox + pos.right, oy + pos.bottom }; 29 | } 30 | 31 | void Element::setPos(int x, int y) { 32 | this->pos.left = x; 33 | this->pos.top = y; 34 | 35 | this->pos.right = this->pos.left + this->width; 36 | this->pos.bottom = this->pos.top + this->height; 37 | } 38 | 39 | void Element::setDimensions(int newWidth, int newHeight) { 40 | this->width = newWidth; 41 | this->height = newHeight; 42 | 43 | this->pos.right = this->pos.left + this->width; 44 | this->pos.bottom = this->pos.top + this->height; 45 | } 46 | 47 | void Element::setFrame(int newBackgroundColor, int newBackgroundOpacity, int newBorderColor) { 48 | this->backgroundColor = newBackgroundColor; 49 | this->backgroundOpacity = newBackgroundOpacity; 50 | this->borderColor = newBorderColor; 51 | } 52 | 53 | void Element::addLine(POINT a, POINT b, int color, int opacity) { 54 | lines.push_back({ a, b, color, opacity }); 55 | } 56 | 57 | void Element::getLinesCallback(std::function(std::vector lines)> newDrawLinesCallback) { 58 | this->drawLinesCallback = newDrawLinesCallback; 59 | } 60 | 61 | void Element::show() { 62 | visible = true; 63 | } 64 | 65 | void Element::hide() { 66 | visible = false; 67 | } 68 | 69 | void Element::setVisibile(bool value) { 70 | visible = value; 71 | } 72 | 73 | bool Element::isVisible() { 74 | return visible; 75 | } 76 | 77 | void Element::addChild(Element* child) { 78 | children.push_back(child); 79 | } 80 | 81 | void Element::onClick(std::function handler) { 82 | clickCallback = handler; 83 | } 84 | 85 | void Element::onKey(std::function handler) { 86 | keyHandler = handler; 87 | } 88 | 89 | void Element::drawFrame(int ox, int oy) { 90 | if (!visible) return; 91 | 92 | if (backgroundColor >= 0 && backgroundOpacity > 0) { 93 | D2::DrawSolidRectAlpha(ox + pos.left, oy + pos.top, ox + pos.right, oy + pos.bottom, backgroundColor, backgroundOpacity); 94 | } 95 | 96 | if (borderColor >= 0) { 97 | RECT b = { ox + pos.left, oy + pos.top, ox + pos.right, oy + pos.bottom }; 98 | D2::DrawRect(&b, (BYTE)borderColor); 99 | } 100 | 101 | for (LineInfo line : (drawLinesCallback ? drawLinesCallback(lines) : lines)) { 102 | D2::DrawLine(ox + pos.left + line.a.x, oy + pos.top + line.a.y, ox + pos.left + line.b.x, oy + pos.top + line.b.y, line.color, line.opacity); 103 | } 104 | } 105 | 106 | void Element::drawChildren(int ox, int oy) { 107 | if (!visible) return; 108 | 109 | ox += pos.left; 110 | oy += pos.top; 111 | 112 | for (Element* child : children) { 113 | child->draw(ox, oy); 114 | } 115 | } 116 | 117 | void Element::draw(int ox, int oy) { 118 | drawFrame(ox, oy); 119 | drawChildren(ox, oy); 120 | }; 121 | 122 | bool Element::inArea(int x, int y) { 123 | return visible && inRect(x, y, pos); 124 | }; 125 | 126 | bool Element::interact(int x, int y, bool down, MouseButton button) { 127 | if (inArea(x, y)) { 128 | for (Element* child : children) { 129 | if (child->interact(x - pos.left, y - pos.top, down, button)) { 130 | return true; 131 | } 132 | } 133 | 134 | if (clickCallback) { 135 | clickCallback(button, down); 136 | return true; 137 | } 138 | } 139 | 140 | return false; 141 | }; 142 | 143 | bool Element::interactKey(DWORD keyCode, bool down, DWORD flags){ 144 | if (!visible) return false; 145 | 146 | for (Element* child : children) { 147 | if (child->interactKey(keyCode, down, flags)) { 148 | return true; 149 | } 150 | } 151 | 152 | if (keyHandler && !keyHandler(keyCode, down, flags)) { 153 | return true; 154 | } 155 | 156 | return false; 157 | }; 158 | 159 | void TextElement::setFont(int value) { 160 | font = value; 161 | } 162 | 163 | void TextElement::setColor(int value) { 164 | color = value; 165 | } 166 | 167 | void TextElement::setText(std::wstring value) { 168 | textCallback = [=]() -> std::wstring { return value; }; 169 | } 170 | 171 | void TextElement::setOrientation(Orientation value) { 172 | orientation = value; 173 | } 174 | 175 | void TextElement::setText(std::function callback) { 176 | textCallback = callback; 177 | } 178 | 179 | void TextElement::setTextOffset(int x, int y) { 180 | textOffsetX = x; 181 | textOffsetY = y; 182 | } 183 | 184 | void TextElement::draw(int ox, int oy) { 185 | if (!visible) return; 186 | 187 | Element::drawFrame(ox, oy); 188 | 189 | DWORD fileno = font, w = 0, h = 0; 190 | std::wstring message = textCallback(); 191 | D2::SetFont(fileno); 192 | h = D2::GetTextSize(message.c_str(), &w, &fileno); 193 | int cy = (pos.top + pos.bottom + h) / 2; 194 | 195 | switch (orientation) { 196 | case Orientation::RIGHT: 197 | D2::DrawGameText(message.c_str(), ox + textOffsetX + pos.right - w, oy + textOffsetY + cy, color, 0); 198 | break; 199 | case Orientation::CENTER: 200 | D2::DrawGameText(message.c_str(), ox + textOffsetX + (pos.left + pos.right - w) / 2, oy + textOffsetY + cy, color, 0); 201 | break; 202 | default: 203 | D2::DrawGameText(message.c_str(), ox + textOffsetX + pos.left, oy + textOffsetY + cy, color, 0); 204 | break; 205 | } 206 | 207 | Element::drawChildren(oy, ox); 208 | } 209 | 210 | Dialog::Dialog() : Element() { 211 | backgroundColor = 0; 212 | backgroundOpacity = 0xD0; 213 | borderColor = 0xD; 214 | dialogs.push_back(this); 215 | } 216 | 217 | bool Dialog::interact(int x, int y, bool down, MouseButton button) { 218 | if (inArea(x, y)) { 219 | for (Element* child : children) { 220 | if (child->interact(x - pos.left, y - pos.top, down, button)) { 221 | return true; 222 | } 223 | } 224 | 225 | if (clickCallback) { 226 | clickCallback(button, down); 227 | } 228 | 229 | return true; 230 | } 231 | 232 | return false; 233 | }; 234 | 235 | namespace Template { 236 | class : public Feature { 237 | std::unordered_map messageButtonMap = { 238 | { WM_RBUTTONDOWN, MouseButton::RIGHT }, 239 | { WM_RBUTTONUP, MouseButton::RIGHT }, 240 | { WM_LBUTTONDOWN, MouseButton::LEFT }, 241 | { WM_LBUTTONUP, MouseButton::LEFT }, 242 | { WM_MBUTTONDOWN, MouseButton::MIDDLE }, 243 | { WM_MBUTTONUP, MouseButton::MIDDLE }, 244 | }; 245 | public: 246 | void allPostDraw() { 247 | for (Dialog* dialog : dialogs) { 248 | dialog->draw(D2::ScreenWidth / 2, D2::ScreenHeight / 2); 249 | } 250 | } 251 | 252 | bool windowMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { 253 | bool allowDefault = true, down = false; 254 | 255 | switch (uMsg) { 256 | case WM_KEYDOWN: 257 | case WM_SYSKEYDOWN: 258 | if (!(lParam & 0x40000000) && wParam != VK_F4) { 259 | for (auto rit = dialogs.rbegin(); rit != dialogs.rend(); ++rit) { 260 | Dialog* dialog = *rit; 261 | if (dialog->interactKey(wParam, uMsg == WM_KEYDOWN, lParam)) { 262 | return false; 263 | } 264 | } 265 | } 266 | 267 | break; 268 | case WM_XBUTTONDBLCLK: 269 | case WM_XBUTTONDOWN: 270 | case WM_XBUTTONUP: 271 | case WM_RBUTTONDBLCLK: 272 | case WM_LBUTTONDBLCLK: 273 | case WM_MBUTTONDBLCLK: 274 | for (auto rit = dialogs.rbegin(); rit != dialogs.rend(); ++rit) { 275 | Dialog* dialog = *rit; 276 | if (dialog->inArea(GET_X_LPARAM(lParam) - D2::ScreenWidth / 2, GET_Y_LPARAM(lParam) - D2::ScreenHeight / 2)) { 277 | return false; 278 | } 279 | 280 | allowDefault = allowDefault && !dialog->isVisible(); 281 | } 282 | 283 | break; 284 | case WM_RBUTTONDOWN: 285 | case WM_LBUTTONDOWN: 286 | case WM_MBUTTONDOWN: 287 | down = true; 288 | case WM_RBUTTONUP: 289 | case WM_LBUTTONUP: 290 | case WM_MBUTTONUP: 291 | for (auto rit = dialogs.rbegin(); rit != dialogs.rend(); ++rit) { 292 | Dialog* dialog = *rit; 293 | if (dialog->interact(GET_X_LPARAM(lParam) - D2::ScreenWidth / 2, GET_Y_LPARAM(lParam) - D2::ScreenHeight / 2, down, messageButtonMap[uMsg])) { 294 | return false; 295 | } 296 | 297 | allowDefault = allowDefault && !dialog->isVisible(); 298 | } 299 | 300 | break; 301 | } 302 | 303 | return allowDefault; 304 | } 305 | } feature; 306 | 307 | } 308 | -------------------------------------------------------------------------------- /features/DrawAllStates.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @description simple file that draws all possible states 3 | */ 4 | #include "headers/remote.h" 5 | #include "headers/feature.h" 6 | #include "headers/hook.h" 7 | 8 | namespace DrawAllStates { 9 | 10 | static ASMPTR start = 0x46e46f; 11 | static ASMPTR end = 0x46e486; 12 | static auto name = "drawAllStates"; 13 | 14 | class : public Feature { 15 | public: 16 | void init() override { 17 | State[name] = false; 18 | } 19 | 20 | void gameLoop() override { 21 | if (Settings[name] && !State[name]) { 22 | 23 | MemoryPatch(start) << NOP_TO(end); 24 | State[name] = !State[name]; 25 | 26 | } else if (!Settings[name] && State[name]) { 27 | 28 | MemoryPatch(start) << REVERT(end - start); 29 | State[name] = !State[name]; 30 | 31 | } 32 | } 33 | } feature; 34 | } -------------------------------------------------------------------------------- /features/Drawing.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Enables all of the drawing hooks. 3 | * 4 | * @todo Reimplement the full render loop function some day. 5 | */ 6 | 7 | #include "headers/feature.h" 8 | #include "headers/common.h" 9 | #include "headers/hook.h" 10 | #include "headers/remote.h" 11 | #include 12 | 13 | REMOTEFUNC(void __fastcall, DrawAutomap, (), 0x45ad60); 14 | REMOTEFUNC(void __fastcall, DrawSprites, (), 0x4F9870); 15 | REMOTEREF(D2::Types::RendererFunctionsStrc*, CurrentRendererFunctions, 0x7c8cc0); 16 | REMOTEFUNC(void, DrawCursor, (), 0x4684C0); 17 | REMOTEFUNC(void, DrawCursorOOG, (), 0x4F97E0); 18 | 19 | /** 20 | * Drawing hooks. 21 | */ 22 | void preDrawUnitsPatch() { 23 | State["inGame"] = true; 24 | const DWORD addr = 0x473c00; 25 | for (Feature* f = Features; f; f = f->next) { 26 | f->gameUnitPreDraw(); 27 | } 28 | __asm { 29 | call addr; 30 | } 31 | } 32 | 33 | void _gameUnitPostDraw() { 34 | for (Feature* f = Features; f; f = f->next) { 35 | f->gameUnitPostDraw(); 36 | } 37 | } 38 | 39 | void gameAutomapDraw() { 40 | for (Feature* f = Features; f; f = f->next) { 41 | f->gameAutomapPreDraw(); 42 | } 43 | DrawAutomap(); 44 | for (Feature* f = Features; f; f = f->next) { 45 | f->gameAutomapPostDraw(); 46 | } 47 | } 48 | 49 | void oogDraw() { 50 | State["inGame"] = false; 51 | D2::GetScreenModeSize(D2::GetScreenMode(), &D2::ScreenWidth, &D2::ScreenHeight); 52 | DWORD old = D2::SetFont(DEFAULT_FONT); 53 | for (Feature* f = Features; f; f = f->next) { 54 | f->oogPostDraw(); 55 | } 56 | for (Feature* f = Features; f; f = f->next) { 57 | f->allPostDraw(); 58 | } 59 | D2::SetFont(old); 60 | DrawCursorOOG(); 61 | // Since we patch to override DrawSprites, we need to call it ourselves. 62 | } 63 | REMOTEFUNC(unsigned int __stdcall, DRAW_GetTypeOfBorder,(void),0x45ae90); 64 | 65 | unsigned int __stdcall gamePreDraw() { 66 | for (Feature* f = Features; f; f = f->next) { 67 | f->preDraw(); 68 | } 69 | 70 | // stuff we override 71 | return DRAW_GetTypeOfBorder(); 72 | } 73 | 74 | void gameDraw() { 75 | DWORD old = D2::SetFont(DEFAULT_FONT); 76 | for (Feature* f = Features; f; f = f->next) { 77 | f->gamePostDraw(); 78 | } 79 | for (Feature* f = Features; f; f = f->next) { 80 | f->allPostDraw(); 81 | } 82 | D2::SetFont(old); 83 | } 84 | 85 | void DrawCursorHook() { 86 | DWORD old = D2::SetFont(DEFAULT_FONT); 87 | for (Feature* f = Features; f; f = f->next) { 88 | f->allPostDraw(); 89 | } 90 | D2::SetFont(old); 91 | DrawCursor(); 92 | } 93 | 94 | void _allFinalDraw() { 95 | for (Feature* f = Features; f; f = f->next) { 96 | f->allFinalDraw(); 97 | } 98 | 99 | CurrentRendererFunctions->fpEndScene(); 100 | } 101 | 102 | REMOTEFUNC(float, PerfModOriginal, (), 0x4f6130); 103 | 104 | // This feature class registers itself. 105 | class : public Feature { 106 | public: 107 | void init() { 108 | MemoryPatch(0x476ce1) << CALL(preDrawUnitsPatch); // Hook the unit draw 109 | MemoryPatch(0x456fa5) << CALL(gameAutomapDraw); // Hook the automap draw 110 | MemoryPatch(0x44CB14) << CALL(gameDraw); // Hook the game draw 111 | MemoryPatch(0x44cae8) << CALL(gamePreDraw); 112 | MemoryPatch(0x4F9A5D) << CALL(oogDraw); // Hook the oog draw 113 | MemoryPatch(0x44ebea) << CALL(DrawCursorHook); // Congratulations screen hook 114 | MemoryPatch(0x45fe1f) << CALL(DrawCursorHook); // Disc screen hook 115 | MemoryPatch(0x4601d6) << CALL(DrawCursorHook); // Unknown screen hook 116 | 117 | 118 | MemoryPatch(0x476d31) << JUMP(_gameUnitPostDraw); 119 | MemoryPatch(0x4F6230) << CALL(_allFinalDraw) << NOP_TO(0x4F623A); 120 | } 121 | } feature; 122 | -------------------------------------------------------------------------------- /features/ExperienceMod.cpp: -------------------------------------------------------------------------------- 1 | #include "headers/feature.h" 2 | #include "headers/common.h" 3 | #include "headers/hook.h" 4 | #include "headers/remote.h" 5 | #include "headers/ghidra.h" 6 | 7 | Ghidra::D2UnitStrc* pLastVictim; 8 | 9 | REMOTEREF(DWORD, PlayerCountOverride, 0x883d70); 10 | REMOTEFUNC(DWORD __fastcall, CALC_Experience, (Ghidra::D2UnitStrc* pUnit, int dummy, Ghidra::D2GameStrc* pGame, unsigned int param_3), 0x57e3f0) 11 | REMOTEFUNC(Ghidra::D2ClientStrc* __stdcall, PLAYER_GetClientFromUnitData, (Ghidra::D2UnitStrc* pUnit), 0x5531c0) 12 | REMOTEFUNC(void __fastcall, SERVER_KillExperience, (Ghidra::D2GameStrc* pGame, Ghidra::D2UnitStrc* pAttacker, Ghidra::D2UnitStrc* pVictim), 0x57e990) 13 | 14 | void __fastcall SERVER_KillExperience_intercept(Ghidra::D2GameStrc* pGame, Ghidra::D2UnitStrc* pAttacker, Ghidra::D2UnitStrc* pVictim) { 15 | pLastVictim = pVictim; 16 | return SERVER_KillExperience(pGame, pAttacker, pVictim); 17 | } 18 | 19 | void __fastcall SetPlayerCount(DWORD count) { 20 | PlayerCountOverride = count > 1 ? count : 1; 21 | gamelog << "Difficulty (players) set to " << count << " and saved." << std::endl; 22 | Settings["PlayerCountOverride"] = PlayerCountOverride; 23 | SaveSettings(); 24 | } 25 | 26 | DWORD __fastcall ExperienceIntercept(Ghidra::D2UnitStrc* pUnit, int dummy, Ghidra::D2GameStrc* pGame, unsigned int param_3) { 27 | DWORD exp = CALC_Experience(pUnit, dummy, pGame, param_3); 28 | 29 | if (Settings["xpMin"] <= 8000) { 30 | exp *= Settings["xpBonus"]; 31 | 32 | if (exp && exp < Settings["xpMin"]) { 33 | exp = Settings["xpMin"]; 34 | } 35 | } 36 | else { 37 | exp = 3520485254; 38 | } 39 | 40 | for (Feature* f = Features; f; f = f->next) { 41 | f->serverExpAward(exp, pUnit, pGame); 42 | } 43 | 44 | return exp; 45 | } 46 | 47 | namespace ExperienceMod { 48 | 49 | class : public Feature { 50 | bool ingame = false; 51 | public: 52 | void init() { 53 | // Rewrite players count modification and leave to our handler - 42 bytes total 54 | MemoryPatch(0x47c4e4) 55 | << ASM::PUSHAD 56 | << ASM::MOV_ECX_EAX 57 | << CALL(SetPlayerCount) 58 | << ASM::POPAD 59 | << NOP_TO(0x47c50e); 60 | 61 | MemoryPatch(0x57e4fa) << CALL(ExperienceIntercept); 62 | 63 | // actual memory patches for feature 64 | MemoryPatch(0x5a4f12) << CALL(SERVER_KillExperience_intercept); 65 | 66 | AutomapInfoHooks.push_back([]() -> std::wstring { 67 | wchar_t ret[256] = L"Players 1"; 68 | 69 | if (PlayerCountOverride > 1) { 70 | swprintf_s(ret, L"Players %d", PlayerCountOverride); 71 | } 72 | 73 | return ret; 74 | }); 75 | } 76 | 77 | void serverExpAward(DWORD exp, Ghidra::D2UnitStrc* pUnit, Ghidra::D2GameStrc* pGame) { 78 | if (pUnit->eUnitType == Ghidra::UNIT_PLAYER && Settings["infoPopups"]) { 79 | Ghidra::D2ClientStrc* pClient = PLAYER_GetClientFromUnitData(pUnit); 80 | 81 | if (pClient != nullptr) { 82 | FlyingTextPacket packet; 83 | D2::Types::UnitAny* unit = (D2::Types::UnitAny*)pUnit; 84 | 85 | packet.pos = unit->pos(); 86 | packet.color = 2; 87 | packet.value = static_cast(exp); 88 | 89 | ServerSendCustomData(pClient, packet); 90 | } 91 | } 92 | } 93 | 94 | void gameLoop() { 95 | if (!ingame || PlayerCountOverride != Settings["PlayerCountOverride"]) { 96 | PlayerCountOverride = Settings["PlayerCountOverride"]; 97 | 98 | if (PlayerCountOverride > 1) { 99 | gamelog << "Difficulty (players) set to " << PlayerCountOverride << "." << std::endl; 100 | } 101 | } 102 | 103 | ingame = true; 104 | } 105 | 106 | void oogLoop() { 107 | ingame = false; 108 | } 109 | } feature; 110 | 111 | } 112 | -------------------------------------------------------------------------------- /features/GameLog.cpp: -------------------------------------------------------------------------------- 1 | #include "headers/feature.h" 2 | #include "headers/common.h" 3 | #include "headers/hook.h" 4 | #include "headers/remote.h" 5 | #include 6 | 7 | REMOTEFUNC(void __fastcall, PrintGameString, (const wchar_t* wMessage, int nColor), 0x49E3A0) 8 | REMOTEFUNC(void __fastcall, PrintPartyString, (const wchar_t* wMessage, int nColor), 0x49E5C0) // Updated 1.14d //0049E5C0-BASE 9 | REMOTEREF(DWORD, PanelState, 0x07a5210); 10 | 11 | struct GameLogMessage { 12 | DWORD timestamp; 13 | std::wstring text; 14 | }; 15 | 16 | std::list GameLogMessages; 17 | GameOutput gamelog; 18 | 19 | bool ingame = false; 20 | 21 | int GameOutput::GameOutputBuffer::sync() { 22 | std::wstring::size_type start = 0; 23 | std::wstring::size_type pos = this->str().find(L"\n"); 24 | 25 | while (pos != std::wstring::npos) { 26 | std::wstring tmp = this->str().substr(start, pos - start); 27 | GameLogMessages.push_back({ GetTickCount(), tmp }); 28 | start = pos + 1; 29 | pos = this->str().find(L"\n", start); 30 | } 31 | 32 | if (start > 0) { 33 | this->str(this->str().substr(start)); 34 | } 35 | 36 | return 0; 37 | } 38 | 39 | GameOutput::GameOutput() : std::wostream(&buf) { } 40 | 41 | std::wstring COLOR(BYTE color) { 42 | wchar_t ret[]{ 0xFF, 0x63, 0x30, 0 }; 43 | ret[2] += (color & 0xf); 44 | return ret; 45 | } 46 | 47 | void __fastcall PrintGameStringHook(const wchar_t* wMessage, int nColor) { 48 | gamelog << COLOR((BYTE)nColor) << wMessage << std::endl; 49 | } 50 | 51 | ASMPTR GameStringInterceptExit = 0x49e3a9; 52 | 53 | void __declspec(naked) PrintGameStringIntercept() { 54 | __asm { 55 | // preserve the registers around our call 56 | pushad 57 | call PrintGameStringHook 58 | popad 59 | 60 | // mimic instructions we've replaced 61 | push ebp 62 | mov ebp, esp 63 | sub esp, 0x110 64 | 65 | // resume the original function 66 | jmp GameStringInterceptExit 67 | } 68 | } 69 | 70 | void ClearChatMessages() { 71 | while (!GameLogMessages.empty()) { 72 | GameLogMessages.pop_front(); 73 | } 74 | } 75 | 76 | namespace GameLog { 77 | 78 | class : public Feature { 79 | DWORD start = 0; 80 | public: 81 | void init() { 82 | //gamelog << COLOR(4) << "Game log installed..." << std::endl; 83 | MemoryPatch(0x49dcb8) << NOP_TO(0x49ddc9); 84 | MemoryPatch(0x49e3a0) << JUMP(PrintGameStringIntercept) << NOP_TO(GameStringInterceptExit); 85 | MemoryPatch(0x4a01fa) << JUMP(ClearChatMessages); 86 | } 87 | 88 | void showMessages(bool lower) { 89 | int top = lower ? 0x5f : 10, left = 15; 90 | DWORD height = 0, width = 0, fontno = 13; 91 | int space = -8; 92 | 93 | if (PanelState == 2) { 94 | left += D2::ScreenWidth / 2; 95 | } 96 | 97 | if (!start) { 98 | start = GetTickCount(); 99 | } 100 | 101 | if (GetTickCount() - start < 300) { 102 | return; 103 | } 104 | 105 | while (!GameLogMessages.empty() && GameLogMessages.front().timestamp + 10000 < GetTickCount()) { 106 | GameLogMessages.pop_front(); 107 | } 108 | 109 | D2::SetFont(fontno); 110 | 111 | for (GameLogMessage message : GameLogMessages) { 112 | height = D2::GetTextSize(message.text.c_str(), &width, &fontno); 113 | D2::DrawGameText(message.text.c_str(), left, top += (height + space), 0, 0); 114 | } 115 | } 116 | 117 | void gamePostDraw() { 118 | if (!D2::GetUiFlag(0x18)) { 119 | showMessages(D2::GetUiFlag(0x13)); 120 | } 121 | } 122 | 123 | void oogPostDraw() { 124 | showMessages(false); 125 | } 126 | } feature; 127 | 128 | } 129 | -------------------------------------------------------------------------------- /features/GameLoop.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Enables throttle and game loop hooks. 3 | */ 4 | 5 | #include "headers/feature.h" 6 | #include "headers/common.h" 7 | #include "headers/hook.h" 8 | #include "headers/remote.h" 9 | #include 10 | 11 | void _postInit() { 12 | for (Feature* f = Features; f; f = f->next) { 13 | f->postInit(); 14 | } 15 | } 16 | 17 | ASMPTR _init_rejoin = 0x405e36, _init_call_1 = 0x514530; 18 | 19 | __declspec(naked) void _postInitHook() { 20 | __asm { 21 | cmp byte ptr[esi + 0x220], 0x0 22 | mov eax, [0x74c704] 23 | jnz skip 24 | cmp eax, 2 25 | jz skip 26 | movzx edx, byte ptr[esi + 0x224] 27 | mov ecx, dword ptr[esi] 28 | call _init_call_1 29 | mov eax, [0x74c704] 30 | mov dword ptr[ebp + -0x4], 0x1 31 | skip: 32 | pushad 33 | call _postInit 34 | popad 35 | jmp _init_rejoin 36 | } 37 | } 38 | 39 | 40 | void _gameLoop() { 41 | for (Feature* f = Features; f; f = f->next) { 42 | f->gameLoop(); 43 | } 44 | } 45 | 46 | void _oogLoop() { 47 | // Out of game logic goes here. 48 | for (Feature* f = Features; f; f = f->next) { 49 | f->oogLoop(); 50 | } 51 | } 52 | 53 | void __stdcall _serverLoop(D2::Types::IncompleteGameData* pGame) { 54 | // to test it ups by one every loop, every tick this function is called for ever hosted game the client does 55 | //int gameFrame = pGame->unk5[46]; 56 | 57 | for (Feature* f = Features; f; f = f->next) { 58 | f->gameServerLoop(pGame); 59 | } 60 | } 61 | 62 | REMOTEFUNC(int, FUN_0052d310,(void), 0x52d310) 63 | __declspec(naked) void _serverLoopIntercept() { 64 | __asm { 65 | // safety, we dont know what the function/gameloops messup in terms of ptrs 66 | pushad 67 | 68 | push edi // pGame 69 | call _serverLoop 70 | 71 | popad 72 | // since we jump to this intercept as a call, the return value is set 73 | JMP FUN_0052d310 74 | } 75 | } 76 | 77 | 78 | // This feature class registers itself. 79 | class : public Feature { 80 | public: 81 | void init() { 82 | MemoryPatch(0x405e09) << JUMP(_postInitHook); 83 | // override the entire sleepy section - 32 bytes long 84 | MemoryPatch(0x451C2A) 85 | << CALL(_gameLoop) 86 | << BYTES(ASM::NOP, 2); 87 | 88 | // override the entire sleepy section - 23 bytes long 89 | MemoryPatch(0x4FA663) 90 | << CALL(_oogLoop) 91 | << BYTES(ASM::NOP, 18); 92 | 93 | // overriding FUN_0052d310 function call 94 | MemoryPatch(0x52d96c) << CALL(_serverLoopIntercept); 95 | 96 | } 97 | } feature; 98 | -------------------------------------------------------------------------------- /features/Input.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Enables chat command and hotkey hooks. 3 | * 4 | * Thanks to Jaenster for the hotkey stuff! 5 | */ 6 | #include "headers/feature.h" 7 | #include "headers/common.h" 8 | #include "headers/hook.h" 9 | #include "headers/remote.h" 10 | #include 11 | 12 | const ASMPTR SoundChaosCheckStart = 0x47c53d; 13 | const ASMPTR SoundChaosCheckEnd = 0x47c559; 14 | const DWORD SuccessfulCommandAddress = 0x47ca4f; 15 | WNDPROC OldWndProc; 16 | 17 | BOOL keyPressEvent(WPARAM wparam, LPARAM lparam) { 18 | BOOL chatBox = D2::GetUiFlag(0x05); 19 | BOOL escMenu = D2::GetUiFlag(0x09); 20 | 21 | //gamelog << COLOR(4) << "chatbox: " << chatBox << "\t" << "escMenu: " << escMenu << "\t" << wparam <<"\t" << lparam << std::endl; 22 | 23 | if (!State["inGame"] || !chatBox && !escMenu) { 24 | 25 | char keycode = static_cast(wparam); 26 | 27 | HotkeyMapIterator it = HotkeyCallbacks.find(keycode); 28 | if (it != HotkeyCallbacks.end()) { 29 | HotkeyCallback cb = it->second; 30 | return cb(lparam); 31 | } 32 | } 33 | 34 | return true; 35 | } 36 | 37 | LRESULT CALLBACK WindowProc(_In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) { 38 | bool allowDefault = true; 39 | for (Feature* f = Features; f; f = f->next) { 40 | if (!f->windowMessage(hwnd, uMsg, wParam, lParam)) { 41 | allowDefault = false; 42 | } 43 | } 44 | 45 | switch (uMsg) { 46 | case WM_KEYDOWN: 47 | if (!(lParam & 0x40000000)) { 48 | for (Feature* f = Features; f; f = f->next) { 49 | if (!f->keyEvent(wParam, true, lParam)) { 50 | allowDefault = false; 51 | } 52 | } 53 | 54 | if (!keyPressEvent(wParam, lParam)) { 55 | allowDefault = false; 56 | } 57 | } 58 | break; 59 | case WM_SYSKEYDOWN: 60 | if (!(lParam & 0x40000000)) { 61 | for (Feature* f = Features; f; f = f->next) { 62 | if (!f->keyEvent(wParam, false, lParam)) { 63 | allowDefault = false; 64 | } 65 | } 66 | 67 | if (!keyPressEvent(wParam, lParam)) { 68 | allowDefault = false; 69 | } 70 | } 71 | break; 72 | } 73 | 74 | return allowDefault ? OldWndProc(hwnd, uMsg, wParam, lParam) : false; 75 | } 76 | ATOM __stdcall RegisterClassAHook(WNDCLASSA* lpWndClass) { 77 | OldWndProc = lpWndClass->lpfnWndProc; 78 | lpWndClass->lpfnWndProc = WindowProc; 79 | return RegisterClassA(lpWndClass); 80 | } 81 | 82 | REMOTEFUNC(void, SoundChaosDebug, (), 0x4BABC0); 83 | 84 | // Replaces the 'soundchaosdebug' command 85 | bool __fastcall ChatCommandProcessor(char* msg) { 86 | std::string tmp(msg); 87 | std::wstring wMsg(tmp.begin(), tmp.end()); 88 | 89 | for (Feature* f = Features; f; f = f->next) { 90 | std::wstringstream wsMsg(wMsg); 91 | 92 | if (!f->chatInput(wsMsg)) { 93 | return false; 94 | } 95 | } 96 | 97 | try { 98 | std::wstringstream wsMsg(wMsg); 99 | std::wstring cmd; 100 | wsMsg >> cmd; 101 | 102 | return ChatInputCallbacks.at(cmd)(cmd, wsMsg); // Find the callback, and then call it. 103 | } 104 | catch (...) { 105 | return true; // Ignore the exception. Command not found. 106 | } 107 | } 108 | 109 | // This feature class registers itself. 110 | class : public Feature { 111 | public: 112 | void init() { 113 | MemoryPatch(SoundChaosCheckStart) 114 | << ASM::MOV_ECX_EDI 115 | << CALL(ChatCommandProcessor) 116 | << ASM::TEST_AL 117 | << JUMP_ZERO(SuccessfulCommandAddress) 118 | << NOP_TO(SoundChaosCheckEnd); 119 | 120 | MemoryPatch(0x4f5379) << CALL(RegisterClassAHook) << ASM::NOP; 121 | } 122 | 123 | bool chatInput(InputStream msg) { 124 | std::wstring cmd; 125 | msg >> cmd; 126 | 127 | // Since we patched this out we should probably reimplement it. 128 | if (cmd == L"soundchaosdebug") { 129 | SoundChaosDebug(); 130 | return false; 131 | } 132 | 133 | return true; 134 | } 135 | } feature; 136 | -------------------------------------------------------------------------------- /features/ItemQoL.cpp: -------------------------------------------------------------------------------- 1 | #include "headers/feature.h" 2 | #include "headers/common.h" 3 | #include "headers/hook.h" 4 | #include "headers/remote.h" 5 | #include "headers/ghidra.h" 6 | 7 | using namespace Ghidra; 8 | 9 | REMOTEFUNC(BOOL __stdcall, ITEM_EditItemData_eItemFlag, (D2UnitStrc* pItem, eD2ItemFlag eItemFlag, BOOL bSet), 0x6280d0); 10 | REMOTEFUNC(void __fastcall, SERVER_PickToCursor, (D2GameStrc* pGame, D2UnitStrc* pPlayer, DWORD nItemGUID, int* param_2_00), 0x55cf50) 11 | 12 | ASMPTR NET_D2GS_SERVER_Send_0x9C_ItemWorld_original = 0x53eae0, NET_D2GS_SERVER_Send_0x9C_ItemWorld_rejoin = 0x53eae9; 13 | 14 | __declspec(naked) void __fastcall NET_D2GS_SERVER_Send_0x9C_ItemWorld_relocated(D2ClientStrc* pClient, D2UnitStrc* pItem, undefined4 nAction, eD2ItemFlag eItemFlag, uint param_5) { 15 | __asm { 16 | PUSH EBP 17 | MOV EBP, ESP 18 | SUB ESP, 0x100 19 | 20 | JMP NET_D2GS_SERVER_Send_0x9C_ItemWorld_rejoin 21 | } 22 | }; 23 | 24 | void __fastcall NET_D2GS_SERVER_Send_0x9C_ItemWorld_hook(D2ClientStrc* pClient, D2UnitStrc* pItem, undefined4 nAction, eD2ItemFlag eItemFlag, uint param_5) { 25 | if (nAction == 0 && Settings["itemQOL"]) {// Dropped on ground 26 | ITEM_EditItemData_eItemFlag(pItem, ITEMFLAG_IDENTIFIED, 1); 27 | } 28 | 29 | NET_D2GS_SERVER_Send_0x9C_ItemWorld_relocated(pClient, pItem, nAction, eItemFlag, param_5); 30 | }; 31 | 32 | /* 33 | ASMPTR NET_D2GS_SERVER_Send_0x19_0x1D_0x1E_0x1F_GoldPickup_original = 0x53e9b0, NET_D2GS_SERVER_Send_0x19_0x1D_0x1E_0x1F_GoldPickup_rejoin = 0x53e9b6; 34 | 35 | __declspec(naked) void __fastcall NET_D2GS_SERVER_Send_0x19_0x1D_0x1E_0x1F_GoldPickup_relocated(D2ClientStrc* pClient, uint nGold, uint nNewGold) { 36 | __asm { 37 | PUSH EBP 38 | MOV EBP, ESP 39 | SUB ESP, 0x8 40 | 41 | JMP NET_D2GS_SERVER_Send_0x19_0x1D_0x1E_0x1F_GoldPickup_rejoin 42 | } 43 | }; 44 | 45 | void __fastcall NET_D2GS_SERVER_Send_0x19_0x1D_0x1E_0x1F_GoldPickup_hook(D2ClientStrc* pClient, uint nNewGold, uint nGold) { 46 | NET_D2GS_SERVER_Send_0x19_0x1D_0x1E_0x1F_GoldPickup_relocated(pClient, nNewGold, nGold); 47 | 48 | return; 49 | }; 50 | */ 51 | 52 | namespace ItemQoL { 53 | 54 | class : public Feature { 55 | public: 56 | void init() { 57 | MemoryPatch(NET_D2GS_SERVER_Send_0x9C_ItemWorld_original) << JUMP(NET_D2GS_SERVER_Send_0x9C_ItemWorld_hook); 58 | // MemoryPatch(NET_D2GS_SERVER_Send_0x19_0x1D_0x1E_0x1F_GoldPickup_original) << JUMP(NET_D2GS_SERVER_Send_0x19_0x1D_0x1E_0x1F_GoldPickup_hook); 59 | } 60 | } feature; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /features/MPQLoader.cpp: -------------------------------------------------------------------------------- 1 | #include "headers/feature.h" 2 | #include "headers/common.h" 3 | #include "headers/hook.h" 4 | #include "headers/remote.h" 5 | #include 6 | #include 7 | 8 | REMOTEREF(BOOL, D2Ini_bUseDirect, 0x74c82c); 9 | REMOTEFUNC(DWORD __fastcall, LoadMPQ, (const char* szMpqFileName, int nZero, DWORD fpRequiredUserAction, int nTimeTBC), 0x517332); 10 | 11 | BOOL GetUseDirect(void) { 12 | for (Feature* f = Features; f; f = f->next) { 13 | for (std::string mpqname : f->mpq) { 14 | if (LoadMPQ(mpqname.c_str(), 0, 0, 5000)) { 15 | gamelog << "Using MPQ: " << mpqname.c_str() << std::endl; 16 | } 17 | else { 18 | gamelog << "Failed to load: " << mpqname.c_str() << std::endl; 19 | } 20 | } 21 | } 22 | 23 | return D2Ini_bUseDirect; 24 | } 25 | 26 | namespace MPQLoader { 27 | 28 | class : public Feature { 29 | public: 30 | void init() { 31 | MemoryPatch(0x4fac38) << CALL(GetUseDirect); 32 | 33 | int argc = 0; 34 | LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); 35 | char arg[128]; 36 | size_t len; 37 | 38 | for (int c = 0; c < argc; c++) { 39 | if (!wcscmp(argv[c], L"-mpq") && ++c < argc) { 40 | wcstombs_s(&len, arg, argv[c], 128); 41 | mpq.push_back(arg); 42 | } 43 | } 44 | 45 | } 46 | } feature; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /features/MapReveal.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Reveals the automap when moving to new areas. 3 | */ 4 | 5 | #define _USE_MATH_DEFINES 6 | 7 | #include "headers/feature.h" 8 | #include "headers/common.h" 9 | #include "headers/hook.h" 10 | #include "headers/remote.h" 11 | #include 12 | #include 13 | #include 14 | 15 | REMOTEFUNC(DWORD __stdcall, GetAutomapSize, (void), 0x45A710); 16 | REMOTEFUNC(D2::Types::AutomapCell* __fastcall, NewAutomapCell, (), 0x457C30); 17 | REMOTEFUNC(void __fastcall, AddAutomapCell, (D2::Types::AutomapCell* aCell, D2::Types::AutomapCell** node), 0x457B00); 18 | REMOTEFUNC(void __stdcall, RevealAutomapRoom, (D2::Types::Room1* pRoom1, DWORD dwClipFlag, D2::Types::AutomapLayer* aLayer), 0x458F40); 19 | REMOTEFUNC(D2::Types::AutomapLayer2* __fastcall, GetLayer, (DWORD dwLevelNo), 0x61E470); 20 | REMOTEREF(D2::Types::AutomapLayer*, AutomapLayer, 0x7A5164); 21 | 22 | struct RevealData { 23 | bool isRevealed = false; 24 | D2::Types::Room2* room2 = nullptr; 25 | D2::Types::AutomapLayer* layer = nullptr; 26 | }; 27 | 28 | struct LevelLabel { 29 | DPOINT pos; 30 | DWORD layer, level; 31 | }; 32 | 33 | std::vector levellabels; 34 | 35 | D2::Types::Room2* GetFirstTileOtherRoom(D2::Types::Room2* room2, DWORD num) { 36 | D2::Types::RoomTile* roomtile = room2->pRoomTiles; 37 | 38 | while (roomtile) { 39 | if (*roomtile->nNum == num) { 40 | return roomtile->pRoom2; 41 | } 42 | roomtile = roomtile->pNext; 43 | } 44 | 45 | return nullptr; 46 | } 47 | 48 | REMOTEFUNC(void __stdcall, D2GFX_DrawSprite6, (D2::Types::DC6Context *pDC6Context, DWORD x, DWORD y, RECT *clipRect, DWORD bright), 0x4f6510); 49 | 50 | void DrawWarpText(DPOINT pos, DWORD levelno, bool isScreen = false) { 51 | POINT apos = isScreen ? pos.toScreen() : pos.toAutomap(); 52 | DWORD width = 0, height = 0, fontNum = 6; 53 | wchar_t* levelNameUnicode = D2::GetLevelText(levelno)->wName; 54 | D2::SetFont(fontNum); 55 | height = D2::GetTextSize(levelNameUnicode, &width, &fontNum); 56 | int offsetx = 8 - ((int)width / 2), offsety = -16; 57 | D2::DrawGameText(levelNameUnicode, apos.x + offsetx, apos.y + offsety, 0, false); 58 | } 59 | 60 | // This feature class registers itself. 61 | class : public Feature { 62 | DWORD currentLevel = 0; 63 | DWORD revealStart = 0; 64 | bool inGame = false; 65 | 66 | std::map revealdata; 67 | 68 | public: 69 | void init() { 70 | MemoryPatch(0x4DC000) << ASM::RET; // Disable fade effects from switching areas (so we can reveal sooner) 71 | } 72 | 73 | void gameLoop() { 74 | if (!inGame) { 75 | revealdata.clear(); 76 | levellabels.clear(); 77 | inGame = true; 78 | } 79 | 80 | D2::Types::CurrentPlayerUnit* me = D2::PlayerUnit; 81 | 82 | if (me && me->getLevel()) { 83 | if (Settings["revealLevel"]) { 84 | DWORD levelno = me->getLevel()->dwLevelNo; 85 | if (levelno != currentLevel) { 86 | currentLevel = levelno; 87 | revealStart = GetTickCount(); 88 | } 89 | 90 | if (GetTickCount() - revealStart > 200) { 91 | RevealCurrentLevel(); 92 | } 93 | } 94 | } 95 | } 96 | 97 | void oogLoop() { 98 | currentLevel = 0; 99 | inGame = false; 100 | } 101 | 102 | void gameAutomapPostDraw() { 103 | for (LevelLabel label : levellabels) { 104 | if (label.layer == AutomapLayer->nLayerNo) { 105 | DrawWarpText(label.pos, label.level); 106 | } 107 | } 108 | } 109 | 110 | bool EnsureRoom(D2::Types::Room2* current) { 111 | if (current->pRoom1 != nullptr) { 112 | return true; 113 | } 114 | 115 | D2::AddRoomData(current->pLevel->pMisc->pAct, current->pLevel->dwLevelNo, current->dwPosX, current->dwPosY, current->pRoom1); 116 | return current->pRoom1 != nullptr; 117 | } 118 | 119 | void RevealCurrentLevel() { 120 | D2::Types::CurrentPlayerUnit* me = D2::PlayerUnit; 121 | int c = 20; 122 | 123 | if (me) for (D2::Types::Level* level = me->pAct->pMisc->pLevelFirst; level != nullptr; level = level->pNextLevel) { 124 | if (level && level->dwLevelNo > 0) { 125 | if (!revealdata[level->dwLevelNo].isRevealed && level->pRoom2First != nullptr && AutomapLayer->nLayerNo == GetLayer(level->dwLevelNo)->nLayerNo) { 126 | revealdata[level->dwLevelNo].layer = AutomapLayer; 127 | revealdata[level->dwLevelNo].room2 = level->pRoom2First; 128 | revealdata[level->dwLevelNo].isRevealed = true; 129 | } 130 | 131 | D2::Types::Room2* room2 = revealdata[level->dwLevelNo].room2; 132 | 133 | for (; c > 0 && room2 != nullptr; c--) { 134 | if (room2->pLevel && room2->pLevel->pMisc && room2->pLevel->pMisc->pAct) { 135 | if (EnsureRoom(room2)) { 136 | RevealAutomapRoom(room2->pRoom1, TRUE, revealdata[level->dwLevelNo].layer); 137 | DrawPresets(room2, revealdata[level->dwLevelNo].layer); 138 | // Not sure that we need to unload the room again... we're going to need it again later. 139 | // D2::RemoveRoomData(room2->pLevel->pMisc->pAct, room2->pLevel->dwLevelNo, room2->dwPosX, room2->dwPosY, room2->pRoom1); 140 | } 141 | } 142 | 143 | room2 = revealdata[level->dwLevelNo].room2 = revealdata[level->dwLevelNo].room2->pRoom2Next; 144 | } 145 | } 146 | } 147 | } 148 | 149 | // Most of this stuff is based on the d2bs preset unit implementation. 150 | void GenerateCell(D2::Types::Room2* pRoom2, D2::Types::PresetUnit* pUnit, int mCell, D2::Types::AutomapLayer* layer) { 151 | D2::Types::AutomapCell* pCell = NewAutomapCell(); 152 | pCell->nCellNo = (WORD)mCell; 153 | int pX = (pUnit->dwPosX + (pRoom2->dwPosX * 5)); 154 | int pY = (pUnit->dwPosY + (pRoom2->dwPosY * 5)); 155 | pCell->xPixel = (WORD)((((pX - pY) * 16) / 10) + 1); 156 | pCell->yPixel = (WORD)((((pY + pX) * 8) / 10) - 3); 157 | AddAutomapCell(pCell, &(layer->pObjects)); 158 | } 159 | 160 | void GenerateMegaMarkerCell(D2::Types::Room2* pRoom2, D2::Types::PresetUnit* pUnit, D2::Types::AutomapLayer* layer) { 161 | GenerateCell(pRoom2, pUnit, 301, layer); 162 | } 163 | 164 | void GenerateMarkerCell(D2::Types::Room2* pRoom2, D2::Types::PresetUnit* pUnit, D2::Types::AutomapLayer* layer) { 165 | GenerateCell(pRoom2, pUnit, 300, layer); 166 | } 167 | 168 | void GenerateSuperchestCell(D2::Types::Room2* pRoom2, D2::Types::PresetUnit* pUnit, D2::Types::AutomapLayer* layer) { 169 | GenerateCell(pRoom2, pUnit, 318, layer); 170 | } 171 | 172 | void GenerateChestCell(D2::Types::Room2* pRoom2, D2::Types::PresetUnit* pUnit, D2::Types::AutomapLayer* layer) { 173 | GenerateCell(pRoom2, pUnit, 319, layer); 174 | } 175 | 176 | void GenerateOwnCell(D2::Types::Room2* pRoom2, D2::Types::PresetUnit* pUnit, D2::Types::AutomapLayer* layer) { 177 | D2::Types::ObjectTxt* obj = obj = D2::GetObjectText(pUnit->dwTxtFileNo); 178 | 179 | if (obj->nAutoMap > 0) { 180 | GenerateCell(pRoom2, pUnit, obj->nAutoMap, layer); 181 | } 182 | } 183 | 184 | void DrawPresets(D2::Types::Room2* pRoom2, D2::Types::AutomapLayer* layer) { 185 | for (D2::Types::PresetUnit* pUnit = pRoom2->pPreset; pUnit; pUnit = pUnit->pPresetNext) { 186 | switch (pUnit->dwType) { 187 | case 1: // NPCs 188 | switch (pUnit->dwTxtFileNo) { 189 | case 250: // Summoner 190 | case 256: // Izual 191 | GenerateMarkerCell(pRoom2, pUnit, layer); 192 | continue; 193 | } 194 | break; 195 | case 2: // Objects 196 | switch (pUnit->dwTxtFileNo) { 197 | case 580: 198 | // Lower Kurast uber chests 199 | if (pRoom2->pLevel->dwLevelNo == 79) { 200 | GenerateChestCell(pRoom2, pUnit, layer); 201 | continue; 202 | } 203 | break; 204 | case 371: // Countess Chest - not sure if this works though 205 | GenerateSuperchestCell(pRoom2, pUnit, layer); 206 | continue; 207 | case 152: // Tomb Orifice 208 | GenerateMarkerCell(pRoom2, pUnit, layer); 209 | continue; 210 | case 460: // Frozen Anya 211 | GenerateCell(pRoom2, pUnit, 1468, layer); 212 | continue; 213 | case 402: // Canyon Waypoint 214 | if (pRoom2->pLevel->dwLevelNo == 46) { 215 | //continue; 216 | } 217 | break; 218 | case 267: // Stashes in act 1, 2, 5 Get double rendered. 219 | if (pRoom2->pLevel->dwLevelNo != 75 && pRoom2->pLevel->dwLevelNo != 103) { 220 | continue; 221 | } 222 | break; 223 | case 268: // Wirt's Body 224 | GenerateMarkerCell(pRoom2, pUnit, layer); 225 | continue; 226 | case 376: // Hellforge gets double rendered. 227 | if (pRoom2->pLevel->dwLevelNo == 107) { 228 | continue; 229 | } 230 | break; 231 | } 232 | 233 | if (pUnit->dwTxtFileNo <= 574) { 234 | GenerateOwnCell(pRoom2, pUnit, layer); 235 | } 236 | 237 | break; 238 | 239 | case 5: 240 | DWORD correctTomb = pRoom2->pLevel && pRoom2->pLevel->pMisc && pRoom2->pLevel->pMisc->dwStaffTombLevel ? pRoom2->pLevel->pMisc->dwStaffTombLevel : 0; 241 | 242 | if (correctTomb) { 243 | for (D2::Types::RoomTile* pRoomTile = pRoom2->pRoomTiles; pRoomTile; pRoomTile = pRoomTile->pNext) { 244 | if (*(pRoomTile->nNum) == pUnit->dwTxtFileNo && pRoomTile->pRoom2->pLevel->dwLevelNo == correctTomb) { 245 | GenerateMegaMarkerCell(pRoom2, pUnit, layer); 246 | } 247 | } 248 | } 249 | 250 | // Check if tile leads to another room 251 | D2::Types::Room2* warpto = GetFirstTileOtherRoom(pRoom2, pUnit->dwTxtFileNo); 252 | 253 | if (warpto != nullptr) { 254 | LevelLabel label{ DPOINT{ (double)(pUnit->dwPosX + (pRoom2->dwPosX * 5)), (double)(pUnit->dwPosY + (pRoom2->dwPosY * 5)) }, layer->nLayerNo, warpto->pLevel->dwLevelNo }; 255 | levellabels.push_back(label); 256 | } 257 | 258 | break; 259 | } 260 | } 261 | } 262 | } feature; 263 | -------------------------------------------------------------------------------- /features/MapUnits.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Displays units on the map and automap. 3 | */ 4 | 5 | #include "headers/feature.h" 6 | #include "headers/common.h" 7 | #include "headers/hook.h" 8 | #include "headers/remote.h" 9 | #include 10 | 11 | DWORD ItemRarityColor[32] = { 255, 29, 30, 32, 151, 132, 111, 155, 111 }; 12 | 13 | // This feature class registers itself. 14 | class : public Feature { 15 | public: 16 | void init() { 17 | // gamelog << COLOR(4) << "Installing unit tracker..." << std::endl; 18 | } 19 | 20 | void gameAutomapPostDraw() { 21 | if (Settings["showMissiles"]) { 22 | // Client side tracks missiles 23 | for (D2::Types::MissileUnit* unit : D2::ClientSideUnits.missiles.all()) { 24 | unit->DrawAutomapDot(0x99); 25 | } 26 | } 27 | 28 | if (Settings["debugMode"]) { 29 | 30 | // Server side tracks objects 31 | for (D2::Types::ObjectUnit* unit : D2::ServerSideUnits.objects.all()) { 32 | unit->DrawAutomapDot(0x69); 33 | } 34 | 35 | if (D2::PlayerUnit->getLevel() != nullptr) { 36 | for (D2::Types::Room2* room : D2::PlayerUnit->getLevel()->getAllRoom2()) { 37 | for (D2::Types::PresetUnit* unit : room->getAllPresetUnits()) { 38 | unit->pos(room).DrawAutomapDot(0x84); 39 | } 40 | } 41 | } 42 | } 43 | 44 | if (Settings["showItems"]) { 45 | // Server side tracks items 46 | for (D2::Types::ItemUnit* unit : D2::ServerSideUnits.items.all()) { 47 | if (unit->dwMode == 3 || unit->dwMode == 5) { 48 | if (unit->pItemData->dwFlags & 0x4000000) { 49 | unit->DrawAutomapX(ItemRarityColor[7], 3); 50 | } 51 | else if (unit->pItemData->dwQuality > 3 || (unit->pItemData->dwFlags & 0x400800 && unit->pItemData->dwQuality > 2 && D2::GetUnitStat(unit, 194, 0) >= 2)) { // @todo check number of sockets 52 | unit->DrawAutomapX(ItemRarityColor[unit->pItemData->dwQuality], 3); 53 | } 54 | else { 55 | D2::Types::ItemTxt* txt = D2::GetItemText(unit->dwTxtFileNo); 56 | if (txt->nType >= 96 && txt->nType <= 102 || txt->nType == 74) { 57 | unit->DrawAutomapX(169, 3); 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | if (Settings["showMonsters"]) { 65 | // Server side tracks enemies 66 | for (D2::Types::NonPlayerUnit* unit : D2::ServerSideUnits.nonplayers.all()) { 67 | if (unit->isPlayerHostile() && unit->unitHP() > 0) { 68 | if (unit->isAttackable()) { 69 | if (unit->pMonsterData->fUnique || unit->pMonsterData->fChamp) { 70 | unit->DrawAutomapX(0x0C); 71 | } 72 | else if (unit->pMonsterData->fMinion) { 73 | unit->DrawAutomapX(0x0B); 74 | } 75 | else { 76 | unit->DrawAutomapX(0x0A); 77 | } 78 | } 79 | else { 80 | unit->DrawAutomapX(0x1B); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } feature; 87 | -------------------------------------------------------------------------------- /features/Misc.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Small patches and features that don't belong elsewhere. 3 | */ 4 | 5 | #include "headers/feature.h" 6 | #include "headers/common.h" 7 | #include "headers/hook.h" 8 | #include "headers/remote.h" 9 | #include "headers/dialog.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | REMOTEREF(int, DrawAutoMapStatsOffsetY, 0x7A51BC); 16 | REMOTEREF(D2::Types::ItemUnit*, CurrentTooltipItem, 0x7BCBF4); 17 | REMOTEFUNC(BYTE __stdcall, GetMaxSocketCount, (D2::Types::ItemUnit *pItem), 0x62BC20); 18 | REMOTEFUNC(int, FUN_0047a560, (), 0x47a560); 19 | 20 | Dialog splashDialog; 21 | 22 | void ClearSplash() { 23 | splashDialog.hide(); 24 | D2::MainMenuForm(); 25 | } 26 | 27 | void SplashScreenDialogSetup() { 28 | int width = 375, top = 0, height = 15; 29 | { 30 | TextElement* elem = new TextElement(); 31 | elem->setPos(0, top); 32 | elem->setDimensions(width, 25); 33 | elem->setFont(5); 34 | elem->setText(L"Charon"); 35 | elem->setOrientation(Orientation::CENTER); 36 | elem->setFrame(0x2, 0xFF, 0xD); 37 | elem->setTextOffset(0, -6); 38 | elem->show(); 39 | splashDialog.addChild(elem); 40 | 41 | top += 25 + height; 42 | } 43 | 44 | { 45 | TextElement* elem = new TextElement(); 46 | elem->setPos(0, top); 47 | elem->setDimensions(width, height); 48 | elem->setFont(0); 49 | elem->setText(L"This is an experiment and utility for Diablo II."); 50 | elem->setOrientation(Orientation::CENTER); 51 | elem->setTextOffset(0, -5); 52 | elem->show(); 53 | splashDialog.addChild(elem); 54 | top += height; 55 | } 56 | 57 | { 58 | TextElement* elem = new TextElement(); 59 | elem->setPos(0, top); 60 | elem->setDimensions(width, height); 61 | elem->setFont(0); 62 | elem->setText(L"It is intended for single player and TCP/IP game"); 63 | elem->setOrientation(Orientation::CENTER); 64 | elem->setTextOffset(0, -5); 65 | elem->show(); 66 | splashDialog.addChild(elem); 67 | top += height; 68 | } 69 | 70 | { 71 | TextElement* elem = new TextElement(); 72 | elem->setPos(0, top); 73 | elem->setDimensions(width, height); 74 | elem->setFont(0); 75 | elem->setText(L"use, so connections to Battle.net are disabled."); 76 | elem->setOrientation(Orientation::CENTER); 77 | elem->setTextOffset(0, -5); 78 | elem->show(); 79 | splashDialog.addChild(elem); 80 | top += height; 81 | } 82 | 83 | int height2 = 12; 84 | top += height2; 85 | 86 | { 87 | TextElement* elem = new TextElement(); 88 | elem->setPos(0, top); 89 | elem->setDimensions(width, height); 90 | elem->setFont(12); 91 | elem->setText(L"\"\u00FFc5I see him there at the oars of his little boat in the lake,"); 92 | elem->setOrientation(Orientation::CENTER); 93 | elem->setTextOffset(0, -3); 94 | elem->show(); 95 | splashDialog.addChild(elem); 96 | top += height2; 97 | } 98 | 99 | { 100 | TextElement* elem = new TextElement(); 101 | elem->setPos(0, top); 102 | elem->setDimensions(width, height); 103 | elem->setFont(12); 104 | elem->setText(L"\u00FFc5the ferryman of the dead, Charon, with his hand upon the oar"); 105 | elem->setOrientation(Orientation::CENTER); 106 | elem->setTextOffset(0, -3); 107 | elem->show(); 108 | splashDialog.addChild(elem); 109 | top += height2; 110 | } 111 | 112 | { 113 | TextElement* elem = new TextElement(); 114 | elem->setPos(0, top); 115 | elem->setDimensions(width, height); 116 | elem->setFont(12); 117 | elem->setText(L"\u00FFc5and he calls me now.\" ~Alcestis(from Alcestis by Euripides)"); 118 | elem->setOrientation(Orientation::CENTER); 119 | elem->setTextOffset(0, -3); 120 | elem->show(); 121 | splashDialog.addChild(elem); 122 | top += height; 123 | } 124 | 125 | { 126 | int w = 30, h = 24; 127 | TextElement* elem = new TextElement(); 128 | elem->setPos((width - w) / 2, top + 4); 129 | elem->setDimensions(w, h); 130 | elem->setFont(0); 131 | elem->setText(L"OK"); 132 | elem->setOrientation(Orientation::CENTER); 133 | elem->setTextOffset(1, -3); 134 | elem->setFrame(0x3, 0xFF, 0xD); 135 | elem->show(); 136 | 137 | elem->onClick([](MouseButton button, bool down) -> void { 138 | ClearSplash(); 139 | }); 140 | 141 | splashDialog.addChild(elem); 142 | top += h + height; 143 | } 144 | 145 | splashDialog.setDimensions(width, top); 146 | splashDialog.setPos(-width / 2, -top / 2); 147 | 148 | // bind the enter key to the dialog 149 | splashDialog.onKey([](DWORD keyCode, bool down, DWORD flags) -> bool { 150 | if (keyCode == 0x0D /* enter */) { 151 | ClearSplash(); 152 | } 153 | 154 | return false; // Block keypresses to D2 and dialogs behind this one (windowMessage and keyEvent still work regardless) 155 | }); 156 | 157 | } 158 | 159 | // Replaces the automatic splash screen timeout 160 | void SplashScreenHook() { 161 | splashDialog.show(); 162 | } 163 | 164 | static DWORD automapTextOffset = 0; 165 | void _drawAutoMapInfo(DWORD size) { 166 | DWORD width = 0, height = 0, fileno = 1; 167 | height = D2::GetTextSize(L"test", &width, &fileno); 168 | DWORD bottom = DrawAutoMapStatsOffsetY - height; 169 | for (AutomapInfoCallback func : AutomapInfoHooks) { 170 | std::wstring msg = func(); 171 | bottom += D2::GetTextSize(msg.c_str(), &width, &fileno); 172 | D2::DrawGameText(msg.c_str(), D2::ScreenWidth - 16 - width, bottom, 4, 0); 173 | } 174 | automapTextOffset = bottom; 175 | } 176 | 177 | void __fastcall DrawRepairImageAlertIntercept(void *pImage, bool isArrow) { 178 | int x = D2::ScreenWidth - (isArrow ? 0x38 : 0x3C), y = 0; 179 | 180 | if (D2::GetUiFlag(0xA)) { 181 | y = (FUN_0047a560() ? 0x2B : 0) + DrawAutoMapStatsOffsetY + automapTextOffset; 182 | } 183 | else { 184 | y = 0x6E; 185 | } 186 | 187 | if (isArrow) { 188 | y += 0x2D; 189 | } 190 | 191 | D2::DrawImage(pImage, x, y, -1, isArrow ? 5 : 1, nullptr); 192 | } 193 | 194 | ASMPTR GetItemName_Original = 0x48C060; 195 | 196 | __declspec(naked) BOOL __fastcall GetItemName_Relocated(D2::Types::ItemUnit* item, wchar_t* wBuffer, DWORD dwSize) { 197 | static ASMPTR GetItemName_Rejoin = 0x48C068; 198 | __asm { 199 | push ebp 200 | mov ebp, esp 201 | mov eax, 0x1bd8 202 | jmp GetItemName_Rejoin 203 | } 204 | } 205 | 206 | BOOL __fastcall GetItemName_Intercept(D2::Types::ItemUnit* item, wchar_t* wBuffer, DWORD dwSize) { 207 | BOOL ret = GetItemName_Relocated(item, wBuffer, dwSize); 208 | DWORD sockets = D2::GetUnitStat(item, 194, 0); 209 | 210 | if (sockets > 0) { 211 | wchar_t *tmpBuffer = new wchar_t[dwSize], *a = wBuffer; 212 | 213 | while (*a != L'\0' && *a != L'\n') { 214 | a++; 215 | } 216 | 217 | if (*a == L'\n') { 218 | *a++ = L'\0'; 219 | swprintf_s(tmpBuffer, dwSize, L"%ls (%d)\n%ls", wBuffer, sockets, a); 220 | wcscpy_s(wBuffer, dwSize, tmpBuffer); 221 | } 222 | else { 223 | swprintf_s(tmpBuffer, dwSize, L"%ls (%d)", wBuffer, sockets); 224 | wcscpy_s(wBuffer, dwSize, tmpBuffer); 225 | } 226 | } 227 | 228 | return ret; 229 | } 230 | 231 | void __fastcall GetItemDescription(wchar_t* wBuffer) { 232 | const size_t dwSize = 1024; 233 | 234 | if (Settings["itemInfo"] && CurrentTooltipItem->pItemData->dwItemLevel > 1) { 235 | wchar_t tmpBuffer[dwSize]; 236 | if (D2::GetUnitStat(CurrentTooltipItem, 194, 0) < 1 && CurrentTooltipItem->pItemData->dwQuality < 4) { 237 | BYTE maxSockets = GetMaxSocketCount(CurrentTooltipItem); 238 | if (maxSockets > 0) { 239 | swprintf(tmpBuffer, 1024, L"\u00FFc5Max Sockets: %d\nItem Level: %d\n%ls", maxSockets, CurrentTooltipItem->pItemData->dwItemLevel, wBuffer); 240 | wcscpy_s(wBuffer, dwSize, tmpBuffer); 241 | return; 242 | } 243 | } 244 | 245 | swprintf(tmpBuffer, 1024, L"\u00FFc5Item Level: %d\n%ls", CurrentTooltipItem->pItemData->dwItemLevel, wBuffer); 246 | wcscpy_s(wBuffer, dwSize, tmpBuffer); 247 | } 248 | } 249 | 250 | ASMPTR ItemDescription_Patches[] = { 0x48ED2F, 0x48E39C, 0x48DB1D }; 251 | REMOTEFUNC(void __fastcall, CalcTextDimensions, (wchar_t* wBuffer, long* x, long* y), 0x502520); 252 | 253 | void __fastcall ItemDescription_Hook(wchar_t* wBuffer, long *x, long *y) { 254 | GetItemDescription(wBuffer); 255 | CalcTextDimensions(wBuffer, x, y); 256 | } 257 | 258 | [[maybe_unused]] int 259 | __fastcall setItemColor(D2::Types::UnitAny *item, WCHAR *buffer, int nColor) { 260 | if (Settings["itemInfo"]) { 261 | D2::Types::ItemTxt *itemText = D2::GetItemText(item->dwTxtFileNo); 262 | if (itemText->nType == 78) { 263 | nColor = 11; 264 | } 265 | } 266 | return nColor; 267 | } 268 | 269 | __declspec(naked) void ItemColor_Hook() { 270 | static ASMPTR jmpBack = 0x452929; 271 | static ASMPTR ITEM_GetTypeFromTxtTable = 0x62b400; 272 | __asm { 273 | mov ecx, esi // Argument 1, item 274 | // argument 2, buffer (already in edx 275 | push DWORD PTR[ebp-0x4]; // add argument 3, 276 | call setItemColor 277 | mov DWORD PTR[ebp-0x4], eax // actual color is set @ setItemColor 278 | 279 | // Original code 280 | call ITEM_GetTypeFromTxtTable; 281 | JMP jmpBack; 282 | } 283 | } 284 | 285 | BOOL __fastcall OverrideQuestState(int questId, int questState, BOOL value) { 286 | switch (questId) { 287 | case 3: // Imbue quest 288 | if (Settings["imbueOverride"] && questState == 0) { 289 | return false; 290 | } 291 | if (Settings["imbueOverride"] && questState == 1) { 292 | return true; 293 | } 294 | break; 295 | 296 | case 4: // Can open cow potal 297 | if (Settings["cowsOverride"] && questState == 10) { 298 | gamelog << "Quest 4:" << questState << " = " << value << std::endl; 299 | return false; 300 | } 301 | break; 302 | 303 | case 7: // Act 1 complete 304 | case 15: // Act 2 complete 305 | case 23: // Act 3 complete 306 | case 28: // Act 4 complete 307 | break; 308 | 309 | case 35: // Socket Quest 310 | if (Settings["socketOverride"] && questState == 1) { 311 | return true; 312 | } 313 | break; 314 | 315 | case 41: // Respec Quest 316 | if (Settings["respecOverride"] && questState == 0) { 317 | return false; 318 | } 319 | if (Settings["respecOverride"] && questState == 1) { 320 | return true; 321 | } 322 | break; 323 | } 324 | 325 | return value; 326 | } 327 | 328 | __declspec(naked) void GetQuestState_Intercept() { 329 | __asm { 330 | push eax 331 | mov ecx, [ebp + 0xc] 332 | mov edx, [ebp + 0x10] 333 | call OverrideQuestState 334 | pop ebp 335 | ret 0xc 336 | } 337 | } 338 | 339 | DWORD __stdcall OverrideWaypoints(DWORD a, DWORD b) { 340 | return true; 341 | } 342 | 343 | ASMPTR SocketNotGrey_Patches[] = { 0x452857, 0x48E878, 0x48E897 }; 344 | 345 | ASMPTR GetGlobalLight_Rejoin = 0x61C0B6; 346 | 347 | __declspec(naked) void __stdcall GetGlobalLight_Original(void* pAct, BYTE* red, BYTE* green, BYTE* blue) { 348 | __asm { 349 | push ebp 350 | mov ebp, esp 351 | mov ecx, dword ptr[ebp + 0x8] 352 | jmp GetGlobalLight_Rejoin 353 | } 354 | } 355 | 356 | // Can this be even more specific? Maybe D2::Types::NonPlayerUnit? 357 | wchar_t* __fastcall UnitVisualname(D2::Types::LivingUnit *pUnit) { 358 | // For now it falls under item info, as it is as item/unit level 359 | if (!Settings["itemInfo"]) return D2::GetUnitName(pUnit); 360 | 361 | const size_t dwSize = 1024; 362 | // Since it returns directly to an append function we just need one static 363 | // buffer here to reuse, because there's no guarantee that the stack will be 364 | // preserved. Also Visual Studio complains about returning a temporary variable. 365 | static wchar_t tmpBuffer[dwSize]; 366 | 367 | swprintf(tmpBuffer, dwSize, L"%ls (%d)", D2::GetUnitName(pUnit), D2::GetUnitStat(pUnit, 12/*level*/, 0)); 368 | return tmpBuffer; 369 | } 370 | 371 | // This feature class registers itself. 372 | class : public Feature { 373 | DWORD disableShake = 0; 374 | public: 375 | void init() { 376 | if (!Settings["disableSplash"]) { 377 | SplashScreenDialogSetup(); 378 | MemoryPatch(0x42fb40) << CALL(SplashScreenHook); 379 | } 380 | 381 | MemoryPatch(0x4f5621) << NOP_TO(0x4f5672); // Allow multiple windows open 382 | 383 | MemoryPatch(0x43BF60) << ASM::RET; // Prevent battle.net connections 384 | MemoryPatch(0x515FB1) << BYTE(0x01); // Delay of 1 on cleaning up sounds after quiting game 385 | MemoryPatch(0x4781AC) << BYTESEQ{ 0x6A, 0x05, 0x90, 0x90, 0x90 }; // Hyperjoin for TCP/IP games 386 | MemoryPatch(GetItemName_Original) << JUMP(GetItemName_Intercept); 387 | MemoryPatch(ItemDescription_Patches[0]) << CALL(ItemDescription_Hook); 388 | MemoryPatch(ItemDescription_Patches[1]) << CALL(ItemDescription_Hook); 389 | MemoryPatch(ItemDescription_Patches[2]) << CALL(ItemDescription_Hook); 390 | 391 | // Disable coloring socketed items grey 392 | MemoryPatch(SocketNotGrey_Patches[0]) << DWORD(0x400000); 393 | MemoryPatch(SocketNotGrey_Patches[1]) << DWORD(0x400000); 394 | MemoryPatch(SocketNotGrey_Patches[2]) << DWORD(0x400000); 395 | 396 | // override item colors when draw on the floor 397 | MemoryPatch(0x452924) << JUMP(ItemColor_Hook); 398 | 399 | // Fix RandTransforms.dat for LoD, to colorize monsters properly 400 | MemoryPatch(0x4666A5) << BYTE(0x26); // RandTransforms 401 | 402 | MemoryPatch(0x65C34E) << JUMP(GetQuestState_Intercept); 403 | MemoryPatch(0x45ADE8) << CALL(_drawAutoMapInfo); 404 | MemoryPatch(0x454ba8) << CALL(UnitVisualname); 405 | 406 | // Repair/Arrow adjustments modified from jaenster's code 407 | MemoryPatch(0x497D72) << BYTESEQ{ 0x8D, 0x4D, 0xB8, 0xBA } << DWORD(0) << CALL(DrawRepairImageAlertIntercept) << BYTESEQ{ 0x5F, 0x5E, 0x8B, 0xE5, 0x5D, 0xC2, 0x04, 0 }; 408 | MemoryPatch(0x497CBA) << BYTESEQ{ 0x8D, 0x4D, 0xB8, 0xBA } << DWORD(1) << CALL(DrawRepairImageAlertIntercept) << BYTESEQ{ 0x5F, 0x5E, 0x8B, 0xE5, 0x5D, 0xC2, 0x04, 0 }; 409 | 410 | AutomapInfoHooks.push_back([]() -> std::wstring { 411 | return version; 412 | }); 413 | } 414 | 415 | void gameLoop() { 416 | if (Settings["disableShake"] != disableShake) { 417 | if (Settings["disableShake"]) { 418 | MemoryPatch(0x476D40) << ASM::RET; // Ignore shaking requests 419 | } 420 | else { 421 | MemoryPatch(0x476D40) << REVERT(1); 422 | } 423 | } 424 | 425 | disableShake = Settings["disableShake"]; 426 | } 427 | 428 | void oogLoop() { 429 | if (Settings["regenMap"] && !State["regenMap"]) { 430 | MemoryPatch(0x56A200) << BYTE(0xEB); 431 | State["regenMap"] = true; 432 | } 433 | else if (!Settings["regenMap"] && State["regenMap"]) { 434 | MemoryPatch(0x56A200) << REVERT(1); 435 | State["regenMap"] = false; 436 | } 437 | } 438 | 439 | void oogPostDraw() { 440 | DWORD width = 0, height = 0, fileno = 1; 441 | height = D2::GetTextSize(version.c_str(), &width, &fileno); 442 | D2::DrawGameText(version.c_str(), D2::ScreenWidth - width - 5, D2::ScreenHeight - 5, 4, 0); 443 | } 444 | 445 | bool keyEvent(DWORD keyCode, bool down, DWORD flags) { 446 | switch (keyCode) { 447 | case VK_PAUSE: 448 | State["paused"] = !State["paused"]; 449 | if (State["paused"]) { 450 | gamelog << COLOR(2) << "Game paused." << std::endl; 451 | } 452 | else { 453 | gamelog << COLOR(1) << "Game unpaused." << std::endl; 454 | } 455 | return false; 456 | } 457 | 458 | return true; 459 | } 460 | 461 | } feature; 462 | -------------------------------------------------------------------------------- /features/Omnivision.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @description patch the Line Of Sight, so we can see monsters on the other side of a wall 3 | * @Author Jaenster 4 | */ 5 | 6 | #include "headers/feature.h" 7 | #include "headers/common.h" 8 | #include "headers/hook.h" 9 | #include "headers/remote.h" 10 | 11 | namespace Omnivision { 12 | 13 | __declspec(naked) void __fastcall GetRoomColors_relocated(D2::Types::Room1* pRoom, BYTE* gamma, BYTE* red, BYTE* green, BYTE* blue) { 14 | static ASMPTR GetRoomColors_Rejoin = 0x66bfd6; 15 | __asm { 16 | push ebp 17 | mov ebp, esp 18 | mov eax, dword ptr [ecx + 0x58] 19 | jmp GetRoomColors_Rejoin 20 | } 21 | } 22 | 23 | void __fastcall GetRoomColors_override(D2::Types::Room1* pRoom, BYTE* gamma, BYTE* red, BYTE* green, BYTE* blue) { 24 | if (Settings["debugMode"] && Settings["debugModeType"] == DebugMode::DARK) { 25 | *gamma = *red = *green = *blue = 1; 26 | return; 27 | } 28 | else if (Settings["omnivision"] || Settings["debugMode"]) { 29 | *gamma = *red = *green = *blue = 0xFF; 30 | return; 31 | } 32 | 33 | GetRoomColors_relocated(pRoom, gamma, red, green, blue); 34 | } 35 | 36 | DWORD __fastcall playerCanSee_override(DWORD value) { 37 | if (Settings["omnivision"] || Settings["debugMode"]) { 38 | return 1; 39 | } 40 | 41 | return value; 42 | } 43 | 44 | __declspec(naked) void playerCanSee_hook() { 45 | static ASMPTR playerCanSeeOriginal = 0x4dc710; 46 | __asm { 47 | call playerCanSeeOriginal 48 | mov ecx, eax 49 | call playerCanSee_override 50 | ret 51 | } 52 | } 53 | 54 | BOOL useLightRadius() { 55 | return !Settings["debugMode"] || Settings["debugModeType"] != DebugMode::DARK; 56 | } 57 | 58 | __declspec(naked) void lightUnitsOverride() { 59 | static ASMPTR originalCall = 0x475780, lightUnitsOverride_rejoin = 0x475892, lightUnitsOverride_skip = 0x4758c6; 60 | __asm { 61 | pushad 62 | call useLightRadius 63 | test eax, eax 64 | popad 65 | jnz allow 66 | jmp lightUnitsOverride_skip 67 | 68 | allow: 69 | call originalCall 70 | jmp lightUnitsOverride_rejoin 71 | } 72 | } 73 | 74 | class : public Feature { 75 | public: 76 | void init() { 77 | //gamelog << COLOR(4) << "Installing debug omnivision..." << std::endl; 78 | MemoryPatch(0x66bfd0) << JUMP(GetRoomColors_override); 79 | MemoryPatch(0x4dc864) << CALL(playerCanSee_hook); 80 | MemoryPatch(0x47588d) << JUMP(lightUnitsOverride); 81 | } 82 | 83 | } feature; 84 | 85 | } 86 | -------------------------------------------------------------------------------- /features/PacketsClient.cpp: -------------------------------------------------------------------------------- 1 | #include "headers/feature.h" 2 | #include "headers/common.h" 3 | #include "headers/hook.h" 4 | #include "headers/remote.h" 5 | 6 | // SID (SERVER -> CLIENT) 7 | ASMPTR NET_SID_CLIENT_IncomingPacketHandler_Call = 0x51c7af; 8 | REMOTEFUNC(void __fastcall, NET_SID_CLIENT_IncomingPacketHandler, (BYTE nPacketId, BYTE* pBytes, size_t nSize), 0x521b00); 9 | 10 | void __fastcall NET_SID_CLIENT_IncomingPacketHandler_Replacement(BYTE nPacketId, BYTE* pBytes, size_t nSize) 11 | { 12 | NET_SID_CLIENT_IncomingPacketHandler(nPacketId, pBytes, nSize); 13 | 14 | if (Settings["debugPackets"]) { 15 | gamelog << COLOR(2) << "SID " << COLOR(4) << "(SERVER -> CLIENT)" << COLOR(0) << " 0x" << std::hex << nPacketId << std::dec << " size of " << nSize << std::endl; 16 | } 17 | } 18 | 19 | // SID (CLIENT -> SERVER) 20 | ASMPTR NET_SID_CLIENT_Send_Original = 0x51c5c0, NET_SID_CLIENT_Send_Rejoin = 0x51c5c8; 21 | 22 | __declspec(naked) void __fastcall NET_SID_CLIENT_Send_Relocated(INT nPacketId, BYTE* pBytes, size_t nSizeNoHeader) { 23 | __asm { 24 | push ebp 25 | mov ebp, esp 26 | mov eax, 0x200C 27 | jmp NET_SID_CLIENT_Send_Rejoin 28 | } 29 | } 30 | 31 | void __fastcall NET_SID_CLIENT_Send_Hook(INT nPacketId, BYTE* pBytes, size_t nSizeNoHeader) 32 | { 33 | NET_SID_CLIENT_Send_Relocated(nPacketId, pBytes, nSizeNoHeader); 34 | 35 | if (Settings["debugPackets"]) { 36 | void** puEBP = NULL; 37 | __asm { mov puEBP, ebp }; 38 | void* pvReturn = puEBP[1]; 39 | 40 | puEBP = (void**)puEBP[0]; 41 | void* pvReturn2 = puEBP[1]; 42 | 43 | printf("SID (CLIENT -> SERVER) Packet ID: 0x%02x -> Caller: 0x%08x, Caller's Caller 0x%08x \n", (BYTE)nPacketId, (int)pvReturn, (int)pvReturn2); 44 | 45 | gamelog << COLOR(1) << "SID " << COLOR(4) << "(CLIENT -> SERVER)" << COLOR(0) << " 0x" << std::hex << (BYTE)nPacketId << std::dec << " size of " << nSizeNoHeader << std::endl; 46 | } 47 | } 48 | 49 | // MCP (SERVER -> CLIENT) 50 | ASMPTR NET_MCP_CLIENT_IncomingPacketsHandler_Call = 0x449ba9; 51 | REMOTEFUNC(void __fastcall, NET_MCP_CLIENT_IncomingPacketsHandler, (BYTE* pBytes, size_t nSize), 0x44b190); 52 | 53 | void __fastcall NET_MCP_CLIENT_IncomingPacketsHandler_Replacement(BYTE* pBytes, size_t nSize) 54 | { 55 | NET_MCP_CLIENT_IncomingPacketsHandler(pBytes, nSize); 56 | 57 | if (Settings["debugPackets"]) { 58 | gamelog << COLOR(2) << "MCP " << COLOR(4) << "(SERVER -> CLIENT)" << COLOR(0) << " 0x" << std::hex << pBytes[0] << std::dec << " size of " << nSize << std::endl; 59 | } 60 | } 61 | 62 | // MCP (CLIENT -> SERVER) 63 | ASMPTR NET_MCP_CLIENT_Send_Original = 0x6bc8a0, NET_MCP_CLIENT_Send_Rejoin = 0x6bc8a8; 64 | 65 | __declspec(naked) void __stdcall NET_MCP_CLIENT_Send_Relocated(void* pQSocket, BYTE* szData, short nDataSizeNoHeader) { 66 | __asm { 67 | push ebp 68 | mov ebp, esp 69 | mov eax, 0x2070 70 | jmp NET_MCP_CLIENT_Send_Rejoin 71 | } 72 | } 73 | 74 | void __stdcall NET_MCP_CLIENT_Send_Hook(void* pQSocket, BYTE* pBytes, short nDataSizeNoHeader) 75 | { 76 | NET_MCP_CLIENT_Send_Relocated(pQSocket, pBytes, nDataSizeNoHeader); 77 | 78 | if (Settings["debugPackets"]) { 79 | void** puEBP = NULL; 80 | __asm { mov puEBP, ebp }; 81 | void* pvReturn = puEBP[1]; 82 | 83 | puEBP = (void**)puEBP[0]; 84 | void* pvReturn2 = puEBP[1]; 85 | 86 | printf("MCP (CLIENT -> SERVER) Packet ID: 0x%02x -> Caller: 0x%08x, Caller's Caller 0x%08x \n", pBytes[0], (int)pvReturn, (int)pvReturn2); 87 | 88 | gamelog << COLOR(1) << "MCP " << COLOR(4) << "(CLIENT -> SERVER)" << COLOR(0) << " 0x" << std::hex << pBytes[0] << std::dec << " size of " << nDataSizeNoHeader << std::endl; 89 | } 90 | } 91 | 92 | // D2GS (SERVER -> CLIENT) 93 | ASMPTR NET_D2GS_CLIENT_PacketHandle_to0xAF_Call = 0x44c73b; 94 | REMOTEFUNC(void __fastcall, NET_D2GS_CLIENT_PacketHandle_to0xAF, (BYTE* pBytes, size_t nSize), 0x45f7b0); 95 | 96 | ASMPTR NET_D2GS_CLIENT_PacketHandle_from0xAF_Call1 = 0x44c70c; 97 | ASMPTR NET_D2GS_CLIENT_PacketHandle_from0xAF_Call2 = 0x44bb4e; 98 | ASMPTR NET_D2GS_CLIENT_PacketHandle_from0xAF_Call3 = 0x44bd5a; 99 | REMOTEFUNC(void __fastcall, NET_D2GS_CLIENT_PacketHandle_from0xAF, (BYTE* pBytes, size_t nSize), 0x45c850); 100 | 101 | void __fastcall NET_D2GS_CLIENT_PacketHandler_Replacement(BYTE* pBytes, size_t nSize) 102 | { 103 | if (nSize != 0xffffffff && Settings["debugPackets"]) 104 | gamelog << COLOR(2) << "D2GS " << COLOR(4) << "(SERVER -> CLIENT)" << COLOR(0) << " 0x" << std::hex << pBytes[0] << std::dec << " size of " << nSize << std::endl; 105 | 106 | if (pBytes[0] >= 0xAF) { 107 | NET_D2GS_CLIENT_PacketHandle_from0xAF(pBytes, nSize); 108 | } 109 | else 110 | { 111 | NET_D2GS_CLIENT_PacketHandle_to0xAF(pBytes, nSize); 112 | } 113 | } 114 | 115 | // D2GS (CLIENT -> SERVER) 116 | ASMPTR NET_D2GS_CLIENT_Send_Original = 0x52ae50, NET_D2GS_CLIENT_Send_Rejoin = 0x52ae62; 117 | ASMPTR pD2PacketsClientBuffer = 0x882b34; 118 | 119 | __declspec(naked) void* __stdcall NET_D2GS_CLIENT_Send_Relocated(size_t nSize, int Unused, BYTE* pBytes) { 120 | __asm { 121 | push ebp 122 | mov ebp, esp 123 | cmp dword ptr[pD2PacketsClientBuffer], 0x0 124 | jnz back 125 | xor eax, eax 126 | pop ebp 127 | ret 0xc 128 | 129 | back: 130 | jmp NET_D2GS_CLIENT_Send_Rejoin 131 | } 132 | } 133 | 134 | void* __stdcall NET_D2GS_CLIENT_Send_Hook(size_t nSize, int Unused, BYTE* pBytes) 135 | { 136 | void* ret = NET_D2GS_CLIENT_Send_Relocated(nSize, Unused, pBytes); 137 | 138 | if (Settings["debugPackets"]) { 139 | void** puEBP = NULL; 140 | __asm { mov puEBP, ebp }; 141 | void* pvReturn = puEBP[1]; 142 | 143 | puEBP = (void**)puEBP[0]; 144 | void* pvReturn2 = puEBP[1]; 145 | 146 | printf("D2GS (CLIENT -> SERVER) Packet ID: 0x%02x -> Caller: 0x%08x, Caller's Caller 0x%08x \n", pBytes[0], (int)pvReturn, (int)pvReturn2); 147 | 148 | gamelog << COLOR(1) << "D2GS " << COLOR(4) << "(CLIENT -> SERVER)" << COLOR(0) << " 0x" << std::hex << pBytes[0] << std::dec << " size of " << nSize << std::endl; 149 | } 150 | 151 | return ret; 152 | } 153 | 154 | namespace Template { 155 | 156 | class : public Feature { 157 | public: 158 | void init() { 159 | // SERVER -> CLIENT 160 | // SID 161 | MemoryPatch(NET_SID_CLIENT_IncomingPacketHandler_Call) << CALL(NET_SID_CLIENT_IncomingPacketHandler_Replacement); 162 | 163 | // MCP 164 | MemoryPatch(NET_MCP_CLIENT_IncomingPacketsHandler_Call) << CALL(NET_MCP_CLIENT_IncomingPacketsHandler_Replacement); 165 | 166 | // D2GS 167 | MemoryPatch(NET_D2GS_CLIENT_PacketHandle_to0xAF_Call) << CALL(NET_D2GS_CLIENT_PacketHandler_Replacement); 168 | MemoryPatch(NET_D2GS_CLIENT_PacketHandle_from0xAF_Call1) << CALL(NET_D2GS_CLIENT_PacketHandler_Replacement); 169 | MemoryPatch(NET_D2GS_CLIENT_PacketHandle_from0xAF_Call2) << CALL(NET_D2GS_CLIENT_PacketHandler_Replacement); 170 | MemoryPatch(NET_D2GS_CLIENT_PacketHandle_from0xAF_Call3) << CALL(NET_D2GS_CLIENT_PacketHandler_Replacement); 171 | 172 | // CLIENT -> SERVER 173 | // SID 174 | MemoryPatch(NET_SID_CLIENT_Send_Original) << JUMP(NET_SID_CLIENT_Send_Hook); 175 | 176 | // MCP 177 | MemoryPatch(NET_MCP_CLIENT_Send_Original) << JUMP(NET_MCP_CLIENT_Send_Hook); 178 | 179 | // D2GS 180 | MemoryPatch(NET_D2GS_CLIENT_Send_Original) << JUMP(NET_D2GS_CLIENT_Send_Hook); 181 | } 182 | } feature; 183 | 184 | } 185 | -------------------------------------------------------------------------------- /features/PreventSockets.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Prevent the fact you can socket an item, to prevent miss click socketing 3 | * @Author Jaenster 4 | */ 5 | 6 | #include "headers/feature.h" 7 | #include "headers/common.h" 8 | #include "headers/hook.h" 9 | #include "headers/remote.h" 10 | namespace PreventSockets { 11 | 12 | unsigned int __fastcall getSetting() { 13 | return Settings["preventSockets"]; 14 | } 15 | 16 | __declspec(naked) unsigned int __fastcall myIntercept() { 17 | static ASMPTR back = 0x48448f; 18 | __asm { 19 | // Original code // 3b c6 1b c0 20 | cmp eax, ESI 21 | sbb eax, eax 22 | 23 | // since ESI is not needed anymore we can use it 24 | mov esi, eax // backup eax 25 | 26 | call getSetting 27 | xchg esi, eax 28 | 29 | // esi got value of setting 30 | // eax got actual number of open sockets on target 31 | test esi, esi 32 | jz Continue 33 | 34 | // fake that we have zero available sockets 35 | mov eax, 0 36 | 37 | Continue: 38 | 39 | // Last statement we overriden with the jump 40 | pop edi // 5f 41 | JMP back 42 | } 43 | } 44 | 45 | class : public Feature { 46 | public: 47 | void init() { 48 | MemoryPatch(0x48448a) << JUMP(myIntercept); 49 | } 50 | 51 | bool keyEvent(DWORD keyCode, bool down, DWORD flags) { 52 | switch (keyCode) { 53 | case VK_INSERT: 54 | bool isActive = (Settings["preventSockets"] = !Settings["preventSockets"]); 55 | gamelog << "Preventing sockets: " << COLOR(isActive ? 2 : 1) << (isActive ? "on": "off") << std::endl; 56 | SaveSettings(); 57 | return false; 58 | } 59 | 60 | return true; 61 | } 62 | 63 | } feature; 64 | 65 | } 66 | -------------------------------------------------------------------------------- /features/RoomInit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace RoomInit { 6 | 7 | ASMPTR RoomInit_Original = 0x542b40, RoomInit_Rejoin = 0x542b46; 8 | 9 | __declspec(naked) void 10 | __fastcall RoomInit_Relocated(D2::Types::IncompleteGameData *pGame, D2::Types::Room1 *pRoom1) { 11 | __asm { 12 | push ebp 13 | mov ebp, esp 14 | sub esp, 0x18 15 | jmp RoomInit_Rejoin 16 | } 17 | } 18 | 19 | void __fastcall RoomInit_Hook(D2::Types::IncompleteGameData *pGame, D2::Types::Room1 *pRoom1) { 20 | RoomInit_Relocated(pGame, pRoom1); 21 | 22 | for (Feature *f = Features; f; f = f->next) { 23 | f->roomInit(pGame, pRoom1); 24 | } 25 | } 26 | 27 | class : public Feature { 28 | public: 29 | void init() override { 30 | MemoryPatch(RoomInit_Original) << JUMP(RoomInit_Hook); 31 | } 32 | } feature; 33 | } -------------------------------------------------------------------------------- /features/Swatch.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Enables a color swatch command: /swatch 3 | */ 4 | 5 | #include "headers/feature.h" 6 | #include "headers/hook.h" 7 | #include "headers/common.h" 8 | #include "headers/remote.h" 9 | #include 10 | 11 | wchar_t wHex[] = L"0123456789ABCDEF"; 12 | 13 | // This feature class registers itself. 14 | class : public Feature { 15 | public: 16 | void allPostDraw() { 17 | DWORD fontnum = 8, height, width; 18 | D2::SetFont(fontnum); 19 | if (State["drawSwatch"]) { 20 | wchar_t msg[3] = { 0 }; 21 | int color, gridsize = 24; 22 | for (int x = 0; x < 32; x++) { 23 | for (int y = 0; y < 8; y++) { 24 | color = (x << 3) | y; 25 | DrawRectangle({ x * gridsize, y * gridsize }, { x * gridsize + gridsize, y * gridsize + gridsize }, color); 26 | msg[0] = wHex[color >> 4]; 27 | msg[1] = wHex[color & 15]; 28 | height = D2::GetTextSize(msg, &width, &fontnum); 29 | D2::DrawGameText(msg, x * gridsize + (gridsize - width) / 2, (y + 1) * gridsize, 0, 0); 30 | } 31 | } 32 | } 33 | } 34 | } feature; 35 | -------------------------------------------------------------------------------- /features/Template.cpp: -------------------------------------------------------------------------------- 1 | #include "headers/feature.h" 2 | #include "headers/common.h" 3 | #include "headers/hook.h" 4 | 5 | namespace Template { 6 | 7 | class : public Feature { 8 | public: 9 | void init() { 10 | //gamelog << COLOR(4) << "Installing template" << std::endl; 11 | } 12 | } feature; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /features/TxtOverride.cpp: -------------------------------------------------------------------------------- 1 | #include "headers/feature.h" 2 | #include "headers/common.h" 3 | #include "headers/hook.h" 4 | #include "headers/remote.h" 5 | 6 | using D2::Types::RunesTable; 7 | using D2::Types::ItemRatioTable; 8 | using D2::Types::UniqueItemsTable; 9 | using D2::Types::TreasureClassExTable; 10 | using D2::Types::BINField; 11 | 12 | struct CubeMainIncomplete { 13 | BYTE enabled; 14 | BYTE ladder; 15 | BYTE unk[326]; 16 | }; 17 | 18 | ASMPTR CreateTxtTableArray_Rejoin = 0x6122f9; 19 | ASMPTR CreateTxtTableArray_Original = 0x6122f0; 20 | 21 | __declspec(naked) void* __stdcall CreateTxtTableArray_Relocated(void* pMemory, char* szTableName, BINField* pBinFieldInput, int* nTxtTableSize, int nLineLength) { 22 | __asm { 23 | push ebp 24 | mov ebp, esp 25 | sub esp, 0x11c 26 | jmp CreateTxtTableArray_Rejoin 27 | } 28 | } 29 | 30 | void* __stdcall CreateTxtTableArray_Intercept(void* pMemory, char* szTableName, BINField* pBinFieldInput, int* nTxtTableSize, int nLineLength) { 31 | void* table; 32 | table = CreateTxtTableArray_Relocated(pMemory, szTableName, pBinFieldInput, nTxtTableSize, nLineLength); 33 | 34 | if (Settings["ladderItems"] && strcmp(szTableName, "runes") == 0) { 35 | int tableSize = *nTxtTableSize; 36 | RunesTable* runes = (RunesTable*)table; 37 | runes = (RunesTable*)table; 38 | 39 | gamelog << COLOR(2) << "Enabling Ladder Runewords" << std::endl; 40 | 41 | for (int c = 0; c < tableSize; c++) { 42 | runes[c].Server = 0; 43 | } 44 | } 45 | else if (Settings["ladderItems"] && strcmp(szTableName, "cubemain") == 0) { 46 | int tableSize = *nTxtTableSize; 47 | CubeMainIncomplete* cubemain = (CubeMainIncomplete*)table; 48 | gamelog << COLOR(2) << "Enabling Ladder Cube Recipes" << std::endl; 49 | 50 | for (int c = 0; c < tableSize; c++) { 51 | cubemain[c].ladder = 0; 52 | } 53 | } 54 | else if (Settings["rebalanceDrops"] && strcmp(szTableName, "itemratio") == 0) { 55 | int tableSize = *nTxtTableSize; 56 | ItemRatioTable* itemratio = (ItemRatioTable*)table; 57 | 58 | gamelog << COLOR(3) << "Tweaking Item Drop Rates for Single Player" << std::endl; 59 | 60 | for (int c = 0; c < tableSize; c++) { 61 | itemratio[c].unique.divisor = itemratio[c].set.divisor = itemratio[c].rare.divisor = itemratio[c].magic.divisor; 62 | itemratio[c].unique.min = itemratio[c].set.min = itemratio[c].rare.min = itemratio[c].magic.min; 63 | itemratio[c].unique.value = itemratio[c].magic.value * 3; 64 | itemratio[c].set.value = itemratio[c].rare.value = itemratio[c].magic.value * 2; 65 | } 66 | } 67 | else if (Settings["ladderItems"] && strcmp(szTableName, "uniqueitems") == 0) { 68 | int tableSize = *nTxtTableSize; 69 | UniqueItemsTable* uniqueitems = (UniqueItemsTable*)table; 70 | 71 | gamelog << COLOR(2) << "Enabling Ladder Unique Items" << std::endl; 72 | 73 | for (int c = 0; c < tableSize; c++) { 74 | uniqueitems[c].ladder = false; 75 | } 76 | } 77 | else if (Settings["rebalanceDrops"] && strcmp(szTableName, "treasureclassex") == 0) { 78 | TreasureClassExTable* treasureclassex = (TreasureClassExTable*)table; 79 | TreasureClassExTable* runes = treasureclassex + 25; 80 | 81 | gamelog << COLOR(3) << "Tweaking Rune Drop Rates for Single Player" << std::endl; 82 | 83 | for (int c = 1; c < 17; c++) { 84 | runes[c].prob[2] = 2 + ((c * c * c * 798) >> 12); 85 | } 86 | 87 | // Adjust Countess drop rates. 88 | strcpy_s(treasureclassex[837].items[0], "Runes 5"); 89 | strcpy_s(treasureclassex[838].items[0], "Runes 11"); 90 | strcpy_s(treasureclassex[839].items[0], "Runes 17"); 91 | treasureclassex[837].nodrop = treasureclassex[838].nodrop = treasureclassex[839].nodrop; 92 | } 93 | 94 | return table; 95 | } 96 | 97 | namespace TxtOverride { 98 | 99 | class : public Feature { 100 | public: 101 | void init() { 102 | MemoryPatch(CreateTxtTableArray_Original) << JUMP(CreateTxtTableArray_Intercept); 103 | } 104 | } feature; 105 | 106 | } 107 | -------------------------------------------------------------------------------- /features/Weather.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Make weather toggleable 3 | * @Author Jaenster 4 | */ 5 | 6 | #include "headers/feature.h" 7 | #include "headers/common.h" 8 | #include "headers/hook.h" 9 | #include "headers/remote.h" 10 | 11 | REMOTEFUNC(void, DrawWeather, (), 0x473910); 12 | 13 | namespace Weather { 14 | 15 | void drawWeatherIntercept() { 16 | if (!Settings["disableWeather"] && !Settings["debugMode"]) DrawWeather(); 17 | } 18 | 19 | class : public Feature { 20 | public: 21 | void init() { 22 | MemoryPatch(0x476D23) << CALL(drawWeatherIntercept); 23 | } 24 | 25 | } feature; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /framework/feature.cpp: -------------------------------------------------------------------------------- 1 | #include "headers/common.h" 2 | #include "headers/feature.h" 3 | #include "headers/remote.h" 4 | #include "headers/D2Structs.h" 5 | #include 6 | 7 | HotkeyCallbackMap HotkeyCallbacks; 8 | AutomapInfoCallbackList AutomapInfoHooks; 9 | InputCallbackMap ChatInputCallbacks; 10 | StateMap State; 11 | StateMap Settings; 12 | Feature* Features; 13 | 14 | Feature::Feature() { 15 | this->next = Features; 16 | Features = this; 17 | } 18 | 19 | Feature::~Feature() { } 20 | void Feature::init() { } 21 | void Feature::postInit() { } 22 | void Feature::deinit() { } 23 | void Feature::gameLoop() { } 24 | void Feature::oogLoop() { } 25 | void Feature::gameServerLoop(D2::Types::IncompleteGameData* pGame) { } 26 | bool Feature::windowMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { return true; } 27 | bool Feature::keyEvent(DWORD keyCode, bool down, DWORD flags) { return true; } 28 | bool Feature::chatInput(InputStream msg) { return true; } 29 | void Feature::gameUnitPreDraw() { } 30 | void Feature::gameUnitPostDraw() { } 31 | void Feature::gameAutomapPreDraw() { } 32 | void Feature::gameAutomapPostDraw() { } 33 | void Feature::gamePostDraw() { } 34 | void Feature::oogPostDraw() { } 35 | void Feature::allPostDraw() { } 36 | void Feature::allFinalDraw() { } 37 | void Feature::preDraw() { } 38 | void Feature::roomInit(D2::Types::IncompleteGameData* pGame, D2::Types::Room1* pRoom1) { } 39 | void Feature::serverExpAward(DWORD exp, Ghidra::D2UnitStrc* pUnit, Ghidra::D2GameStrc* pGame) { } 40 | void Feature::valueFromServer(D2::Types::LivingUnit* unit, int value, char color) { } 41 | void Feature::serverGetCustomData(int clientId, char* pBytes, int nSize) { } 42 | void Feature::clientGetCustomData(char* pBytes, int nSize) { } 43 | -------------------------------------------------------------------------------- /framework/ghidra.extensions.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @Author Jaenster 4 | * @Description 5 | 6 | Please read headers/ghidra/readme.md first 7 | 8 | This file is automatically read by the ghidra-tooling script and these methods are automaticlly attached with header to ghidra.h 9 | https://github.com/jaenster/ghidra-tooling/ 10 | 11 | 12 | There are 2 ways of defining a method 13 | 1) Ghidra::D2RoomExStrc::getWorldX() 14 | 2) Within correct namespace block 15 | 16 | Do not: 17 | - Put anything at the same line as the definition. (dont put comments before the line of code) 18 | - Dont use depth (1 level max) namespace tags. My regex isn't that fancy 19 | - I dont want to write a full c++ parser so please keep the function definitions a bit clean 20 | - Dont write templated functions (might add this in the future) 21 | 22 | Please do not create function signatures separately from the definition. As it's signature gets included in Ghidra.h anyway 23 | IDE's dont always get external changes, so if script is running with live reloading, simply open ghidra.h for your IDE 24 | to automatically update the file 25 | 26 | */ 27 | 28 | #include "headers/D2Structs.h" // DPOINT 29 | #include "headers/ghidra.h" 30 | namespace Ghidra { 31 | 32 | D2UnitPlayer* D2UnitStrc::asPlayer() { 33 | return reinterpret_cast(this); 34 | } 35 | 36 | D2UnitMonster* D2UnitStrc::asMonster() { 37 | return reinterpret_cast(this); 38 | } 39 | 40 | D2UnitMissile* D2UnitStrc::asMissile() { 41 | return reinterpret_cast(this); 42 | } 43 | 44 | D2UnitObject* D2UnitStrc::asObject() { 45 | return reinterpret_cast(this); 46 | } 47 | 48 | D2UnitItem* D2UnitStrc::asItem() { 49 | return reinterpret_cast(this); 50 | } 51 | 52 | D2UnitWarp* D2UnitStrc::asWarp() { 53 | return reinterpret_cast(this); 54 | } 55 | 56 | DWORD D2RoomExStrc::getWorldX() { 57 | return pRoom->sCoords.dwXStart; 58 | } 59 | 60 | DWORD D2RoomExStrc::getWorldY() { 61 | return pRoom->sCoords.dwYStart; 62 | } 63 | 64 | DWORD D2RoomExStrc::getWorldWidth() { 65 | return pRoom->sCoords.dwXSize; 66 | } 67 | 68 | 69 | DWORD D2RoomExStrc::getWorldHeight() { 70 | return pRoom->sCoords.dwYSize; 71 | } 72 | 73 | DWORD D2RoomExStrc::getCollision(DWORD localx, DWORD localy, WORD mask) { 74 | // untested, converted blindly from current version 75 | return pRoom->pCollisionGrid->pMapStart[localx + localy * pRoom->pCollisionGrid->World.WorldSize.x] & mask; 76 | } 77 | 78 | DPOINT D2PresetUnitStrc::pos(D2RoomExStrc* pRoom, DPOINT adjust) { 79 | return { adjust.x + (double)pRoom->sCoords.WorldPosition.x * 5 + (double)this->nPosX, adjust.y + (double)pRoom->sCoords.WorldPosition.y * 5 + (double)this->nPosY }; 80 | } 81 | 82 | DPOINT D2UnitStrc::pos() { 83 | return this->pos(DPOINT(0.0,0.0)); 84 | } 85 | 86 | DPOINT D2UnitStrc::pos(DPOINT adjust) { 87 | if (this->eUnitType == UNIT_OBJECT || this->eUnitType == UNIT_WARP || this->eUnitType == UNIT_ITEM) { 88 | return { adjust.x + (double)this->pPath.pStaticPath->nPosX, adjust.y + (double)this->pPath.pStaticPath->nPosY }; 89 | } 90 | 91 | return { 0, 0 }; // ToDo; need to fix dynamic path as its wrong defined in ghidra 92 | } 93 | 94 | DPOINT D2UnitStrc::getTargetPos(DPOINT adjust) { 95 | if (this->eUnitType == UNIT_OBJECT || this->eUnitType == UNIT_WARP || this->eUnitType == UNIT_ITEM) { 96 | return this->pos(adjust); 97 | } 98 | 99 | return { 0, 0 }; // ToDo; need to fix dynamic path as its wrong defined in ghidra 100 | } 101 | } -------------------------------------------------------------------------------- /framework/hook.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../headers/hook.h" 3 | #include 4 | 5 | std::unordered_map OriginalBytes; 6 | 7 | long calcRelAddr(DWORD instructionAddress, DWORD targetAddress, DWORD instructionLength) { 8 | return (long)targetAddress - (long)instructionAddress - (long)instructionLength; 9 | } 10 | 11 | template 12 | DWORD SetData(DWORD pAddr, const T& value) { 13 | DWORD dwOld, dwSize = sizeof(T); 14 | 15 | if (VirtualProtect((LPVOID)pAddr, dwSize, PAGE_READWRITE, &dwOld)) { 16 | for (DWORD i = 0; i < dwSize; i++) { 17 | if (!OriginalBytes.count(pAddr + i)) { 18 | OriginalBytes[pAddr + i] = *(BYTE*)(pAddr + i); 19 | } 20 | } 21 | 22 | T* addr = (T*)pAddr; 23 | *addr = value; 24 | VirtualProtect((LPVOID)pAddr, dwSize, dwOld, &dwOld); 25 | return dwSize; 26 | } 27 | 28 | return 0; 29 | } 30 | 31 | DWORD PatchCall(BYTE instruction, DWORD pAddr, DWORD pFunc) { 32 | DWORD dwOld, dwLen = sizeof(BYTE) + sizeof(DWORD); 33 | 34 | if (VirtualProtect((LPVOID)pAddr, dwLen, PAGE_READWRITE, &dwOld)) { 35 | for (DWORD i = 0; i < dwLen; i++) { 36 | if (!OriginalBytes.count(pAddr + i)) { 37 | OriginalBytes[pAddr + i] = *(BYTE*)(pAddr + i); 38 | } 39 | } 40 | 41 | BYTE* bytes = (BYTE*)pAddr; 42 | bytes[0] = instruction; 43 | *(long*)(bytes + 1) = calcRelAddr(pAddr, pFunc, dwLen); 44 | VirtualProtect((LPVOID)pAddr, dwLen, dwOld, &dwOld); 45 | return dwLen; 46 | } 47 | 48 | return 0; 49 | } 50 | 51 | DWORD PatchCallW(const BYTE instruction[2], DWORD pAddr, DWORD pFunc) { 52 | DWORD dwOld, dwLen = sizeof(BYTE) * 2 + sizeof(DWORD); 53 | 54 | if (VirtualProtect((LPVOID)pAddr, dwLen, PAGE_READWRITE, &dwOld)) { 55 | for (DWORD i = 0; i < dwLen; i++) { 56 | if (!OriginalBytes.count(pAddr + i)) { 57 | OriginalBytes[pAddr + i] = *(BYTE*)(pAddr + i); 58 | } 59 | } 60 | 61 | BYTE* bytes = (BYTE*)pAddr; 62 | bytes[0] = instruction[0]; 63 | bytes[1] = instruction[1]; 64 | *(long*)(bytes + 2) = calcRelAddr(pAddr, pFunc, dwLen); 65 | VirtualProtect((LPVOID)pAddr, dwLen, dwOld, &dwOld); 66 | return dwLen; 67 | } 68 | 69 | return 0; 70 | } 71 | 72 | DWORD SetBytes(DWORD pAddr, BYTE value, DWORD dwLen) { 73 | DWORD dwOld; 74 | 75 | if (dwLen > 0 && VirtualProtect((LPVOID)pAddr, dwLen, PAGE_READWRITE, &dwOld)) { 76 | for (DWORD i = 0; i < dwLen; i++) { 77 | if (!OriginalBytes.count(pAddr + i)) { 78 | OriginalBytes[pAddr + i] = *(BYTE*)(pAddr + i); 79 | } 80 | } 81 | 82 | std::memset((LPVOID)pAddr, value, dwLen); 83 | VirtualProtect((LPVOID)pAddr, dwLen, dwOld, &dwOld); 84 | return dwLen; 85 | } 86 | 87 | return 0; 88 | } 89 | 90 | DWORD RevertBytes(DWORD pAddr, DWORD dwLen) { 91 | DWORD dwOld; 92 | 93 | if (dwLen > 0 && VirtualProtect((LPVOID)pAddr, dwLen, PAGE_READWRITE, &dwOld)) { 94 | for (DWORD i = 0; i < dwLen; i++) { 95 | try { 96 | *(BYTE*)(pAddr + i) = OriginalBytes.at(pAddr + i); 97 | } catch (...) { } 98 | } 99 | VirtualProtect((LPVOID)pAddr, dwLen, dwOld, &dwOld); 100 | return dwLen; 101 | } 102 | 103 | return 0; 104 | } 105 | 106 | BYTES::BYTES(BYTE value, size_t length) { 107 | this->value = value; 108 | this->length = length; 109 | } 110 | 111 | NOP_TO::NOP_TO(LPVOID addr) { 112 | this->addr = (DWORD)addr; 113 | } 114 | 115 | NOP_TO::NOP_TO(DWORD addr) { 116 | this->addr = addr; 117 | } 118 | 119 | SKIP::SKIP(size_t length) { 120 | this->length = length; 121 | } 122 | 123 | REWIND::REWIND(size_t length) { 124 | this->length = length; 125 | } 126 | 127 | CALL::CALL(LPVOID pFunc) { 128 | this->pFunc = pFunc; 129 | } 130 | 131 | JUMP::JUMP(LPVOID pFunc) { 132 | this->pFunc = pFunc; 133 | } 134 | 135 | JUMP::JUMP(DWORD pFunc) { 136 | this->pFunc = (LPVOID)pFunc; 137 | } 138 | 139 | JUMP_EQUAL::JUMP_EQUAL(LPVOID pFunc) { 140 | this->pFunc = pFunc; 141 | } 142 | 143 | JUMP_EQUAL::JUMP_EQUAL(DWORD pFunc) { 144 | this->pFunc = (LPVOID)pFunc; 145 | } 146 | 147 | JUMP_NOT_EQUAL::JUMP_NOT_EQUAL(LPVOID pFunc) { 148 | this->pFunc = pFunc; 149 | } 150 | 151 | JUMP_NOT_EQUAL::JUMP_NOT_EQUAL(DWORD pFunc) { 152 | this->pFunc = (LPVOID)pFunc; 153 | } 154 | 155 | REVERT::REVERT(size_t length) { 156 | this->length = length; 157 | } 158 | 159 | template 160 | MemoryPatch& MemoryPatch::d(const T data) { 161 | pAddr += SetData(pAddr, data); 162 | return *this; 163 | } 164 | 165 | MemoryPatch::MemoryPatch(DWORD dwAddr) { 166 | pAddr = dwAddr; 167 | } 168 | 169 | MemoryPatch& MemoryPatch::operator << (const bool data) { return d(data); } 170 | MemoryPatch& MemoryPatch::operator << (const char data) { return d(data); } 171 | MemoryPatch& MemoryPatch::operator << (const wchar_t data) { return d(data); } 172 | MemoryPatch& MemoryPatch::operator << (const unsigned char data) { return d(data); } 173 | MemoryPatch& MemoryPatch::operator << (const short data) { return d(data); } 174 | MemoryPatch& MemoryPatch::operator << (const unsigned short data) { return d(data); } 175 | MemoryPatch& MemoryPatch::operator << (const int data) { return d(data); } 176 | MemoryPatch& MemoryPatch::operator << (const unsigned int data) { return d(data); } 177 | MemoryPatch& MemoryPatch::operator << (const long data) { return d(data); } 178 | MemoryPatch& MemoryPatch::operator << (const unsigned long data) { return d(data); } 179 | MemoryPatch& MemoryPatch::operator << (const long long data) { return d(data); } 180 | MemoryPatch& MemoryPatch::operator << (const unsigned long long data) { return d(data); } 181 | MemoryPatch& MemoryPatch::operator << (const float data) { return d(data); } 182 | MemoryPatch& MemoryPatch::operator << (const double data) { return d(data); } 183 | 184 | MemoryPatch& MemoryPatch::operator << (const BYTES bytes) { 185 | pAddr += SetBytes(pAddr, bytes.value, bytes.length); 186 | return *this; 187 | } 188 | 189 | MemoryPatch& MemoryPatch::operator << (const NOP_TO address) { 190 | if (address.addr < pAddr) { 191 | char msg[128]; 192 | sprintf_s(msg, "Your NOP_TO is already past the target address: at 0x%08x, target 0x%08x", pAddr, address.addr); 193 | std::cout << msg << std::endl; 194 | MessageBoxA(NULL, msg, "MemoryPatch Error", MB_OK); 195 | ExitProcess((UINT)-1); 196 | } 197 | else { 198 | DWORD len = address.addr - pAddr; 199 | pAddr += SetBytes(pAddr, ASM::NOP, len); 200 | } 201 | return *this; 202 | } 203 | 204 | MemoryPatch& MemoryPatch::operator << (const SKIP offset) { 205 | pAddr += offset.length; 206 | return *this; 207 | } 208 | 209 | MemoryPatch& MemoryPatch::operator << (const REWIND offset) { 210 | pAddr -= offset.length; 211 | return *this; 212 | } 213 | 214 | MemoryPatch& MemoryPatch::operator << (const CALL call) { 215 | pAddr += PatchCall(ASM::CALL, pAddr, (DWORD)call.pFunc); 216 | return *this; 217 | } 218 | 219 | MemoryPatch& MemoryPatch::operator << (const JUMP jump) { 220 | pAddr += PatchCall(ASM::JUMP, pAddr, (DWORD)jump.pFunc); 221 | return *this; 222 | } 223 | 224 | MemoryPatch& MemoryPatch::operator << (const JUMP_EQUAL jump) { 225 | BYTE ins[]{ 0x0F, 0x84 }; 226 | pAddr += PatchCallW(ins, pAddr, (DWORD)jump.pFunc); 227 | return *this; 228 | } 229 | 230 | MemoryPatch& MemoryPatch::operator << (const JUMP_NOT_EQUAL jump) { 231 | BYTE ins[]{ 0x0F, 0x85 }; 232 | pAddr += PatchCallW(ins, pAddr, (DWORD)jump.pFunc); 233 | return *this; 234 | } 235 | 236 | MemoryPatch& MemoryPatch::operator << (const REVERT revert) { 237 | pAddr += RevertBytes(pAddr, revert.length); 238 | return *this; 239 | } 240 | 241 | MemoryPatch& MemoryPatch::operator << (BYTESEQ bytes) { 242 | DWORD dwOld, dwSize = bytes.size(); 243 | 244 | if (dwSize > 0 && VirtualProtect((LPVOID)pAddr, dwSize, PAGE_READWRITE, &dwOld)) { 245 | for (DWORD i = 0; i < dwSize; i++) { 246 | if (!OriginalBytes.count(pAddr + i)) { 247 | OriginalBytes[pAddr + i] = *(BYTE*)(pAddr + i); 248 | } 249 | } 250 | 251 | BYTE* addr = (BYTE*)pAddr; 252 | for (size_t c = 0; c < dwSize; c++) { 253 | addr[c] = bytes[c]; 254 | } 255 | VirtualProtect((LPVOID)pAddr, dwSize, dwOld, &dwOld); 256 | pAddr += dwSize; 257 | } 258 | 259 | return *this; 260 | } 261 | -------------------------------------------------------------------------------- /framework/utilities.cpp: -------------------------------------------------------------------------------- 1 | #define _USE_MATH_DEFINES 2 | 3 | #include "../headers/D2Structs.h" 4 | #include "../headers/common.h" 5 | #include "../headers/feature.h" 6 | #include "../headers/utilities.h" 7 | #include "../headers/remote.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | REMOTEFUNC(void __stdcall, D2Drawline, (int X1, int Y1, int X2, int Y2, DWORD dwColor, DWORD dwAlpha), 0x4F6380) 14 | REMOTEFUNC(void __stdcall, D2DrawRectangle, (int X1, int Y1, int X2, int Y2, DWORD dwColor, DWORD dwAlpha), 0x4F6340) 15 | REMOTEFUNC(long __fastcall, GetMouseXOffset, (), 0x45AFC0) 16 | REMOTEFUNC(long __fastcall, GetMouseYOffset, (), 0x45AFB0) 17 | REMOTEREF(POINT, AutomapOffset, 0x7A5198) 18 | REMOTEREF(int, Divisor, 0x711254) 19 | 20 | namespace D2 { 21 | int ScreenWidth = 0, ScreenHeight = 0; 22 | 23 | namespace Defs { 24 | typedef POINT p_t; 25 | } 26 | } 27 | 28 | DPOINT xvector = { 16.0, 8.0 }, yvector = { -16.0, 8.0 }; 29 | 30 | std::random_device rd; // obtain a random number from hardware 31 | std::mt19937 gen(rd()); // seed the generator 32 | 33 | int randIntInRange(int low, int high) { 34 | std::uniform_int_distribution<> distr(low, high); 35 | return distr(gen); 36 | } 37 | 38 | double randDoubleInRange(double low, double high) { 39 | std::uniform_real_distribution<> distr(low, high); 40 | return distr(gen); 41 | } 42 | 43 | DPOINT::DPOINT(double x, double y) { 44 | this->x = x; 45 | this->y = y; 46 | } 47 | 48 | void DrawRectangle(POINT a, POINT b, DWORD dwColor) { 49 | if ( 50 | a.x >= 0 && a.x < D2::ScreenWidth || 51 | b.x >= 0 && b.x < D2::ScreenWidth || 52 | a.y >= 0 && a.y < D2::ScreenHeight || 53 | b.y >= 0 && b.y < D2::ScreenHeight 54 | ) { 55 | D2DrawRectangle(a.x, a.y, b.x, b.y, dwColor, 0xFF); 56 | } 57 | } 58 | 59 | void DrawLine(POINT a, POINT b, DWORD dwColor) { 60 | if ( 61 | a.x >= 0 && a.x < D2::ScreenWidth || 62 | b.x >= 0 && b.x < D2::ScreenWidth || 63 | a.y >= 0 && a.y < D2::ScreenHeight || 64 | b.y >= 0 && b.y < D2::ScreenHeight 65 | ) { 66 | D2Drawline(a.x, a.y, b.x, b.y, dwColor, 0xFF); 67 | } 68 | } 69 | 70 | void DrawDot(POINT pos, DWORD dwColor) { 71 | DrawLine({ pos.x - 1, pos.y }, { pos.x + 1, pos.y }, dwColor); 72 | DrawLine({ pos.x, pos.y - 1 }, { pos.x, pos.y + 1 }, dwColor); 73 | } 74 | 75 | DPOINT DPOINT::operator +(const DPOINT& p) { 76 | return { x + p.x, y + p.y }; 77 | } 78 | 79 | DPOINT DPOINT::operator -(const DPOINT& p) { 80 | return { x - p.x, y - p.y }; 81 | } 82 | 83 | DPOINT DPOINT::operator /(const double d) { 84 | return { x / d, y / d }; 85 | } 86 | 87 | double DPOINT::distanceTo(DPOINT target) { 88 | DPOINT v = target - *this; 89 | return sqrt(v.x * v.x + v.y + v.y); 90 | } 91 | 92 | POINT DPOINT::toScreen(POINT screenadjust) { 93 | return { 94 | screenadjust.x + (long)(x * xvector.x + y * yvector.x) - GetMouseXOffset(), 95 | screenadjust.y + (long)(x * xvector.y + y * yvector.y) - GetMouseYOffset() 96 | }; 97 | } 98 | 99 | POINT DPOINT::toAutomap(POINT screenadjust) { 100 | return { 101 | screenadjust.x + (long)((x * xvector.x + y * yvector.x) / (double)Divisor) - AutomapOffset.x + 8, 102 | screenadjust.y + (long)((x * xvector.y + y * yvector.y) / (double)Divisor) - AutomapOffset.y - 8 103 | }; 104 | } 105 | 106 | void DPOINT::DrawAutomapX(DWORD dwColor, double size) { 107 | DrawLine(DPOINT(x - size, y).toAutomap(), DPOINT(x + size, y).toAutomap(), dwColor); 108 | DrawLine(DPOINT(x, y - size).toAutomap(), DPOINT(x, y + size).toAutomap(), dwColor); 109 | } 110 | 111 | void DPOINT::DrawWorldX(DWORD dwColor, double size) { 112 | DrawLine(DPOINT(x - size, y).toScreen(), DPOINT(x + size, y).toScreen(), dwColor); 113 | DrawLine(DPOINT(x, y - size).toScreen(), DPOINT(x, y + size).toScreen(), dwColor); 114 | } 115 | 116 | void DPOINT::DrawAutomapDot(DWORD dwColor) { 117 | DrawLine(this->toAutomap({ -1, 0 }), this->toAutomap({ 1, 0 }), dwColor); 118 | DrawLine(this->toAutomap({ 0, -1 }), this->toAutomap({ 0, 1 }), dwColor); 119 | } 120 | 121 | void DPOINT::DrawWorldDot(DWORD dwColor) { 122 | DrawLine(this->toScreen({ -1, 0 }), this->toScreen({ 1, 0 }), dwColor); 123 | DrawLine(this->toScreen({ 0, -1 }), this->toScreen({ 0, 1 }), dwColor); 124 | } 125 | 126 | REMOTEFUNC(void __stdcall, NET_D2GS_SERVER_SendPacket_Helper, (char* pBytes, size_t nSize /* uses EDI for pClient*/), 0x53b280) 127 | REMOTEFUNC(void __stdcall, NET_D2GS_CLIENT_Send, (char* pBytes), 0x478350) 128 | 129 | void __stdcall serverSendPacket(Ghidra::D2ClientStrc* pClient, char* pBytes, size_t nSize) { 130 | __asm { 131 | pushad 132 | 133 | mov EDI, pClient; 134 | push nSize; 135 | push pBytes; 136 | call NET_D2GS_SERVER_SendPacket_Helper; 137 | 138 | popad 139 | } 140 | } 141 | 142 | void clientSendPacket(char* pBytes, size_t nSize) { 143 | __asm { 144 | pushad 145 | 146 | mov EDI, nSize; 147 | push pBytes; 148 | call NET_D2GS_CLIENT_Send; 149 | 150 | popad 151 | } 152 | } 153 | 154 | namespace D2 { 155 | namespace Types { 156 | void UnitAny::DrawAutomapX(DWORD dwColor, double size) { return this->pos().DrawAutomapX(dwColor, size); } 157 | void UnitAny::DrawWorldX(DWORD dwColor, double size) { return this->pos().DrawWorldX(dwColor, size); } 158 | void UnitAny::DrawAutomapDot(DWORD dwColor) { return this->pos().DrawAutomapDot(dwColor); } 159 | void UnitAny::DrawWorldDot(DWORD dwColor) { return this->pos().DrawWorldDot(dwColor); } 160 | 161 | double UnitAny::distanceTo(UnitAny* pTarget) { 162 | return this->pos().distanceTo(pTarget->pos()); 163 | } 164 | 165 | std::vector Room2::getAllPresetUnits() { 166 | std::vector ret; 167 | 168 | for (D2::Types::PresetUnit* unit = this->pPreset; unit != NULL; unit = unit->pPresetNext) { 169 | ret.push_back(unit); 170 | } 171 | 172 | return ret; 173 | } 174 | 175 | std::vector Level::getAllRoom1() { 176 | std::vector ret; 177 | 178 | for (D2::Types::Room2* room = this->pRoom2First; room != NULL; room = room->pRoom2Next) { 179 | if (room->pRoom1) { 180 | ret.push_back(room->pRoom1); 181 | } 182 | } 183 | 184 | return ret; 185 | } 186 | 187 | std::vector Level::getAllRoom2() { 188 | std::vector ret; 189 | 190 | for (D2::Types::Room2* room = this->pRoom2First; room != NULL; room = room->pRoom2Next) { 191 | ret.push_back(room); 192 | } 193 | 194 | return ret; 195 | } 196 | 197 | DPOINT PresetUnit::pos(Room2* pRoom, DPOINT adjust) { 198 | return { adjust.x + (double)pRoom->dwPosX * 5 + (double)this->dwPosX, adjust.y + (double)pRoom->dwPosY * 5 + (double)this->dwPosY }; 199 | } 200 | 201 | DPOINT UnitAny::pos(DPOINT adjust) { 202 | if (this->dwType == 2 || this->dwType == 5) { 203 | return { adjust.x + (double)this->pObjectPath->dwPosX, adjust.y + (double)this->pObjectPath->dwPosY }; 204 | } 205 | 206 | if (this->dwType == 4) { 207 | return { adjust.x + (double)this->pItemPath->dwPosX, adjust.y + (double)this->pItemPath->dwPosY }; 208 | } 209 | 210 | return { adjust.x + (double)this->pPath->xPos + (double)this->pPath->xOffset / 65536.0, adjust.y + (double)this->pPath->yPos + (double)this->pPath->yOffset / 65536.0 }; 211 | } 212 | 213 | DPOINT UnitAny::getTargetPos(DPOINT adjust) { 214 | if (this->dwType == 2 || this->dwType == 4) { 215 | return this->pos(); 216 | } 217 | 218 | return { (double)this->pPath->xTarget, (double)this->pPath->yTarget }; 219 | } 220 | 221 | Room1* LivingUnit::getRoom1() { 222 | if (this->pPath && this->pPath->pRoom1) { 223 | return this->pPath->pRoom1; 224 | } 225 | 226 | return nullptr; 227 | } 228 | 229 | Room2* LivingUnit::getRoom2() { 230 | if (this->pPath && this->pPath->pRoom1 && this->pPath->pRoom1->pRoom2) { 231 | return this->pPath->pRoom1->pRoom2; 232 | } 233 | 234 | return nullptr; 235 | } 236 | 237 | Level* LivingUnit::getLevel() { 238 | if (this->pPath && this->pPath->pRoom1 && this->pPath->pRoom1->pRoom2 && this->pPath->pRoom1->pRoom2->pLevel) { 239 | return this->pPath->pRoom1->pRoom2->pLevel; 240 | } 241 | 242 | return nullptr; 243 | } 244 | 245 | DWORD LivingUnit::unitHP() { 246 | return D2::GetUnitStat(this, 6, 0) >> 8; 247 | } 248 | 249 | bool LivingUnit::isPlayerFriendly() { 250 | return D2::GetUnitStat(this, 172, 0) == 2; 251 | } 252 | 253 | bool LivingUnit::isPlayerHostile() { 254 | return D2::GetUnitStat(this, 172, 0) == 0; 255 | } 256 | 257 | bool LivingUnit::isAttackable() { 258 | return this->dwFlags & 0x4; 259 | } 260 | 261 | bool LivingUnit::isPlayerEnemy() { 262 | return this->unitHP() > 0 && this->isPlayerHostile() && this->isAttackable(); 263 | } 264 | 265 | WORD CollMap::getCollision(DWORD localx, DWORD localy, WORD mask) { 266 | return pMapStart[localx + localy * dwSizeGameX] & mask; 267 | } 268 | 269 | WORD Room1::getCollision(DWORD localx, DWORD localy, WORD mask) { 270 | return Coll->pMapStart[localx + localy * Coll->dwSizeGameX] & mask; 271 | } 272 | 273 | WORD Room2::getCollision(DWORD localx, DWORD localy, WORD mask) { 274 | return pRoom1->Coll->pMapStart[localx + localy * pRoom1->Coll->dwSizeGameX] & mask; 275 | } 276 | 277 | DWORD Room2::getWorldX() { 278 | return pRoom1->dwXStart; 279 | } 280 | 281 | DWORD Room2::getWorldY() { 282 | return pRoom1->dwYStart; 283 | } 284 | 285 | DWORD Room2::getWorldWidth() { 286 | return pRoom1->dwXSize; 287 | } 288 | 289 | DWORD Room2::getWorldHeight() { 290 | return pRoom1->dwYSize; 291 | } 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /headers/common.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Just some common stuff. 3 | */ 4 | #pragma once 5 | 6 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | extern HINSTANCE hInst; 13 | extern HWND hwndSettings; 14 | extern wchar_t saveDir[512]; 15 | extern wchar_t settingsPath[512]; 16 | extern std::wstring filterParams[6]; 17 | 18 | class GameOutput : public std::wostream { 19 | class GameOutputBuffer : public std::wstringbuf { 20 | public: 21 | virtual int sync(); 22 | } buf; 23 | public: 24 | GameOutput(); 25 | }; 26 | 27 | std::wstring COLOR(BYTE color); 28 | 29 | const DWORD DEFAULT_FONT = 1; 30 | const std::wstring version = L"Charon v1.4.2"; 31 | extern GameOutput gamelog; 32 | 33 | namespace D2 { 34 | extern int ScreenWidth, ScreenHeight; 35 | } 36 | 37 | #pragma warning(disable : 4100) -------------------------------------------------------------------------------- /headers/dialog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | bool inRect(int x, int y, RECT r); 9 | 10 | enum class MouseButton { 11 | LEFT, 12 | RIGHT, 13 | MIDDLE, 14 | }; 15 | 16 | enum class Orientation { 17 | LEFT, 18 | RIGHT, 19 | CENTER, 20 | }; 21 | 22 | struct LineInfo { 23 | POINT a, b; 24 | int color, opacity; 25 | }; 26 | 27 | class Element { 28 | protected: 29 | RECT pos = { 0, 0, 0, 0 }; 30 | int width = 0, height = 0, backgroundColor = -1, backgroundOpacity = 192, borderColor = -1; 31 | std::function clickCallback; 32 | std::function keyHandler; 33 | bool visible = false; 34 | std::vector children; 35 | std::vector lines; 36 | std::function(std::vector lines)> drawLinesCallback; 37 | 38 | public: 39 | Element(); 40 | ~Element(); 41 | RECT getRect(); 42 | 43 | RECT getRect(int ox, int oy); 44 | void setPos(int x, int y); 45 | void setDimensions(int width, int height); 46 | void setFrame(int backgroundColor, int backgroundOpacity, int borderColor); 47 | void addLine(POINT a, POINT b, int color, int opacity = 0xFF); 48 | void getLinesCallback(std::function(std::vector lines)> drawLinesCallback); 49 | void show(); 50 | void hide(); 51 | void setVisibile(bool value); 52 | bool isVisible(); 53 | void addChild(Element* child); 54 | void onClick(std::function handler); 55 | void onKey(std::function handler); 56 | virtual void drawFrame(int ox, int oy); 57 | virtual void drawChildren(int ox, int oy); 58 | virtual void draw(int ox, int oy); 59 | bool inArea(int x, int y); 60 | virtual bool interact(int x, int y, bool down, MouseButton button); 61 | virtual bool interactKey(DWORD keyCode, bool down, DWORD flags); 62 | }; 63 | 64 | class TextElement : public Element { 65 | protected: 66 | std::function textCallback = []() -> std::wstring { return L"Text Element"; }; 67 | int font = 0, color = 4, textOffsetX = -4, textOffsetY = 0; 68 | Orientation orientation = Orientation::LEFT; 69 | 70 | public: 71 | void setFont(int value); 72 | void setColor(int value); 73 | void setText(std::wstring value); 74 | void setOrientation(Orientation value); 75 | void setText(std::function callback); 76 | void setTextOffset(int x, int y); 77 | virtual void draw(int ox, int oy); 78 | }; 79 | 80 | class Dialog : public Element { 81 | public: 82 | Dialog(); 83 | virtual bool interact(int x, int y, bool down, MouseButton button); 84 | }; 85 | 86 | #ifndef DEFINE_DIALOG_VARS 87 | extern std::vector dialogs; 88 | #endif -------------------------------------------------------------------------------- /headers/feature.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "utilities.h" 11 | #include "headers/D2Structs.h" 12 | #include "headers/ghidra.h" 13 | 14 | typedef std::wstringstream& InputStream; 15 | typedef std::function InputCallback; 16 | typedef std::unordered_map InputCallbackMap; 17 | typedef InputCallbackMap::iterator InputMapIterator; 18 | typedef std::pair InputCallbackPair; 19 | 20 | // A hotkey is like a command 21 | typedef std::function HotkeyCallback; 22 | typedef std::unordered_map HotkeyCallbackMap; 23 | typedef HotkeyCallbackMap::iterator HotkeyMapIterator; 24 | 25 | typedef std::function AutomapInfoCallback; 26 | typedef std::vector AutomapInfoCallbackList; 27 | 28 | class Feature { 29 | public: 30 | Feature* next; 31 | std::vector mpq; 32 | Feature(); 33 | ~Feature(); 34 | virtual void init(); 35 | virtual void postInit(); 36 | virtual void deinit(); 37 | virtual void gameLoop(); 38 | virtual void oogLoop(); 39 | virtual void gameServerLoop(D2::Types::IncompleteGameData* pGame); 40 | virtual bool windowMessage(HWND, UINT, WPARAM, LPARAM); 41 | virtual bool keyEvent(DWORD, bool, DWORD); 42 | virtual bool chatInput(InputStream); 43 | virtual void gameUnitPreDraw(); 44 | virtual void gameUnitPostDraw(); 45 | virtual void gameAutomapPreDraw(); 46 | virtual void gameAutomapPostDraw(); 47 | virtual void gamePostDraw(); 48 | virtual void oogPostDraw(); 49 | virtual void allPostDraw(); 50 | virtual void allFinalDraw(); 51 | virtual void preDraw(); 52 | virtual void roomInit(D2::Types::IncompleteGameData* pGame, D2::Types::Room1* pRoom1); 53 | virtual void serverExpAward(DWORD exp, Ghidra::D2UnitStrc* pUnit, Ghidra::D2GameStrc* pGame); 54 | virtual void valueFromServer(D2::Types::LivingUnit* unit, int value, char color); 55 | virtual void serverGetCustomData(int clientId, char *pBytes, int nSize); 56 | virtual void clientGetCustomData(char* pBytes, int nSize); 57 | }; 58 | 59 | extern Feature* Features; 60 | 61 | typedef std::unordered_map StateMap; 62 | 63 | extern HotkeyCallbackMap HotkeyCallbacks; 64 | extern AutomapInfoCallbackList AutomapInfoHooks; 65 | extern InputCallbackMap ChatInputCallbacks; 66 | extern StateMap State; 67 | extern StateMap Settings; 68 | void LoadSettings(); 69 | void SaveSettings(); 70 | 71 | namespace DebugMode { 72 | enum DebugModeTypes { 73 | NORMAL = 0, 74 | DARK = 1, 75 | HIDDEN = 2, 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /headers/ghidra.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // All generated files 4 | #include "./D2Structs.h" 5 | #include "./ghidra/main.h" 6 | #include "./ghidra/user.extensions.h" 7 | -------------------------------------------------------------------------------- /headers/ghidra/naked.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | /* 9 | This file is generated, do not edit by hand. 10 | Consult readme.md 11 | */ 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | #pragma once 21 | namespace Ghidra { 22 | struct D2PoolBlockStrc; 23 | struct D2PoolStrc; 24 | struct D2PoolBlockEntryStrc; 25 | struct D2PoolManagerStrc; 26 | union uD2UnitMode; 27 | struct D2SeedStrc; 28 | struct D2BitBufferStrc; 29 | struct D2WaypointStrc; 30 | struct D2UnitDataPlayerArenaStrc; 31 | struct D2PetDataStrc; 32 | struct D2PetStrc; 33 | struct D2PetListStrc; 34 | struct D2UnitDataPlayerPetsStrc; 35 | struct D2UnitDataPlayerInteractedNPCStrc; 36 | struct D2PartyDataStrc; 37 | struct D2TimerStrc; 38 | struct D2TimerListStrc; 39 | struct D2TimerQueueStrc; 40 | struct D2DrlgEnvironmentStrc; 41 | struct D2TileLibraryHashRefStrc; 42 | struct D2TileLibraryHashNodeStrc; 43 | struct D2TileLibraryHashStrc; 44 | struct D2TileRecordStrc; 45 | struct D2TileLibraryEntryStrc; 46 | struct D2DrlgTileDataStrc; 47 | struct D2DrlgRoomTilesStrc; 48 | struct D2DrlgCoordsStrc; 49 | struct D2DrlgOrthStrc; 50 | struct D2DrlgGridStrc; 51 | struct D2DrlgVertexStrc; 52 | struct D2DrlgOutdoorRoomStrc; 53 | struct D2DrlgPresetRoomStrc; 54 | union D2DrlgRoomExDataUnion; 55 | struct D2DrlgTileGridStrc; 56 | struct D2DrlgRoomTilesListStrc; 57 | struct D2LvlMazeTxt; 58 | struct D2LvlPrestTxt; 59 | struct D2DrlgFileStrc; 60 | struct D2DrlgMapStrc; 61 | struct D2DrlgLevelDataPresetArea; 62 | struct D2DrlgLevelDataWildernessLevel; 63 | union D2DrlgLevelDataUnion; 64 | struct D2DrlgActWarpsInfoStrc; 65 | struct D2DrlgActMiscSubA0Strc; 66 | struct D2DrlgStrc; 67 | struct D2DrlgLevelStrc; 68 | struct D2PresetUnitStrc; 69 | struct D2DrlgCoordStrc; 70 | struct D2RoomCoordListStrc; 71 | struct D2DrlgCoordListStrc; 72 | struct D2RoomExStrc; 73 | struct D2DrlgDeleteStrc; 74 | struct D2RoomCollisionGridStrc; 75 | struct D2DrlgRoomCoordsStrc; 76 | struct D2RoomStrc; 77 | struct D2DrlgActStrc; 78 | struct D2InactiveUnitListStrc; 79 | struct D2MonsterRegionFieldStrc; 80 | struct D2MonsterRegionStrc; 81 | struct D2QuestGUIDStrc; 82 | struct D2QuestArgStrcUnion1; 83 | struct D2QuestArgStrcUnion2; 84 | struct D2QuestArgStrcUnion3; 85 | union uD2QuestArgStrcUnion; 86 | struct D2QuestArgStrc; 87 | struct D2QuestDataCallbacksStrc; 88 | struct D2NPCMessageStrc; 89 | struct D2NPCMessageTableStrc; 90 | struct D2QuestDataStrc; 91 | struct D2QuestTimerStrc; 92 | struct D2QuestControlStrc; 93 | struct D2UnitNodeStrc; 94 | struct D2InventoryGridStrc; 95 | struct D2InventoryGridInfoStrc; 96 | struct D2InventoryStrc; 97 | struct D2GameNpcStrc; 98 | struct D2GameNpcArrayStrc; 99 | struct D2GameNpcControlStrc; 100 | struct D2ArenaControlStrc; 101 | struct D2GameStrc; 102 | struct D2PacketListStrc; 103 | struct D2ClientHotKeyStrc; 104 | struct D2PlayerBasicsData; 105 | struct D2ClientStrc; 106 | struct D2UnitDataPlayerStrc; 107 | struct D2MonStatsTxtTC; 108 | struct D2MonStatsTxt; 109 | struct D2MonsterDataComponents; 110 | struct D2MonStats2Txt; 111 | struct D2AiParamStrc; 112 | struct D2MonsterAiCmdStrc; 113 | struct D2MonsterDataMinionList; 114 | struct D2AiGeneralStrc; 115 | struct D2MonsterNameStrc; 116 | struct D2MonsterAiStrc; 117 | struct D2UnitDataMonsterStrc; 118 | struct D2ObjectsTxt; 119 | struct D2ShrinesTxt; 120 | struct D2UnitDataObjectStrc; 121 | struct D2CoordsShort; 122 | struct D2UnitDataMissileStrc; 123 | struct D2UnitDataItemStrc; 124 | union D2UnitDataUnion; 125 | struct D2StaticPathStrc; 126 | struct D2DynamicPathCoordsStrc; 127 | union D2DynamicPathCoordsUnion; 128 | struct D2PathPointStrc; 129 | struct D2DynamicPathStrc; 130 | union D2PathUnion; 131 | struct D2AnimSeqStrc; 132 | struct D2AnimDataRecordStrc; 133 | struct D2AnimDataStrc; 134 | struct D2GfxInfoUnkStrc; 135 | struct D2ParticleOffset3DVectorStrc; 136 | struct D2ParticleStrc; 137 | struct D2GfxInfoStrc; 138 | struct D2StatInfoStrc; 139 | struct D2StatListStrc; 140 | struct D2StateFlags; 141 | struct D2StatListExStrc; 142 | struct D2LightMapStrc; 143 | struct D2TimerArgStrc; 144 | struct D2CUnitEventStrc; 145 | struct D2SUnitEventStrc; 146 | union D2UnitEvent; 147 | struct D2HoverTextStrc; 148 | struct D2SkillsTxt; 149 | struct D2SkillStrc; 150 | struct D2SkillListStrc; 151 | struct D2ServerCmdStrc; 152 | struct D2SUnitMsgStrc; 153 | struct D2UnitStrc; 154 | class D2UnitPlayer; //: public D2UnitStrc; 155 | class D2UnitMonster; //: public D2UnitStrc; 156 | class D2UnitObject; //: public D2UnitStrc; 157 | class D2UnitMissile; //: public D2UnitStrc; 158 | class D2UnitItem; //: public D2UnitStrc; 159 | class D2UnitWarp; //: public D2UnitStrc; 160 | struct D2CharSelCompStrc; 161 | struct D2CharSelStrc; 162 | struct D2QServerClientPacketStrc; 163 | struct D2QServerClientStrc; 164 | struct D2QServerClientListStrc; 165 | struct D2QServerStrc; 166 | struct D2CharSelSaveDataStrc; 167 | struct D2UnknownSequence; 168 | struct D2IniConfigStrc; 169 | struct DC6Block; 170 | struct DC6; 171 | struct D2ControlMsg; 172 | struct D2ControlStrc; 173 | struct FORMS_PredefinedFormStrc; 174 | struct D2BoundingBoxStrc; 175 | struct D2ItemUnknownServerSideStrc; 176 | struct D2UnderMouseStrc; 177 | struct D2PlayerClassTxt; 178 | struct D2TxtLinkNodeStrc; 179 | struct D2TxtLinkStrc; 180 | struct D2BodyLocsTxt; 181 | struct D2StorePageTxt; 182 | struct D2ElemTypesTxt; 183 | struct D2HitClassTxt; 184 | struct D2MonModeTxtSmall; 185 | struct D2PlrModeTxt; 186 | struct D2SkillCalcTxt; 187 | struct D2MissCalcTxt; 188 | struct D2SkillsHeaderTxt; 189 | struct D2EventsTxt; 190 | struct D2CompCodeTxt; 191 | struct D2MonAiTxt; 192 | struct D2PropertiesTxt; 193 | struct D2HireDescTxt; 194 | struct D2StatesTxt; 195 | struct D2StatesDataTbls; 196 | struct D2AllStatesDataTbl; 197 | struct D2SoundsTxtSmall; 198 | struct D2HirelingTxt; 199 | struct D2NpcTxt; 200 | struct D2ColorsTxt; 201 | struct D2ParsedTreasureClassSub0x28Strc; 202 | struct D2ParsedTreasureClassStrc; 203 | struct D2ParsedChestTreasureClassStrc; 204 | struct D2ParsedActTreasureClassStrc; 205 | struct D2ParsedDifficultyTreasureClassStrc; 206 | struct D2MonSoundsTxt; 207 | struct D2MonPlaceTxt; 208 | struct D2MonPresetTxtPlace; 209 | struct D2MonPresetTxt; 210 | struct D2SuperUniquesTxt; 211 | struct D2MissilesTxt; 212 | struct D2MonLvlTxt; 213 | struct D2MonSeqTxt; 214 | struct D2MonSeqMonsterTbls; 215 | struct D2SkillDescTxt; 216 | struct D2SkillsPerClassCounter; 217 | struct D2SkillsPerClassStrc; 218 | struct D2PassiveSkillsPerClassStrc; 219 | struct D2OverlayTxt; 220 | struct D2CharItemStrc; 221 | struct D2CharStatsTxt; 222 | struct D2ItemStatCostTxt; 223 | struct D2MonEquipTxt; 224 | struct D2PetTypeTxt; 225 | struct D2ItemTypesTxt; 226 | struct D2SetsTxtPStrc; 227 | struct D2SetsTxtFStrc; 228 | struct D2UniqueSetItemsTxtEntry; 229 | struct D2SetItemsTxtEntry; 230 | struct D2SetItemsTxt; 231 | struct D2SetsTxt; 232 | struct D2UniqueItemsTxt; 233 | struct D2MonPropTxtEntry; 234 | struct D2MonPropTxt; 235 | struct D2MonTypeTxt; 236 | struct D2MonUModTxt; 237 | struct D2LevelsTxt; 238 | struct D2LevelDefsColor; 239 | struct D2LevelDefsTxt; 240 | struct D2ExperienceTxt; 241 | struct D2DifficultyLevelsTxt; 242 | struct D2DataTableTxtStrc; 243 | struct D2CoordStrc; 244 | }; // ghidra namespace -------------------------------------------------------------------------------- /headers/ghidra/readme.md: -------------------------------------------------------------------------------- 1 | # Generated ghidra files 2 | 3 | ## Setup 4 | Use the [ghidra-tooling](https://github.com/jaenster/ghidra-tooling/) and set this up first 5 | 6 | ## What happens 7 | The ghidra-tooling does several things. 8 | 1) Reads `c:\user\username\game.exe.h` (what you config) 9 | - Parse all `Structs`, `Union`, `Typedefs`, `Enums` 10 | - It selects which structs/enums are wanted for Charon (by config). 11 | - It enrich the list by recursively search trough the current set to fetch all dependencies 12 | 2) Reads `framework\ghidra.extensions.cpp` 13 | - Parse all methods for existing D2Structs that are exported out of ghidra and store it in the collection 14 | 3) Reads `headers\ghidra\user.extensions.h` 15 | - Parse all newly created `user defined` classes that are extended from a D2Structs and store it in the collection 16 | - Example; It contains the class D2UnitItem, which extends the actual struct D2UnitStrc 17 | 4) Generates `headers\ghidra\naked.h` 18 | - Put all blank struct signatures in a file 19 | - Insert all blank structs from `user.extensions.h` 20 | 5) Generates `headers\ghidra\enums.h` 21 | - Put all needed enums in this file (those that are required and those that are dependencies) 22 | 6) Generates `headers\ghidra\main.h` 23 | - Includes enums 24 | - Includes naked 25 | - Lists all `Structs`, `Union`, `Typedefs` in a way its dependencies are above what it requires 26 | - Add user defined methods to `Structs` 27 | - Add user defined comments to `Structs` 28 | 29 | ## What to avoid 30 | 1) For now, please dont use templated user defined methods/classes/structs 31 | 2) Im not sure if unions work properly, please dont 32 | 33 | ## How to use 34 | In a feature, like `ExperienceMod` you can simple use `#include "headers/ghidra.h"` as before. 35 | 36 | ## Why a separated headers/ghidra.h? 37 | Because `headers\ghidra\user.extensions.h` need to include `headers\ghidra\main.h`, i cant include it the other way around as well. 38 | 39 | For features that require `ghidra.h`, please use the old (and now wrapper) file `headers/ghidra.h` to both have the user defined methods / classes and actual D2Structs 40 | -------------------------------------------------------------------------------- /headers/ghidra/user.extensions.h: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @Author Jaenster 4 | * @see readme.me 5 | * @Description 6 | 7 | This contains classes that are spliced out of the ghidra files. 8 | 9 | Like the game only has a D2UnitStrc, but i want to have type specific functions 10 | As you cant drop() a monster, while an item doesnt have health 11 | 12 | */ 13 | #pragma once 14 | 15 | #include "./main.h" 16 | 17 | namespace Ghidra { 18 | 19 | class D2UnitPlayer : public D2UnitStrc { 20 | public: 21 | D2UnitDataPlayerStrc *getUnitData() { 22 | if (this->eUnitType != UNIT_PLAYER) return nullptr; 23 | return this->pUnitData.pUnitDataPlayer; 24 | } 25 | 26 | D2DynamicPathStrc* getPath() { 27 | return this->pPath.pDynamicPath; 28 | } 29 | }; 30 | 31 | class D2UnitMonster : public D2UnitStrc { 32 | public: 33 | D2UnitDataMonsterStrc *getUnitData() { 34 | if (this->eUnitType != UNIT_MONSTER) return nullptr; 35 | return this->pUnitData.pUnitDataMonster; 36 | } 37 | 38 | D2DynamicPathStrc* getPath() { 39 | return this->pPath.pDynamicPath; 40 | } 41 | }; 42 | 43 | class D2UnitObject : public D2UnitStrc { 44 | public: 45 | D2UnitDataObjectStrc *getUnitData() { 46 | if (this->eUnitType != UNIT_OBJECT) return nullptr; 47 | return this->pUnitData.pUnitDataObject; 48 | } 49 | }; 50 | 51 | class D2UnitMissile : public D2UnitStrc { 52 | public: 53 | D2UnitDataMissileStrc *getUnitData() { 54 | if (this->eUnitType != UNIT_MISSILE) return nullptr; 55 | return this->pUnitData.pUnitDataMissile; 56 | } 57 | }; 58 | 59 | class D2UnitItem : public D2UnitStrc { 60 | public: 61 | D2UnitDataItemStrc *getUnitData() { 62 | if (this->eUnitType != UNIT_ITEM) return nullptr; 63 | return this->pUnitData.pUnitDataItem; 64 | } 65 | }; 66 | 67 | class D2UnitWarp : public D2UnitStrc { 68 | public: 69 | D2UnitDataObjectStrc *getUnitData() { 70 | return nullptr; 71 | } 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /headers/hook.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Utilities for hooking into the goodies. 3 | */ 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | extern std::unordered_map OriginalBytes; 12 | 13 | class BYTES { 14 | BYTE value; 15 | size_t length; 16 | public: 17 | BYTES(BYTE value, size_t length); 18 | friend class MemoryPatch; 19 | }; 20 | 21 | class NOP_TO { 22 | DWORD addr; 23 | public: 24 | NOP_TO(LPVOID addr); 25 | NOP_TO(DWORD addr); 26 | friend class MemoryPatch; 27 | }; 28 | 29 | typedef std::vector BYTESEQ; 30 | 31 | class SKIP { 32 | size_t length; 33 | public: 34 | SKIP(size_t length); 35 | friend class MemoryPatch; 36 | }; 37 | 38 | class REWIND { 39 | size_t length; 40 | public: 41 | REWIND(size_t length); 42 | friend class MemoryPatch; 43 | }; 44 | 45 | class CALL { 46 | LPVOID pFunc; 47 | public: 48 | CALL(LPVOID pFunc); 49 | friend class MemoryPatch; 50 | }; 51 | 52 | class JUMP { 53 | LPVOID pFunc; 54 | public: 55 | JUMP(LPVOID pFunc); 56 | JUMP(DWORD pFunc); 57 | friend class MemoryPatch; 58 | }; 59 | 60 | class JUMP_EQUAL { 61 | LPVOID pFunc; 62 | public: 63 | JUMP_EQUAL(LPVOID pFunc); 64 | JUMP_EQUAL(DWORD pFunc); 65 | friend class MemoryPatch; 66 | }; 67 | 68 | class JUMP_NOT_EQUAL { 69 | LPVOID pFunc; 70 | public: 71 | JUMP_NOT_EQUAL(LPVOID pFunc); 72 | JUMP_NOT_EQUAL(DWORD pFunc); 73 | friend class MemoryPatch; 74 | }; 75 | 76 | typedef JUMP_EQUAL JUMP_ZERO; 77 | typedef JUMP_NOT_EQUAL JUMP_NOT_ZERO; 78 | 79 | class REVERT { 80 | size_t length; 81 | public: 82 | REVERT(size_t length); 83 | friend class MemoryPatch; 84 | }; 85 | 86 | DWORD RevertBytes(DWORD pAddr, DWORD dwLen); 87 | 88 | class MemoryPatch { 89 | DWORD pAddr = NULL; 90 | 91 | template MemoryPatch& d(const T data); 92 | 93 | public: 94 | MemoryPatch(DWORD dwAddr); 95 | MemoryPatch& operator << (const bool data); 96 | MemoryPatch& operator << (const char data); 97 | MemoryPatch& operator << (const wchar_t data); 98 | MemoryPatch& operator << (const unsigned char data); 99 | MemoryPatch& operator << (const short data); 100 | MemoryPatch& operator << (const unsigned short data); 101 | MemoryPatch& operator << (const int data); 102 | MemoryPatch& operator << (const unsigned int data); 103 | MemoryPatch& operator << (const long data); 104 | MemoryPatch& operator << (const unsigned long data); 105 | MemoryPatch& operator << (const long long data); 106 | MemoryPatch& operator << (const unsigned long long data); 107 | MemoryPatch& operator << (const float data); 108 | MemoryPatch& operator << (const double data); 109 | MemoryPatch& operator << (const BYTES bytes); 110 | MemoryPatch& operator << (const NOP_TO address); 111 | MemoryPatch& operator << (const SKIP offset); 112 | MemoryPatch& operator << (const REWIND offset); 113 | MemoryPatch& operator << (const CALL call); 114 | MemoryPatch& operator << (const JUMP jump); 115 | MemoryPatch& operator << (const JUMP_EQUAL jump); 116 | MemoryPatch& operator << (const JUMP_NOT_EQUAL jump); 117 | MemoryPatch& operator << (const REVERT revert); 118 | MemoryPatch& operator << (BYTESEQ bytes); 119 | }; 120 | 121 | template BYTESEQ DATA(const T& data) { 122 | BYTESEQ bytes; 123 | 124 | for (size_t c = 0; c < sizeof(T); c++) { 125 | bytes.push_back(((BYTE*)&data)[c]); 126 | } 127 | 128 | return bytes; 129 | } 130 | 131 | namespace ASM { 132 | const BYTE NOP = 0x90; 133 | const BYTE CALL = 0xE8; 134 | const BYTE JUMP = 0xE9; 135 | const BYTE RET = 0xC3; 136 | const BYTE PUSHAD = 0x60; 137 | const BYTE POPAD = 0x61; 138 | const BYTE PUSH_EDI = 0x57; 139 | const BYTESEQ TEST_AL{ 0x84, 0xC0 }; 140 | const BYTESEQ TEST_EAX{ 0x85, 0xC0 }; 141 | const BYTESEQ MOV_ECX_EDI{ 0x89, 0xF9 }; 142 | const BYTESEQ MOV_ECX_EAX{ 0x89, 0xC1 }; 143 | } 144 | -------------------------------------------------------------------------------- /headers/remote.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | #include "D2Structs.h" 5 | 6 | typedef unsigned long ASMPTR; 7 | 8 | #ifdef DEFINE_REMOTE_REFERENCES 9 | 10 | #define GLOBALFUNC(r, n, a, o) namespace FuncDefs { typedef r n a; } FuncDefs::n *n = (FuncDefs::n*)o; 11 | #define GLOBALREF(t, n, o) namespace VarDefs { typedef t n; } VarDefs::n &n = *(VarDefs::n *)o; 12 | #define GLOBALPTR(t, n, o) namespace VarDefs { typedef t n; } VarDefs::n *n = (VarDefs::n *)o; 13 | 14 | #else 15 | 16 | #define GLOBALFUNC(r, n, a, o) namespace FuncDefs { typedef r n a; } extern FuncDefs::n *n; 17 | #define GLOBALREF(t, n, o) namespace VarDefs { typedef t n; } extern VarDefs::n &n; 18 | #define GLOBALPTR(t, n, o) namespace VarDefs { typedef t n; } extern VarDefs::n *n; 19 | 20 | #endif 21 | 22 | #pragma warning( disable : 4229 ) 23 | 24 | namespace D2 { 25 | 26 | // Basically, if the address represents an array (not a pointer to an array) use this 27 | // global pointer 'GLOBALPTR' macro, otherwise use global reference 'GLOBALREF' instead 28 | GLOBALREF(Types::UnitHashTableCollection, ClientSideUnits, 0x7A5270); 29 | GLOBALREF(Types::UnitHashTableCollection, ServerSideUnits, 0x7A5E70); 30 | GLOBALPTR(Types::GameStructInfo, GameInfo, 0x7A0438); 31 | GLOBALREF(Types::CurrentPlayerUnit*, PlayerUnit, 0x7A6A70); 32 | GLOBALREF(DWORD, NoPickUp, 0x7A6A90); 33 | GLOBALREF(HINSTANCE, hInst, 0x7C8CA8); 34 | GLOBALREF(HWND, hWnd, 0x7C8CBC); 35 | GLOBALREF(int, CurrentGameType, 0x7A0610); 36 | 37 | // For referencing D2's functions use this specialized macro instead 38 | GLOBALFUNC(DWORD __fastcall, SetFont, (DWORD dwFileNo), 0x502EF0); 39 | GLOBALFUNC(void __fastcall, DrawGameText, (const wchar_t* wStr, int xPos, int yPos, DWORD dwColor, BOOL bMultiLineCenterFlag), 0x502320); 40 | GLOBALFUNC(void __stdcall, DrawLine, (int nXStart, int nYStart, int nXEnd, int nYEnd, DWORD dwColor, DWORD nAlpha), 0x4F6380); 41 | GLOBALFUNC(void __stdcall, DrawDiamond, (RECT* pRect, BYTE nPaletteIndex), 0x4F6280); 42 | GLOBALFUNC(void __stdcall, DrawRect, (RECT* pRect, BYTE nPaletteIndex), 0x4F62A0); 43 | GLOBALFUNC(void __stdcall, DrawSolidRectAlpha, (int nXStart, int nYStart, int nXEnd, int nYEnd, DWORD dwColor, DWORD nAlpha), 0x4F6340); 44 | GLOBALFUNC(void __stdcall, DrawImage, (void* pDC6Context, int nPositionX, int nPositionY, int dwGamma, DWORD eMode, void* pPalette), 0x4F6480); 45 | GLOBALFUNC(DWORD __fastcall, GetTextSize, (const wchar_t* wStr, DWORD* dwWidth, DWORD* dwFileNo), 0x502520); 46 | GLOBALFUNC(DWORD __stdcall, GetUnitStat, (Types::UnitAny* pUnit, DWORD dwStat, DWORD dwStat2), 0x625480); 47 | GLOBALFUNC(int __stdcall, GetUnitState, (Types::UnitAny* pUnit, DWORD dwStateNo), 0x639DF0); 48 | GLOBALFUNC(wchar_t* __fastcall, GetUnitName, (Types::UnitAny* unit), 0x464a60); 49 | GLOBALFUNC(void __stdcall, GetScreenModeSize, (int nResolutionMode, int* pResRightMax, int* pResBottomMax), 0x4F5570); 50 | GLOBALFUNC(DWORD __stdcall, GetScreenMode, (), 0x4F5160); 51 | GLOBALFUNC(DWORD __fastcall, GetUiFlag, (DWORD dwVarNo), 0x4538D0); 52 | GLOBALFUNC(void __stdcall, AddRoomData, (Types::Act* ptAct, int LevelId, int Xpos, int Ypos, D2::Types::Room1* pRoom), 0x61A070); 53 | GLOBALFUNC(void __stdcall, RemoveRoomData, (Types::Act* ptAct, int LevelId, int Xpos, int Ypos, D2::Types::Room1* pRoom), 0x61A0C0); 54 | GLOBALFUNC(void __stdcall, InitLevel, (Types::Level* pLevel), 0x6424A0); 55 | GLOBALFUNC(Types::LevelTxt* __stdcall, GetLevelText, (DWORD levelno), 0x61DB70); 56 | GLOBALFUNC(Types::ObjectTxt* __stdcall, GetObjectText, (DWORD objno), 0x640E90); 57 | GLOBALFUNC(Types::ItemTxt* __stdcall, GetItemText, (DWORD itemno), 0x6335F0); 58 | GLOBALFUNC(Types::UnitAny* __fastcall, CreateUnit, (UnitType type, DWORD classId, DWORD x, DWORD y, void* ptGame, Types::Room1* ptRoom, DWORD uk1, DWORD uk2, DWORD uk3), 0x555230); 59 | GLOBALFUNC(void __fastcall, OkDialog, (const WCHAR* title, const WCHAR* primary, const WCHAR* secondary, void (*callback)()), 0x4331c0); 60 | GLOBALFUNC(void, MainMenuForm, (), 0x4336c0); // No params, so calling convention doesn't matter 61 | GLOBALFUNC(void __fastcall, CreateFormElementFromListExEx, (int idx), 0x42f430); 62 | 63 | GLOBALFUNC(DWORD __fastcall, LinkPortal, (void* ptGame, int unused, D2::Types::UnitAny* ptObject, DWORD levelEndID, DWORD levelStartID), 0x56CF40); 64 | GLOBALFUNC(DWORD __fastcall, SpawnPortal, (D2::Types::IncompleteGameData* pGame, D2::Types::UnitAny* pUnit, D2::Types::Room1* pDrlgRoom, int nX, int nY, DWORD eD2LevelId, D2::Types::UnitAny** param_7, int nClassId, DWORD param_9), 0x56D130); 65 | GLOBALFUNC(void __fastcall, OpenPortal, (D2::Types::IncompleteGameData* pGame, D2::Types::UnitAny* pUnit, DWORD LevelId), 0x5A9930); 66 | GLOBALFUNC(void __fastcall, FindSpawnablePosition, (D2::Types::Room1* pDrlgRoom, POINT* pos, DWORD param_1_00, DWORD param_2_00, D2::Types::Room1** pRoomsNear, DWORD param_6, int param_7), 0x545340); 67 | GLOBALFUNC(void __stdcall, UnitLocation, (D2::Types::UnitAny* pUnit, POINT* pPoint), 0x620870); 68 | GLOBALFUNC(DWORD __stdcall, GetAct, (int levelId), 0x6427f0); 69 | GLOBALFUNC(void, NET_D2GS_CLIENT_IncomingReturn, (char* pBytes), 0x45c900); 70 | } 71 | 72 | #pragma warning( default : 4229 ) 73 | 74 | #define REMOTEFUNC(r, n, a, o) namespace FuncDefs { typedef r n a; } FuncDefs::n *n = (FuncDefs::n*)o; 75 | #define REMOTEREF(t, n, o) namespace VarDefs { typedef t n; } VarDefs::n &n = *(VarDefs::n *)o; 76 | #define REMOTEPTR(t, n, o) namespace VarDefs { typedef t n; } VarDefs::n *n = (VarDefs::n *)o; 77 | -------------------------------------------------------------------------------- /headers/utilities.h: -------------------------------------------------------------------------------- 1 | #define _USE_MATH_DEFINES 2 | 3 | #pragma once 4 | 5 | #include "D2Structs.h" 6 | #include "ghidra.h" 7 | #include 8 | #include 9 | 10 | extern DPOINT xvector, yvector; 11 | void DrawRectangle(POINT a, POINT b, DWORD dwColor); 12 | void DrawLine(POINT a, POINT b, DWORD dwColor); 13 | void DrawDot(POINT pos, DWORD dwColor = 30); 14 | int randIntInRange(int low, int high); 15 | double randDoubleInRange(double low, double high); 16 | 17 | void __stdcall serverSendPacket(Ghidra::D2ClientStrc* pClient, char* pBytes, size_t nSize); 18 | void clientSendPacket(char* pBytes, size_t nSize); 19 | const char CUSTOM_PACKET_ID = 0x2B; 20 | 21 | struct CustomPacketHeader { 22 | BYTE id = CUSTOM_PACKET_ID; 23 | short size = sizeof(CustomPacketHeader); 24 | }; 25 | 26 | void ServerSendCustomData(Ghidra::D2ClientStrc* pClient, char* pBytes, size_t nSize); 27 | void ClientSendCustomData(char* pBytes, size_t nSize); 28 | 29 | template void ServerSendCustomData(Ghidra::D2ClientStrc* pClient, T data) { 30 | ServerSendCustomData(pClient, (char*)&data, sizeof(T)); 31 | } 32 | 33 | template void ClientSendCustomData(T data) { 34 | ClientSendCustomData((char*)&data, sizeof(T)); 35 | } 36 | 37 | const char FLYING_TEXT_PACKET_ID = 0x1; 38 | 39 | struct FlyingTextPacket { 40 | char subPacketId = FLYING_TEXT_PACKET_ID; 41 | DPOINT pos{ 0, 0 }; 42 | char color = 0; 43 | int value = 0; 44 | }; 45 | 46 | struct FlyingText { 47 | ULONGLONG counter; 48 | char color; 49 | int value; 50 | DWORD font = 4; 51 | DPOINT pos{ 0, 0 }; 52 | DPOINT delta{ 0, 0 }; 53 | 54 | FlyingText(DPOINT pos, int value, char color); 55 | FlyingText(FlyingTextPacket *packet); 56 | }; 57 | 58 | extern std::vector FlyingTexts; 59 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Charon 2 | 3 | This is an experiment and utility for Diablo 2. It is intended for single player and TCP/IP game use, so connections to Battle.net are disabled. It's main features include: 4 | 5 | Always in Direct 3D Mode with Alt+Enter fullscreen toggle. 6 | Ladder Runewords, Cube Recipes, and Unique Items in Single Player and TCP/IP 7 | Uber Tristram and Diablo Clone in Single Player and TCP/IP 8 | Gently Improved Drop Rates with Respect to Single Player 9 | Gently Improved Rune Drop Rates with Respect to Single Player 10 | Map Reveal (more information now) 11 | Display Monsters, Missiles, and Items (superior and above) on Automap 12 | Updated Item Tooltips and Item Level Display 13 | /players X uncap (16-20 is reasonable, but very difficult) 14 | Experience Bonuses and Scaling 15 | Repeatable Socket, Respec, and Imbue Quests 16 | Repeatable Cow Portal (even if you killed the Cow King) 17 | Disabled Weather and Screen Shaking 18 | Regenerate Single Player Maps (always a new map!) 19 | All Items Drop Pre-Identified 20 | Single Player FPS Uncap (40 FPS is a good spot) 21 | Multiple Simultaneous Windows 22 | Fixed Unique Monster Color Bug 23 | Loading additional mpq via command line parameter: -mpq "filename.mpq" 24 | 25 | Almost all features are optional now! Press F11 while Diablo 2 is running to enable/disable these options! 26 | 27 | A note about 'Always in Direct 3D Mode': 28 | This feature causes the game to ignore command line flags that control the video, such as `-w`. The game now starts windowed every time, and you can toggle full screen mode on and off by holding Alt and pressing Enter (Alt+Enter). We're currently trying to understand how the game uses the rendering API, and we'll be looking to update the renderer to newer APIs, since the game currently uses DirectDraw v7 and Direct3D v3. 29 | 30 | # Instructions 31 | 32 | - Download Charon.zip from the releases tab. 33 | - Unzip Charon.zip to your Diablo 2 directory (Game.exe should be here). 34 | - Create a shortcut to Game.exe 35 | - Edit the shortcut and add this at the very end of the 'target' line: -loaddll Charon.dll 36 | 37 | Now you can run the game using the new shortcut and it will load Charon automatically! 38 | 39 | Note: Do not try to run Charon with any other Diablo 2 modifications. It will most likely crash! 40 | 41 | # Compiling 42 | 43 | To use Charon you'll need to compile it with Visual Studio 2019. Once you've compiled Charon you must load Charon.dll into the game using [DLL Loader](https://github.com/Nishimura-Katsuo/DLLLoader). Loading it with traditional injection techniques may work, but will most likely crash or not function properly, since this is written to operate completely on Diablo 2's main thread for stability. 44 | 45 | A lot of hard work is being put in by us at [Blizzhackers](https://github.com/blizzhackers) to bring you this tool with love. 46 | 47 | >I see him there at the oars of his little boat in the lake, the ferryman of the dead, Charon, with his hand upon the oar and he calls me now. ~Alcestis (from Alcestis by Euripides) 48 | --------------------------------------------------------------------------------