├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bin ├── Debug │ ├── .gitignore │ └── MHUD2.log.conf └── Release │ ├── .gitignore │ └── MHUD2.log.conf ├── doc └── isaac-mhud2-example-141.jpg ├── res ├── DllResources.h ├── DllResources.rc ├── MissingHUD2.qrc ├── WinResource.rc ├── WinResources.h ├── files │ ├── MHUD2Font.png │ ├── MHUD2FontCharmap.texmap │ ├── MHUD2StatIcons.texmap │ ├── MUD2StatIcons.png │ ├── MissingHUD2Banner.png │ ├── MissingHUD2Logo.png │ ├── MissingHUD2_Ico.ico │ ├── psds │ │ ├── MHUD2Font.psd │ │ ├── MUD2StatIcons.psd │ │ ├── Missing HUD2 Banner.psd │ │ └── Missing HUD2 Logo.psd │ └── steam_logo.png └── shaders │ ├── hud_icon_frag.glsl │ └── hud_icon_vert.glsl └── src ├── BoIInjector.cpp ├── BoIInjector.h ├── BoIProcess.cpp ├── BoIProcess.h ├── LoaderGUI.cpp ├── LoaderGUI.h ├── LoaderGUI.ui ├── MHUD_MsgQueue.cpp ├── MHUD_MsgQueue.h ├── MHUD_Options.cpp ├── MHUD_Options.h ├── MissingHUD2.cpp ├── dll ├── AfterbirthMemReader.cpp ├── AfterbirthMemReader.h ├── DLLPreferences.cpp ├── DLLPreferences.h ├── DllMain.cpp ├── GDISwapBuffers.cpp ├── GDISwapBuffers.h ├── GLStructs.cpp ├── GLStructs.h ├── HUDOverlay.cpp ├── HUDOverlay.h ├── HUDStat.cpp ├── HUDStat.h ├── IATHook.cpp ├── IATHook.h ├── IsaacMemSignatures.h ├── MemReader.cpp ├── MemReader.h ├── RebirthMemReader.cpp ├── RebirthMemReader.h ├── ResourceLoader.cpp ├── ResourceLoader.h ├── ShaderProgram.cpp ├── ShaderProgram.h ├── SpriteSheet.cpp ├── SpriteSheet.h ├── TextRenderer.cpp └── TextRenderer.h ├── mhud2_version.h └── proto └── mhud2.proto /.gitignore: -------------------------------------------------------------------------------- 1 | ## CLion 2 | .idea/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.3) 2 | project(MissingHUD2) 3 | 4 | # Set mingw32 base directory 5 | set(MINGW32_BASE_DIR D:/Programming/MSYS2/mingw32) 6 | 7 | # Set output directories 8 | if (${CMAKE_BUILD_TYPE} MATCHES Debug) 9 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin/Debug") 10 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin/Debug") 11 | else() 12 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin/Release") 13 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin/Release") 14 | endif() 15 | 16 | # Global C++ flags and defines 17 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 18 | add_definitions(-DWINVER=0x0601 -D_WIN32_WINNT=0x0601) # Target Windows 7 19 | add_definitions(-DBOOST_DATE_TIME_NO_LIB -DBOOST_INTERPROCESS_BOOTSTAMP_IS_LASTBOOTUPTIME) # Boost options 20 | 21 | # Strip targets if Release build 22 | if (${CMAKE_BUILD_TYPE} MATCHES Release) 23 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s") 24 | endif() 25 | 26 | # Generate boost::interprocess protobuf's 27 | find_package(Protobuf REQUIRED) 28 | set(PROTO_SRC_FILES 29 | src/proto/mhud2.proto 30 | ) 31 | PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS ${PROTO_SRC_FILES}) 32 | 33 | # Build the Hook DLL 34 | set(CMAKE_SHARED_MODULE_PREFIX "") 35 | set(DLL_SOURCE_FILES 36 | src/dll/DllMain.cpp 37 | src/MHUD_MsgQueue.h src/MHUD_MsgQueue.cpp 38 | src/dll/IATHook.h src/dll/IATHook.cpp 39 | src/dll/GLStructs.h src/dll/GLStructs.cpp 40 | src/dll/GDISwapBuffers.h src/dll/GDISwapBuffers.cpp 41 | 42 | src/dll/IsaacMemSignatures.h 43 | src/dll/MemReader.h src/dll/MemReader.cpp 44 | src/dll/AfterbirthMemReader.h src/dll/AfterbirthMemReader.cpp 45 | src/dll/RebirthMemReader.h src/dll/RebirthMemReader.cpp 46 | src/dll/HUDOverlay.h src/dll/HUDOverlay.cpp 47 | src/dll/HUDStat.h src/dll/HUDStat.cpp 48 | 49 | src/dll/SpriteSheet.h src/dll/SpriteSheet.cpp 50 | src/dll/TextRenderer.h src/dll/TextRenderer.cpp 51 | src/dll/ShaderProgram.h src/dll/ShaderProgram.cpp 52 | src/dll/ResourceLoader.h src/dll/ResourceLoader.cpp 53 | src/dll/DLLPreferences.h src/dll/DLLPreferences.cpp 54 | res/DllResources.h res/DllResources.rc 55 | ) 56 | add_library(MissingHUD2Hook MODULE ${DLL_SOURCE_FILES} ${PROTO_SRCS} ${PROTO_HDRS}) 57 | 58 | # We link all dep's statically so we don't have to inject them along with the DLL 59 | set_target_properties(MissingHUD2Hook PROPERTIES LINK_FLAGS "-static") 60 | target_compile_definitions(MissingHUD2Hook PRIVATE "-DGLEW_STATIC") 61 | target_link_libraries(MissingHUD2Hook 62 | glew32 63 | soil2 64 | OpenGL32 65 | shlwapi 66 | protobuf-lite 67 | ) 68 | 69 | # Qt5 settings 70 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 71 | set(CMAKE_AUTOMOC ON) 72 | set(CMAKE_AUTOUIC ON) 73 | set(CMAKE_AUTORCC ON) 74 | 75 | # Set Qt5 to static / dynamic based on buildtype 76 | unset(CMAKE_PREFIX_PATH CACHE) 77 | if (${CMAKE_BUILD_TYPE} MATCHES Release) 78 | set(CMAKE_PREFIX_PATH "${MINGW32_BASE_DIR}/qt5-static/lib/cmake ${CMAKE_PREFIX_PATH}") 79 | endif() 80 | 81 | # Find Qt libraries 82 | unset(Qt5Core_DIR CACHE) 83 | unset(Qt5Gui_DIR CACHE) 84 | unset(Qt5Widgets_DIR CACHE) 85 | unset(Qt5Network_DIR CACHE) 86 | unset(Qt5WinExtras_DIR CACHE) 87 | find_package(Qt5Core REQUIRED) 88 | find_package(Qt5Widgets REQUIRED) 89 | find_package(Qt5Network REQUIRED) 90 | find_package(Qt5WinExtras REQUIRED) 91 | 92 | # Build the Loader application 93 | set(SOURCE_FILES 94 | src/MissingHUD2.cpp 95 | 96 | src/LoaderGUI.h src/LoaderGUI.cpp src/LoaderGUI.ui 97 | src/BoIInjector.h src/BoIInjector.cpp 98 | src/BoIProcess.h src/BoIProcess.cpp 99 | src/MHUD_MsgQueue.h src/MHUD_MsgQueue.cpp 100 | src/MHUD_Options.h src/MHUD_Options.cpp 101 | src/mhud2_version.h 102 | 103 | res/MissingHUD2.qrc 104 | res/WinResources.h res/WinResource.rc 105 | ) 106 | add_executable(MissingHUD2 WIN32 ${SOURCE_FILES} ${PROTO_SRCS} ${PROTO_HDRS}) 107 | target_compile_definitions(MissingHUD2 PRIVATE "-DELPP_THREAD_SAFE -DELPP_NO_DEFAULT_LOG_FILE") 108 | target_link_libraries(MissingHUD2 109 | Qt5::Core Qt5::Widgets Qt5::Network Qt5::WinExtras 110 | shlwapi 111 | protobuf-lite 112 | ) 113 | 114 | # Set Qt5 to static as well as GCC C++ libs if Release buildtype 115 | if (${CMAKE_BUILD_TYPE} MATCHES Release) 116 | target_compile_definitions(MissingHUD2 PRIVATE "-DQT_STATIC") 117 | set_target_properties(MissingHUD2 PROPERTIES LINK_FLAGS "-static") 118 | endif() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | Missing HUD 2 is an OpenGL powered informational overlay for the Binding of Isaac: Rebirth + Afterbirth. 3 | 4 | The developers of the Binding of Isaac (Edmund McMillen, Nicalis) decided that one of their design decisions for the game would be to hide raw player statistics from the player as to not to overwhelm them. This project gives the player the choice to see their raw statistics if they choose to. 5 | 6 | It is a transparent mod that **DOES NOT** disable achievements nor alter your Isaac game files in any way. **Note:** It can be used in Afterbirth daily runs with no repercussions. 7 | 8 | It can be enabled and disabled at any point, during any run, with no lasting consequences. One can run other mods side-by-side with Missing HUD 2 with no issues. 9 | 10 | Unlike other statistic based mods, it uses your live character statistics during a run. This translates to Missing HUD 2 remaining 100% accurate even after picking up items like [Experimental Treatment](http://bindingofisaacrebirth.gamepedia.com/Experimental_Treatment) and [Libra](http://bindingofisaacrebirth.gamepedia.com/Libra). 11 | 12 | ![Image of MissingHUD2](https://raw.githubusercontent.com/networkMe/missinghud2/master/doc/isaac-mhud2-example-141.jpg) 13 | 14 | ## Using 15 | Missing HUD 2 aims to be nearly transparent to the user (and to Isaac itself). 16 | 17 | You simply run the main executable (which acts as the DLL injector) and the HUD will be drawn onto an active Isaac process. 18 | Note: The HUD only appears if you are in an active run. You must leave Missing HUD 2 open while you play the game. 19 | 20 | If you wish to no longer see the HUD, just close the main executable and the HUD will disappear (the DLL will be unloaded). 21 | 22 | The latest binary release can be found here: 23 | https://github.com/networkMe/missinghud2/releases/latest 24 | 25 | **Note:** The **latest version of Missing HUD 2** is designed to be used on the **latest Steam version** of the game. 26 | If you are crashing or seeing weird stat values that are clearly incorrect, you most likely are not running the latest version of the game and/or Missing HUD 2. Pirated copies of the game are not officially supported by Missing HUD 2. 27 | 28 | ## Current features 29 | * Works in fullscreen and windowed mode (as it's a direct OpenGL implementation) 30 | * Shows how your raw statistics change as you pick up items and use pills, real-time 31 | * Allows you to choose at what precision you see the raw statistics (default is 2 decimal place) 32 | * Statistics HUD on the left of the Isaac viewport shows: 33 | * Total tears fired in run (optional) 34 | * Speed 35 | * Range 36 | * Tear firerate (Tear delay, lower is faster) 37 | * Shot speed 38 | * Shot height (optional) 39 | * Damage 40 | * Luck 41 | * Deal with the Devil % chance 42 | * Deal with the Angel % chance 43 | 44 | ## Building 45 | Missing HUD 2 has the below dependencies: 46 | 47 | 1. Main executable (DLL injector) 48 | * [Qt5](http://www.qt.io/) (static version directory set manually in CMake) 49 | * [EasyLogging++](https://github.com/easylogging/easyloggingpp) 50 | * [Boost.Interprocess](http://www.boost.org/doc/libs/1_59_0/doc/html/interprocess.html) 51 | * [Google's Protobuf](https://github.com/google/protobuf) 52 | 2. Injected DLL 53 | * [GLEW](https://github.com/nigels-com/glew) 54 | * [SOIL2](https://bitbucket.org/SpartanJ/soil2) 55 | * [Boost.Interprocess](http://www.boost.org/doc/libs/1_59_0/doc/html/interprocess.html) 56 | * [Google's Protobuf](https://github.com/google/protobuf) 57 | 58 | It uses the CMake build system to compile. 59 | The easiest Windows MinGW environment to compile it on is the MSYS2 enviroment. -------------------------------------------------------------------------------- /bin/Debug/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !MHUD2.log.conf -------------------------------------------------------------------------------- /bin/Debug/MHUD2.log.conf: -------------------------------------------------------------------------------- 1 | * GLOBAL: 2 | FORMAT = "[%datetime{%Y-%M-%d %h:%m:%s %F}] [TID: %thread] [%level] %msg" 3 | ENABLED = true 4 | TO_STANDARD_OUTPUT = true 5 | MILLISECONDS_WIDTH = 3 6 | PERFORMANCE_TRACKING = true 7 | MAX_LOG_FILE_SIZE = 1048576 ## 1MB 8 | LOG_FLUSH_THRESHOLD = 10 ## Flush after every 10 logs -------------------------------------------------------------------------------- /bin/Release/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !MHUD2.log.conf -------------------------------------------------------------------------------- /bin/Release/MHUD2.log.conf: -------------------------------------------------------------------------------- 1 | * GLOBAL: 2 | FORMAT = "[%datetime{%Y-%M-%d %h:%m:%s %F}] [TID: %thread] [%level] %msg" 3 | ENABLED = true 4 | TO_STANDARD_OUTPUT = true 5 | MILLISECONDS_WIDTH = 3 6 | PERFORMANCE_TRACKING = true 7 | MAX_LOG_FILE_SIZE = 1048576 ## 1MB 8 | LOG_FLUSH_THRESHOLD = 10 ## Flush after every 10 logs -------------------------------------------------------------------------------- /doc/isaac-mhud2-example-141.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networkMe/missinghud2/759bd359f99338e7b35966b218d81df927eceb4c/doc/isaac-mhud2-example-141.jpg -------------------------------------------------------------------------------- /res/DllResources.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_DLLRESOURCES_H 16 | #define MISSINGHUD2_DLLRESOURCES_H 17 | 18 | #include 19 | 20 | #define MHUD2_STAT_ICONS 1000 21 | 22 | #define MHUD2_ISAAC_FONT_PNG 2000 23 | 24 | #define OPENGL_HUD_SPRITE_VERTEX_SHADER 3000 25 | #define OPENGL_HUD_SPRITE_FRAG_SHADER 3001 26 | 27 | #define MHUD2_STAT_ICONS_TEXMAP 4000 28 | #define MHUD2_ISAAC_FONT_CHARMAP 4001 29 | 30 | #endif //MISSINGHUD2_DLLRESOURCES_H 31 | -------------------------------------------------------------------------------- /res/DllResources.rc: -------------------------------------------------------------------------------- 1 | #include "DllResources.h" 2 | 3 | MHUD2_STAT_ICONS RCDATA "files/MUD2StatIcons.png" 4 | MHUD2_STAT_ICONS_TEXMAP RCDATA "files/MHUD2StatIcons.texmap" 5 | 6 | MHUD2_ISAAC_FONT_PNG RCDATA "files/MHUD2Font.png" 7 | MHUD2_ISAAC_FONT_CHARMAP RCDATA "files/MHUD2FontCharmap.texmap" 8 | 9 | OPENGL_HUD_SPRITE_VERTEX_SHADER RCDATA "shaders/hud_icon_vert.glsl" 10 | OPENGL_HUD_SPRITE_FRAG_SHADER RCDATA "shaders/hud_icon_frag.glsl" 11 | 12 | #define PRODUCT_VER 1,4,4,0 13 | #define PRODUCT_VER_STR "1.4.4" 14 | 15 | #ifdef NDEBUG 16 | #define VER_DEBUG 0 17 | #else 18 | #define VER_DEBUG VS_FF_DEBUG 19 | #endif 20 | 21 | VS_VERSION_INFO VERSIONINFO 22 | FILEVERSION PRODUCT_VER 23 | PRODUCTVERSION PRODUCT_VER 24 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 25 | FILEFLAGS VER_DEBUG 26 | FILEOS VOS_NT_WINDOWS32 27 | FILETYPE VFT_APP 28 | FILESUBTYPE VFT2_UNKNOWN 29 | BEGIN 30 | BLOCK "StringFileInfo" 31 | BEGIN 32 | BLOCK "040904B0" 33 | BEGIN 34 | VALUE "CompanyName", "Trevor Meehl" 35 | VALUE "FileDescription", "Binding of Isaac: Rebirth Statistics HUD Hook DLL" 36 | VALUE "FileVersion", PRODUCT_VER_STR 37 | VALUE "InternalName", "MissingHUD2Hook" 38 | VALUE "OriginalFilename", "MissingHUD2Hook.dll" 39 | VALUE "ProductName", "Missing HUD 2 Hook DLL" 40 | VALUE "ProductVersion", PRODUCT_VER_STR 41 | VALUE "LegalCopyright", "Apache 2.0 License" 42 | END 43 | END 44 | 45 | BLOCK "VarFileInfo" 46 | BEGIN 47 | VALUE "Translation", 0x409, 1200 48 | END 49 | END -------------------------------------------------------------------------------- /res/MissingHUD2.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | files/MissingHUD2Logo.png 4 | 5 | 6 | files/steam_logo.png 7 | files/MissingHUD2Banner.png 8 | 9 | 10 | -------------------------------------------------------------------------------- /res/WinResource.rc: -------------------------------------------------------------------------------- 1 | #include "WinResources.h" 2 | 3 | APP_ICON ICON "files/MissingHUD2_Ico.ico" 4 | 5 | #define PRODUCT_VER 1,4,4,0 6 | #define PRODUCT_VER_STR "1.4.4" 7 | 8 | #ifdef NDEBUG 9 | #define VER_DEBUG 0 10 | #else 11 | #define VER_DEBUG VS_FF_DEBUG 12 | #endif 13 | 14 | VS_VERSION_INFO VERSIONINFO 15 | FILEVERSION PRODUCT_VER 16 | PRODUCTVERSION PRODUCT_VER 17 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 18 | FILEFLAGS VER_DEBUG 19 | FILEOS VOS_NT_WINDOWS32 20 | FILETYPE VFT_APP 21 | FILESUBTYPE VFT2_UNKNOWN 22 | BEGIN 23 | BLOCK "StringFileInfo" 24 | BEGIN 25 | BLOCK "040904B0" 26 | BEGIN 27 | VALUE "CompanyName", "Trevor Meehl" 28 | VALUE "FileDescription", "Binding of Isaac: Rebirth Statistics HUD" 29 | VALUE "FileVersion", PRODUCT_VER_STR 30 | VALUE "InternalName", "MissingHUD2" 31 | VALUE "OriginalFilename", "MissingHUD2.exe" 32 | VALUE "ProductName", "Missing HUD 2" 33 | VALUE "ProductVersion", PRODUCT_VER_STR 34 | VALUE "LegalCopyright", "Apache 2.0 License" 35 | END 36 | END 37 | 38 | BLOCK "VarFileInfo" 39 | BEGIN 40 | VALUE "Translation", 0x409, 1200 41 | END 42 | END -------------------------------------------------------------------------------- /res/WinResources.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_WINRESOURCES_H 16 | #define MISSINGHUD2_WINRESOURCES_H 17 | 18 | #include 19 | 20 | #define APP_ICON 1000 21 | 22 | #endif //MISSINGHUD2_WINRESOURCES_H 23 | -------------------------------------------------------------------------------- /res/files/MHUD2Font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networkMe/missinghud2/759bd359f99338e7b35966b218d81df927eceb4c/res/files/MHUD2Font.png -------------------------------------------------------------------------------- /res/files/MHUD2FontCharmap.texmap: -------------------------------------------------------------------------------- 1 | 0 1 2 3 4 5 6 7 8 9 % . - + -------------------------------------------------------------------------------- /res/files/MHUD2StatIcons.texmap: -------------------------------------------------------------------------------- 1 | speed range firerate shotspeed shotheight damage luck deal_door_chance tears_fired deal_with_angel deal_with_devil -------------------------------------------------------------------------------- /res/files/MUD2StatIcons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networkMe/missinghud2/759bd359f99338e7b35966b218d81df927eceb4c/res/files/MUD2StatIcons.png -------------------------------------------------------------------------------- /res/files/MissingHUD2Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networkMe/missinghud2/759bd359f99338e7b35966b218d81df927eceb4c/res/files/MissingHUD2Banner.png -------------------------------------------------------------------------------- /res/files/MissingHUD2Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networkMe/missinghud2/759bd359f99338e7b35966b218d81df927eceb4c/res/files/MissingHUD2Logo.png -------------------------------------------------------------------------------- /res/files/MissingHUD2_Ico.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networkMe/missinghud2/759bd359f99338e7b35966b218d81df927eceb4c/res/files/MissingHUD2_Ico.ico -------------------------------------------------------------------------------- /res/files/psds/MHUD2Font.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networkMe/missinghud2/759bd359f99338e7b35966b218d81df927eceb4c/res/files/psds/MHUD2Font.psd -------------------------------------------------------------------------------- /res/files/psds/MUD2StatIcons.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networkMe/missinghud2/759bd359f99338e7b35966b218d81df927eceb4c/res/files/psds/MUD2StatIcons.psd -------------------------------------------------------------------------------- /res/files/psds/Missing HUD2 Banner.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networkMe/missinghud2/759bd359f99338e7b35966b218d81df927eceb4c/res/files/psds/Missing HUD2 Banner.psd -------------------------------------------------------------------------------- /res/files/psds/Missing HUD2 Logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networkMe/missinghud2/759bd359f99338e7b35966b218d81df927eceb4c/res/files/psds/Missing HUD2 Logo.psd -------------------------------------------------------------------------------- /res/files/steam_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/networkMe/missinghud2/759bd359f99338e7b35966b218d81df927eceb4c/res/files/steam_logo.png -------------------------------------------------------------------------------- /res/shaders/hud_icon_frag.glsl: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | varying vec2 v_texture_coord; 4 | 5 | uniform sampler2D app_texture; 6 | uniform vec3 texture_color; 7 | 8 | void main() 9 | { 10 | gl_FragColor = texture2D(app_texture, v_texture_coord) * vec4(texture_color, 1.0); 11 | } -------------------------------------------------------------------------------- /res/shaders/hud_icon_vert.glsl: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | uniform mat4 projection; 4 | 5 | attribute vec2 position; 6 | attribute vec2 texture_coord; 7 | 8 | varying vec2 v_texture_coord; 9 | 10 | void main() 11 | { 12 | v_texture_coord = texture_coord; 13 | gl_Position = projection * vec4(position.xy, 0.0, 1.0); 14 | } -------------------------------------------------------------------------------- /src/BoIInjector.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "BoIInjector.h" 16 | 17 | BoIInjector::BoIInjector() 18 | { 19 | } 20 | 21 | BoIInjector::~BoIInjector() 22 | { 23 | Stop(); 24 | } 25 | 26 | void BoIInjector::Start() 27 | { 28 | LOG(INFO) << "Starting DLL injection monitor thread."; 29 | inject_thread_ = std::thread(&BoIInjector::InjectorThread, this); 30 | } 31 | 32 | void BoIInjector::Stop() 33 | { 34 | LOG(INFO) << "Stopping DLL injection monitor thread."; 35 | stop_injector_ = true; 36 | if (inject_thread_.joinable()) 37 | inject_thread_.join(); 38 | } 39 | 40 | void BoIInjector::InjectorThread() 41 | { 42 | try 43 | { 44 | while (!stop_injector_) 45 | { 46 | if (IsBoIRunning()) 47 | { 48 | LOG(INFO) << "BoI process found, time to inject..."; 49 | 50 | // Set-up the DLL message queue (for IPC) 51 | MHUD::MsgQueue::Remove(MSG_QUEUE_APP_TO_DLL); 52 | MHUD::MsgQueue::Remove(MSG_QUEUE_DLL_TO_APP); 53 | app_msg_queue_ = MHUD::MsgQueue::GetInstance(MSG_QUEUE_APP_TO_DLL); 54 | dll_msg_queue_ = MHUD::MsgQueue::GetInstance(MSG_QUEUE_DLL_TO_APP); 55 | std::thread handle_dll_msgs = std::thread(&BoIInjector::HandleDllMsgs, this); 56 | handle_dll_msgs.detach(); 57 | 58 | // Send the preferences message ready for the DLL 59 | SendNewPrefs(MHUD::Options::ReadCfgFile(CFG_FILENAME)); 60 | 61 | // Inject DLL 62 | isaac_process_ = BoIProcess::GetInstance(); 63 | isaac_process_->HookBoIProcess(); 64 | 65 | // Notify that we're injected 66 | LOG(INFO) << "BoI process injected successfully."; 67 | emit InjectionStatus(InjectStatus(InjectStatus::Result::OK)); 68 | 69 | // Just sleep until Isaac closes or injector is quitting 70 | while (isaac_process_->IsRunning() && !stop_injector_) 71 | { 72 | if (!isaac_process_->MHUD2Active()) // MHUD2 should not unload until we want it to (or Isaac closes) 73 | throw std::runtime_error("Error occurred within injected DLL. Check MHUD2.log for details."); 74 | 75 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 76 | } 77 | 78 | // Eject our DLLs (if they are still in the running BoI process) 79 | LOG(INFO) << "BoI process closed or MissingHUD2 requested quit."; 80 | BoIProcess::Close(); 81 | 82 | // Clean-up DLL message queue 83 | app_msg_queue_ = nullptr; 84 | dll_msg_queue_ = nullptr; 85 | MHUD::MsgQueue::Remove(MSG_QUEUE_APP_TO_DLL); 86 | MHUD::MsgQueue::Remove(MSG_QUEUE_DLL_TO_APP); 87 | } 88 | else 89 | { 90 | emit InjectionStatus(InjectStatus(InjectStatus::Result::NOT_FOUND)); 91 | } 92 | 93 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 94 | } 95 | } 96 | catch (std::runtime_error &e) 97 | { 98 | LOG(ERROR) << "Injection thread error occured: " << e.what() << " (Error Code: " << GetLastError() << ")"; 99 | emit FatalError(e.what()); 100 | } 101 | catch (boost::interprocess::interprocess_exception &ie) 102 | { 103 | LOG(ERROR) << "boost::interprocess communication error occured: " << ie.what() << " (Error Code: " << GetLastError() << ")"; 104 | emit FatalError("boost::interprocesscommunication error occured. Check MHUD2.log for details."); 105 | } 106 | catch (...) 107 | { 108 | LOG(ERROR) << "Unknown error occured. " << " (Error Code: " << GetLastError() << ")"; 109 | emit FatalError("Unknown error occured."); 110 | } 111 | } 112 | 113 | void BoIInjector::SendNewPrefs(MHUD::Prefs new_prefs) 114 | { 115 | if (app_msg_queue_) 116 | { 117 | app_msg_queue_->SendPrefs(new_prefs); 118 | } 119 | } 120 | 121 | bool BoIInjector::IsBoIRunning() 122 | { 123 | bool isaac_running = false; 124 | 125 | PROCESSENTRY32W proc_entry = { 0 }; 126 | proc_entry.dwSize = sizeof(proc_entry); 127 | HANDLE proc_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 128 | if (proc_snapshot == INVALID_HANDLE_VALUE) 129 | throw std::runtime_error("[IsBoIRunning] Unable to obtain the system's process list to check if Isaac is running."); 130 | if (!Process32FirstW(proc_snapshot, &proc_entry)) 131 | throw std::runtime_error("[IsBoIRunning] Unable to read the system's process list to check if Isaac is running."); 132 | do 133 | { 134 | if (std::wstring(proc_entry.szExeFile) == WCHAR_BOI_PROCESS_NAME) 135 | { 136 | isaac_running = true; 137 | break; 138 | } 139 | } while (Process32NextW(proc_snapshot, &proc_entry)); 140 | CloseHandle(proc_snapshot); 141 | 142 | return isaac_running; 143 | } 144 | 145 | void BoIInjector::HandleDllMsgs() 146 | { 147 | try 148 | { 149 | while (dll_msg_queue_ != nullptr) 150 | { 151 | MHUD::MHUDMsg mhud_msg; 152 | while(MHUD::MsgQueue::GetInstance(MSG_QUEUE_DLL_TO_APP)->TryRecieve(&mhud_msg)) 153 | { 154 | switch(mhud_msg.msg_type) 155 | { 156 | case MHUD_IPC_LOG_MSG: 157 | { 158 | mhud2::Log log_msg; 159 | log_msg.ParseFromString(mhud_msg.msg_content); 160 | LogFromDLL(log_msg); 161 | } break; 162 | } 163 | } 164 | 165 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 166 | } 167 | } 168 | catch(boost::interprocess::interprocess_exception &ie) 169 | { 170 | LOG(ERROR) << "boost::interprocess communication error occurred: " << ie.what(); 171 | emit FatalError("boost::interprocess communication error occurred in DLL interprocess message thread."); 172 | } 173 | catch (...) 174 | { 175 | LOG(ERROR) << "Unknown error occurred in DLL interprocess message thread."; 176 | emit FatalError("Unknown error occurred in DLL interprocess message thread."); 177 | } 178 | } 179 | 180 | void BoIInjector::LogFromDLL(mhud2::Log log_msg) 181 | { 182 | if (log_msg.has_log_type() && log_msg.has_log_msg()) 183 | { 184 | switch (log_msg.log_type()) 185 | { 186 | case log_msg.LOG_INFO: 187 | { 188 | LOG(INFO) << "[MHUD2 DLL] " << log_msg.log_msg(); 189 | } break; 190 | 191 | case log_msg.LOG_ERROR: 192 | { 193 | LOG(ERROR) << "[MHUD2 DLL] " << log_msg.log_msg(); 194 | } break; 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/BoIInjector.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef BOISTATSREBORN_BOIINJECTOR_H 16 | #define BOISTATSREBORN_BOIINJECTOR_H 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include "BoIProcess.h" 25 | #include "MHUD_MsgQueue.h" 26 | 27 | struct InjectStatus 28 | { 29 | enum Result 30 | { 31 | NOT_FOUND, 32 | FAIL, 33 | OK 34 | }; 35 | 36 | InjectStatus() { } 37 | InjectStatus(Result cons_status) { inj_result = cons_status; } 38 | 39 | Result inj_result = Result::NOT_FOUND; 40 | }; 41 | Q_DECLARE_METATYPE(InjectStatus); 42 | 43 | class BoIInjector : public QObject 44 | { 45 | Q_OBJECT 46 | 47 | public: 48 | BoIInjector(); 49 | ~BoIInjector(); 50 | 51 | void Start(); 52 | void Stop(); 53 | 54 | inline BoIProcess* GetIsaacProcess() 55 | { 56 | return isaac_process_; 57 | } 58 | 59 | signals: 60 | void InjectionStatus(InjectStatus s); 61 | void FatalError(std::string err_msg); 62 | 63 | private slots: 64 | void SendNewPrefs(MHUD::Prefs new_prefs); 65 | 66 | private: 67 | void InjectorThread(); 68 | 69 | bool IsBoIRunning(); 70 | void HandleDllMsgs(); 71 | 72 | void LogFromDLL(mhud2::Log log_msg); 73 | 74 | private: 75 | bool stop_injector_ = false; 76 | std::thread inject_thread_; 77 | BoIProcess *isaac_process_ = nullptr; 78 | 79 | MHUD::MsgQueue *app_msg_queue_ = nullptr; 80 | MHUD::MsgQueue *dll_msg_queue_ = nullptr; 81 | }; 82 | 83 | 84 | #endif //BOISTATSREBORN_BOIINJECTOR_H 85 | -------------------------------------------------------------------------------- /src/BoIProcess.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "BoIProcess.h" 16 | 17 | BoIProcess* BoIProcess::instance_ = nullptr; 18 | 19 | BoIProcess* BoIProcess::GetInstance() 20 | { 21 | if (instance_ == nullptr) 22 | instance_ = new BoIProcess(); 23 | 24 | return instance_; 25 | } 26 | 27 | void BoIProcess::Close() 28 | { 29 | if (instance_ != nullptr) 30 | delete instance_; 31 | 32 | instance_ = nullptr; 33 | } 34 | 35 | BoIProcess::BoIProcess() 36 | { 37 | } 38 | 39 | BoIProcess::~BoIProcess() 40 | { 41 | UnhookBoIProcess(); 42 | } 43 | 44 | bool BoIProcess::HookBoIProcess() 45 | { 46 | // Walk through windows processes and open BoI 47 | PROCESSENTRY32W proc_entry = { 0 }; 48 | proc_entry.dwSize = sizeof(proc_entry); 49 | HANDLE proc_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 50 | if (proc_snapshot == INVALID_HANDLE_VALUE) 51 | throw std::runtime_error("[HookBoIProcess] Unable to obtain the system's process list in order to inject into Isaac."); 52 | if (!Process32FirstW(proc_snapshot, &proc_entry)) 53 | throw std::runtime_error("[HookBoIProcess] Unable to read the system's process list in order to inject into Isaac."); 54 | do 55 | { 56 | if (std::wstring(proc_entry.szExeFile) == WCHAR_BOI_PROCESS_NAME) 57 | { 58 | process_id_ = proc_entry.th32ProcessID; 59 | process_ = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process_id_); 60 | if (process_ == NULL) 61 | { 62 | // Let's try with SeDebugPrivilege enabled 63 | if (!EnableDebugPrivilege()) 64 | throw std::runtime_error("[HookBoIProcess] Couldn't access the BoI process, then tried to enable " 65 | "debug privileges, that also failed! Try running MHUD2 as admin."); 66 | 67 | process_ = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process_id_); 68 | if (process_ == NULL) 69 | throw std::runtime_error("[HookBoIProcess] Couldn't access the BoI process, even with debug privileges. " 70 | "Your anti-virus is probably blocking MHUD2."); 71 | } 72 | 73 | break; 74 | } 75 | } while (Process32NextW(proc_snapshot, &proc_entry)); 76 | CloseHandle(proc_snapshot); 77 | 78 | if (process_id_ == 0) 79 | throw std::runtime_error("[HookBoIProcess] Unable to find an active Binding of Isaac process."); 80 | 81 | // Attempt to make sure that Isaac is initialized before we try to inject MHUD2 82 | WaitForBoIProcessInit(); 83 | 84 | // Commit memory into the process for our DLL path 85 | LPVOID remote_mem = VirtualAllocEx(process_, 0, MAX_PATH * sizeof(wchar_t), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 86 | if (remote_mem == NULL) 87 | throw std::runtime_error("[HookBoIProcess] Unable to commit memory into the active BoI process."); 88 | 89 | // Load in our hook DLL (MissingHUD2Hook.dll) 90 | LoadLibraryIntoBoI(remote_mem, "MissingHUD2Hook.dll"); 91 | 92 | // Call "Start" method in the MissingHUD2Hook.dll 93 | FARPROC start_addr = GetRemoteProcAddress(injected_dll_, "MissingHUD2Hook.dll", "MHUD2_Start"); 94 | HANDLE rThread = CreateRemoteThread(process_, NULL, 0, (LPTHREAD_START_ROUTINE)start_addr, NULL, 0, NULL); 95 | if (rThread == NULL) 96 | throw std::runtime_error("[HookBoIProcess] Couldn't execute MHUD2_Start in MissingHUD2Hook.dll."); 97 | CloseHandle(rThread); 98 | 99 | // Clear up the memory we committed 100 | VirtualFreeEx(process_, remote_mem, MAX_PATH * sizeof(wchar_t), MEM_RELEASE); 101 | return true; 102 | } 103 | 104 | bool BoIProcess::EnableDebugPrivilege() 105 | { 106 | TOKEN_PRIVILEGES token_privs = { 0 }; 107 | token_privs.PrivilegeCount = 1; 108 | HANDLE proc_token = NULL; 109 | 110 | if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &proc_token) || 111 | !LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &token_privs.Privileges[0].Luid)) 112 | return false; 113 | 114 | token_privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 115 | bool adjust_result = (bool)AdjustTokenPrivileges(proc_token, FALSE, &token_privs, 0, NULL, NULL); 116 | 117 | CloseHandle(proc_token); 118 | return adjust_result; 119 | } 120 | 121 | HMODULE BoIProcess::LoadLibraryIntoBoI(LPVOID remote_mem, std::string library) 122 | { 123 | // Get path of library to inject 124 | FARPROC load_library_addr = GetRemoteProcAddress(GetModuleHandleW(L"kernel32.dll"), "kernel32.dll", "LoadLibraryW"); 125 | std::wstring dir_path = QCoreApplication::applicationDirPath().toStdWString(); 126 | std::wstring library_path = dir_path + L"/" + QString::fromStdString(library).toStdWString(); 127 | if (library_path.length() > MAX_PATH) 128 | throw std::runtime_error("[LoadLibraryIntoBoI] Path of " + library + " is too long..." + 129 | QString::fromStdWString(library_path).toStdString()); 130 | 131 | // Run LoadLibraryW in the remote process (BoI) 132 | LOG(INFO) << "Injecting dll: " << QString::fromStdWString(library_path).toStdString(); 133 | if (WriteProcessMemory(process_, remote_mem, library_path.c_str(), library_path.length() * sizeof(wchar_t), NULL) == 0) 134 | throw std::runtime_error("[LoadLibraryIntoBoI] Couldn't write the path to " + library + " into the BoI process."); 135 | HANDLE rem_thread = CreateRemoteThread(process_, NULL, 0, (LPTHREAD_START_ROUTINE)load_library_addr, remote_mem, 0, NULL); 136 | if (rem_thread == NULL) 137 | throw std::runtime_error("[LoadLibraryIntoBoI] Couldn't execute LoadLibraryW in the BoI process."); 138 | WaitForSingleObject(rem_thread, INFINITE); 139 | 140 | // Make sure the DLL loaded properly 141 | DWORD exit_code = 0; 142 | GetExitCodeThread(rem_thread, &exit_code); 143 | if (exit_code == 0) 144 | throw std::runtime_error("[LoadLibraryIntoBoI] Error while executing LoadLibraryW in the BoI process. " 145 | "Try running Missing HUD 2 from another directory path."); 146 | CloseHandle(rem_thread); 147 | 148 | // Record the LoadLibraryW return code so that we can FreeLibrary when it comes time 149 | injected_dll_ = (HMODULE)exit_code; 150 | 151 | // Cleanup what we wrote to remote memory 152 | wchar_t zero_data[MAX_PATH] = { 0 }; 153 | WriteProcessMemory(process_, remote_mem, zero_data, sizeof(zero_data), NULL); 154 | 155 | LOG(INFO) << "Injected " << library << " into BoI process with handle 0x" << std::hex << exit_code << "."; 156 | return (HMODULE)exit_code; 157 | } 158 | 159 | bool BoIProcess::IsRunning() 160 | { 161 | DWORD exit_code = 0; 162 | if (GetExitCodeProcess(process_, &exit_code) == 0) 163 | return false; 164 | 165 | return (exit_code == STILL_ACTIVE); 166 | } 167 | 168 | bool BoIProcess::MHUD2Active() 169 | { 170 | bool mhud2_active = false; 171 | 172 | // Check the modules associated with the Rebirth process for MissingHUD2Hook.dll 173 | MODULEENTRY32W module_entry = { 0 }; 174 | module_entry.dwSize = sizeof(module_entry); 175 | HANDLE module_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, process_id_); 176 | if (module_snapshot == INVALID_HANDLE_VALUE) 177 | throw std::runtime_error("Unable to take a snapshot of Rebirth's active modules."); 178 | bool module32first_result = (bool)Module32FirstW(module_snapshot, &module_entry); 179 | while (!module32first_result && GetLastError() == ERROR_BAD_LENGTH) // MSDN says keep trying until non-failure if ERROR_BAD_LENGTH 180 | { 181 | module32first_result = (bool)Module32FirstW(module_snapshot, &module_entry); 182 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 183 | } 184 | if (!module32first_result) 185 | throw std::runtime_error("Unable to read the snapshot of Rebirth's active modules."); 186 | do 187 | { 188 | if (std::wstring(module_entry.szModule).find(L"MissingHUD2Hook.dll") != std::string::npos) 189 | { 190 | mhud2_active = true; 191 | break; 192 | } 193 | } while (Module32NextW(module_snapshot, &module_entry)); 194 | CloseHandle(module_snapshot); 195 | 196 | return mhud2_active; 197 | } 198 | 199 | bool BoIProcess::UnhookBoIProcess() 200 | { 201 | if (process_ == NULL) 202 | return true; 203 | 204 | // If the process is still running we should unhook our dll 205 | if (IsRunning() && MHUD2Active()) 206 | { 207 | LOG(INFO) << "Unhooking MHUD2 from the active BoI process."; 208 | 209 | // Call the "MHUD2_Stop" function on the injected DLL, it should clean everything up inside Isaac by itself 210 | FARPROC stop_addr = GetRemoteProcAddress(injected_dll_, "MissingHUD2Hook.dll", "MHUD2_Stop"); 211 | HANDLE rem_thread = CreateRemoteThread(process_, NULL, 0, (LPTHREAD_START_ROUTINE)stop_addr, NULL, 0, NULL); 212 | if (rem_thread == NULL) 213 | throw std::runtime_error("[HookBoIProcess] Couldn't execute MHUD2_Stop in MissingHUD2Hook.dll."); 214 | WaitForSingleObject(rem_thread, 5000); 215 | CloseHandle(rem_thread); 216 | } 217 | 218 | // Close the process handle 219 | return (bool)CloseHandle(process_); 220 | } 221 | 222 | FARPROC BoIProcess::GetRemoteProcAddress(HMODULE rem_dll_module, std::string rem_module_name, std::string proc_name) 223 | { 224 | // Load the DLL into the local process (if it's not already loaded) 225 | std::wstring win_module_name = QString::fromStdString(rem_module_name).toStdWString(); 226 | HMODULE local_hud2hookdll = GetModuleHandleW(win_module_name.c_str()); 227 | if (local_hud2hookdll == NULL) 228 | { 229 | std::wstring hud2hookdll_path = QCoreApplication::applicationDirPath().toStdWString() + L"/" + win_module_name; 230 | local_hud2hookdll = LoadLibraryW(hud2hookdll_path.c_str()); 231 | if (local_hud2hookdll == NULL) 232 | throw std::runtime_error("[GetRemoteProcAddress] Couldn't load " + rem_module_name + " into local process."); 233 | } 234 | 235 | // Get the local address of the procedure and calculate the offset 236 | FARPROC local_address = GetProcAddress(local_hud2hookdll, proc_name.c_str()); 237 | if (local_address == NULL) 238 | throw std::runtime_error("[GetRemoteProcAddress] Unable to GetProcAddress in " + rem_module_name); 239 | DWORD proc_offset = (DWORD)local_address - (DWORD)local_hud2hookdll; 240 | 241 | FARPROC remote_address = (FARPROC)((DWORD)rem_dll_module + (DWORD)proc_offset); 242 | LOG(INFO) << "Local " << proc_name << " address: " << std::hex << (DWORD)local_address; 243 | LOG(INFO) << "Remote " << proc_name << " address: " << std::hex << (DWORD)remote_address; 244 | 245 | // Return the remote address equivalent of the function 246 | return remote_address; 247 | } 248 | 249 | void BoIProcess::WaitForBoIProcessInit() 250 | { 251 | if (process_ == NULL) 252 | return; 253 | 254 | // Make sure the BoI process has been executing for at least 5 seconds, or wait for it to have been 255 | FILETIME process_timing[4] = { 0 }; 256 | if (GetProcessTimes(process_, &process_timing[0], &process_timing[1], &process_timing[2], &process_timing[3]) == 0) 257 | throw std::runtime_error("[HookBoIProcess] Unable to GetProcessTimes for the active BoI executable."); 258 | ULARGE_INTEGER process_created_lg = { 0 }; 259 | process_created_lg.LowPart = process_timing[0].dwLowDateTime; 260 | process_created_lg.HighPart = process_timing[0].dwHighDateTime; 261 | uint64_t process_created = process_created_lg.QuadPart; 262 | 263 | FILETIME system_timing = { 0 }; 264 | GetSystemTimeAsFileTime(&system_timing); 265 | ULARGE_INTEGER system_time_lg = { 0 }; 266 | system_time_lg.LowPart = system_timing.dwLowDateTime; 267 | system_time_lg.HighPart = system_timing.dwHighDateTime; 268 | uint64_t system_time = system_time_lg.QuadPart; 269 | 270 | if (process_created > (system_time - 50000000)) // 5 seconds 271 | { 272 | auto nanoseconds_to_sleep = std::chrono::nanoseconds((process_created - (system_time - 50000000)) * 100); 273 | LOG(INFO) << "Waiting " << std::chrono::duration_cast(nanoseconds_to_sleep).count() 274 | << " milliseconds before injecting."; 275 | std::this_thread::sleep_for(nanoseconds_to_sleep); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/BoIProcess.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef BOISTATSREBORN_BOIPROCESS_H 16 | #define BOISTATSREBORN_BOIPROCESS_H 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #define BOI_PROCESS_NAME "isaac-ng.exe" 33 | #define WCHAR_BOI_PROCESS_NAME L"isaac-ng.exe" 34 | 35 | class BoIProcess 36 | { 37 | public: 38 | static BoIProcess* GetInstance(); 39 | static void Close(); 40 | 41 | bool HookBoIProcess(); 42 | bool IsRunning(); 43 | bool MHUD2Active(); 44 | 45 | private: 46 | BoIProcess(); 47 | ~BoIProcess(); 48 | 49 | bool EnableDebugPrivilege(); 50 | HMODULE LoadLibraryIntoBoI(LPVOID remote_mem, std::string library_path); 51 | FARPROC GetRemoteProcAddress(HMODULE rem_dll_module, std::string rem_module_name, std::string proc_name); 52 | bool UnhookBoIProcess(); 53 | 54 | void WaitForBoIProcessInit(); 55 | 56 | private: 57 | static BoIProcess* instance_; 58 | 59 | HANDLE process_ = NULL; 60 | DWORD process_id_ = 0; 61 | HMODULE injected_dll_ = NULL; 62 | }; 63 | 64 | #endif //BOISTATSREBORN_BOIPROCESS_H 65 | -------------------------------------------------------------------------------- /src/LoaderGUI.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "LoaderGUI.h" 16 | 17 | LoaderGUI::LoaderGUI(QWidget *parent) : 18 | QMainWindow(parent) 19 | { 20 | ui_.setupUi(this); 21 | 22 | // Register Qt5 metatypes (for signals/slots) 23 | qRegisterMetaType(); 24 | qRegisterMetaType(); 25 | 26 | // Connect GUI signals/slots 27 | QObject::connect(ui_.btn_UpdateCheck, SIGNAL(clicked(bool)), 28 | this, SLOT(CheckForUpdates(bool))); 29 | QObject::connect(ui_.btn_ApplyPrefs, SIGNAL(clicked(bool)), 30 | this, SLOT(SavePreferences(bool))); 31 | QObject::connect(ui_.btn_ResetPrefs, SIGNAL(clicked(bool)), 32 | this, SLOT(ResetPreferences(bool))); 33 | QObject::connect(ui_.btn_RunIsaacSteam, SIGNAL(clicked(bool)), 34 | this, SLOT(RunSteamIsaac(bool))); 35 | } 36 | 37 | void LoaderGUI::ConnectSlots(BoIInjector &injector) 38 | { 39 | QObject::connect(this, SIGNAL(NewPrefs(MHUD::Prefs)), 40 | &injector, SLOT(SendNewPrefs(MHUD::Prefs))); 41 | 42 | QObject::connect(&injector, SIGNAL(InjectionStatus(InjectStatus)), 43 | this, SLOT(OnInjectionStatusChange(InjectStatus))); 44 | QObject::connect(&injector, SIGNAL(FatalError(std::string)), 45 | this, SLOT(OnFatalError(std::string))); 46 | } 47 | 48 | void LoaderGUI::UpdatePrefs(MHUD::Prefs mhud_prefs) 49 | { 50 | ui_.cb_ShowNumTears->setChecked(mhud_prefs.show_tears_fired); 51 | ui_.cb_ShowShotHeight->setChecked(mhud_prefs.show_shot_height); 52 | ui_.cb_SplitDealChance->setChecked(mhud_prefs.split_deal_chance); 53 | ui_.spinbox_Precision->setValue(mhud_prefs.stat_precision); 54 | } 55 | 56 | void LoaderGUI::showEvent(QShowEvent *event) 57 | { 58 | QWidget::showEvent(event); 59 | 60 | // Fix UI size 61 | this->setFixedSize(this->size()); 62 | 63 | #ifdef NDEBUG 64 | // Check for MHUD2 updates automatically... 65 | CheckForUpdates(); 66 | #endif 67 | } 68 | 69 | void LoaderGUI::CheckForUpdates(bool checked) 70 | { 71 | if (ui_.btn_UpdateCheck->text().toStdString() == "New version available!") 72 | { 73 | QDesktopServices::openUrl(QUrl(QString::fromStdString("https://github.com/networkMe/missinghud2/releases"))); 74 | return; 75 | } 76 | 77 | ui_.btn_UpdateCheck->setText(QString::fromStdString("Checking for updates...")); 78 | ui_.btn_UpdateCheck->setDisabled(true); 79 | 80 | QNetworkAccessManager *network_mgr = new QNetworkAccessManager(this); 81 | QObject::connect(network_mgr, SIGNAL(finished(QNetworkReply*)), 82 | this, SLOT(OnUpdateResponse(QNetworkReply*))); 83 | QObject::connect(network_mgr, SIGNAL(finished(QNetworkReply*)), 84 | network_mgr, SLOT(deleteLater())); 85 | 86 | QNetworkRequest github_req(QUrl(QString::fromStdString("https://api.github.com/repos/networkMe/missinghud2/releases/latest"))); 87 | network_mgr->get(github_req); 88 | } 89 | 90 | void LoaderGUI::SavePreferences(bool checked) 91 | { 92 | MHUD::Prefs new_mhud2_prefs; 93 | new_mhud2_prefs.show_tears_fired = ui_.cb_ShowNumTears->isChecked(); 94 | new_mhud2_prefs.show_shot_height = ui_.cb_ShowShotHeight->isChecked(); 95 | new_mhud2_prefs.split_deal_chance = ui_.cb_SplitDealChance->isChecked(); 96 | new_mhud2_prefs.stat_precision = ui_.spinbox_Precision->value(); 97 | MHUD::Options::SaveCfgFile(CFG_FILENAME, new_mhud2_prefs); 98 | 99 | // Send the new prefs to the injected DLL 100 | emit NewPrefs(new_mhud2_prefs); 101 | 102 | QMessageBox prefs_saved(this); 103 | prefs_saved.setText("Updated preferences!"); 104 | prefs_saved.setStandardButtons(QMessageBox::Ok); 105 | prefs_saved.exec(); 106 | } 107 | 108 | void LoaderGUI::ResetPreferences(bool checked) 109 | { 110 | UpdatePrefs(MHUD::Prefs()); 111 | } 112 | 113 | void LoaderGUI::RunSteamIsaac(bool checked) 114 | { 115 | // 250900 is Rebirth/Afterbirth's Steam App ID 116 | QDesktopServices::openUrl(QUrl(QString::fromStdString("steam://rungameid/250900"))); 117 | } 118 | 119 | void LoaderGUI::OnUpdateResponse(QNetworkReply *response) 120 | { 121 | try 122 | { 123 | if (response->error() != QNetworkReply::NoError) 124 | throw std::runtime_error("NetworkReply failed. Maybe GitHub is down?"); 125 | 126 | QString str_response = response->readAll(); 127 | 128 | QJsonParseError json_parse_result; 129 | QJsonDocument json_response = QJsonDocument::fromJson(str_response.toUtf8(), &json_parse_result); 130 | if (json_parse_result.error != QJsonParseError::ParseError::NoError) 131 | throw std::runtime_error("Unable to parse the JSON GitHub replied with."); 132 | 133 | QJsonObject json_obj = json_response.object(); 134 | QJsonObject::iterator tag_member = json_obj.find(QString::fromStdString("tag_name")); 135 | if (tag_member == json_obj.end()) 136 | throw std::runtime_error("Unable to find the latest version number in GitHub's API."); 137 | 138 | std::string latest_version = tag_member.value().toString().toStdString(); 139 | if (latest_version != MHUD2_VERSION) 140 | { 141 | LOG(INFO) << "GitHub reported latest version is " << latest_version << 142 | " whilst running version is " << MHUD2_VERSION; 143 | ui_.btn_UpdateCheck->setText(QString::fromStdString("New version available! (" + latest_version + ")")); 144 | ui_.btn_UpdateCheck->setStyleSheet(QString::fromStdString("color: rgb(200, 0, 0);")); 145 | } 146 | else 147 | { 148 | LOG(INFO) << "GitHub reported no updates available."; 149 | ui_.btn_UpdateCheck->setText(QString::fromStdString("No update available")); 150 | } 151 | } 152 | catch(std::runtime_error &e) 153 | { 154 | LOG(ERROR) << "Error occured during update check: " << e.what(); 155 | ui_.btn_UpdateCheck->setText(QString::fromStdString("Update check failed")); 156 | } 157 | 158 | ui_.btn_UpdateCheck->setEnabled(true); 159 | response->deleteLater(); 160 | } 161 | 162 | void LoaderGUI::OnInjectionStatusChange(InjectStatus inject_status) 163 | { 164 | switch (inject_status.inj_result) 165 | { 166 | case InjectStatus::Result::OK: 167 | { 168 | ui_.lbl_InjectStatus->setText(QString::fromStdString("Injected Missing HUD 2 into active BoI Process.")); 169 | } break; 170 | 171 | case InjectStatus::Result::FAIL: 172 | { 173 | ui_.lbl_InjectStatus->setText(QString::fromStdString("Error injecting Missing HUD 2 into active BoI process.")); 174 | } break; 175 | 176 | case InjectStatus::Result::NOT_FOUND: 177 | { 178 | ui_.lbl_InjectStatus->setText(QString::fromStdString("No active Binding of Isaac process found.")); 179 | } break; 180 | } 181 | } 182 | 183 | void LoaderGUI::OnFatalError(std::string err_msg) 184 | { 185 | QMessageBox err_box(this); 186 | err_box.setText(QString::fromStdString("Error occurred.")); 187 | err_box.setInformativeText(QString::fromStdString(err_msg)); 188 | err_box.setStandardButtons(QMessageBox::Ok); 189 | err_box.exec(); 190 | 191 | QApplication::quit(); 192 | } 193 | -------------------------------------------------------------------------------- /src/LoaderGUI.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef BOISTATSREBORN_LOADERGUI_H 16 | #define BOISTATSREBORN_LOADERGUI_H 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include "ui_LoaderGUI.h" 29 | #include "BoIInjector.h" 30 | #include "MHUD_Options.h" 31 | #include "mhud2_version.h" 32 | 33 | Q_DECLARE_METATYPE(std::string); 34 | 35 | class LoaderGUI : public QMainWindow 36 | { 37 | Q_OBJECT 38 | 39 | public: 40 | LoaderGUI(QWidget *parent = 0); 41 | 42 | void ConnectSlots(BoIInjector &injector); 43 | void UpdatePrefs(MHUD::Prefs mhud_prefs); 44 | 45 | signals: 46 | void NewPrefs(MHUD::Prefs mhud_prefs); 47 | 48 | public slots: 49 | void RunSteamIsaac(bool checked = false); 50 | 51 | protected: 52 | void showEvent(QShowEvent *event); 53 | 54 | private slots: 55 | void CheckForUpdates(bool checked = false); 56 | void SavePreferences(bool checked = false); 57 | void ResetPreferences(bool checked = false); 58 | void OnInjectionStatusChange(InjectStatus s); 59 | void OnFatalError(std::string err_msg); 60 | void OnUpdateResponse(QNetworkReply* response); 61 | 62 | private: 63 | Ui::LoaderGUI ui_; 64 | }; 65 | 66 | 67 | #endif //BOISTATSREBORN_LOADERGUI_H 68 | -------------------------------------------------------------------------------- /src/LoaderGUI.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | LoaderGUI 4 | 5 | 6 | Qt::ApplicationModal 7 | 8 | 9 | 10 | 0 11 | 0 12 | 318 13 | 437 14 | 15 | 16 | 17 | Missing HUD 2 Loader 18 | 19 | 20 | 21 | :/icon/APP_ICON:/icon/APP_ICON 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Segoe UI 30 | 10 31 | 32 | 33 | 34 | No active Binding of Isaac process found. 35 | 36 | 37 | Qt::AlignHCenter|Qt::AlignTop 38 | 39 | 40 | 5 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Segoe UI 49 | 12 50 | 51 | 52 | 53 | background: #333; color: white; border: 1px solid black; padding: 10px; 54 | 55 | 56 | Run Isaac via Steam 57 | 58 | 59 | 60 | :/img/STEAM_LOGO:/img/STEAM_LOGO 61 | 62 | 63 | 64 | 64 65 | 19 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Segoe UI 75 | 76 | 77 | 78 | 79 | 80 | 81 | :/img/MHUD2_BANNER 82 | 83 | 84 | false 85 | 86 | 87 | Qt::AlignCenter 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | Segoe UI 96 | 97 | 98 | 99 | QFrame::StyledPanel 100 | 101 | 102 | QFrame::Plain 103 | 104 | 105 | 106 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 107 | 108 | 109 | 5 110 | 111 | 112 | 5 113 | 114 | 115 | 5 116 | 117 | 118 | 119 | 120 | Stats Precision (decimal places): 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 50 129 | 16777215 130 | 131 | 132 | 133 | 5 134 | 135 | 136 | 2 137 | 138 | 139 | 140 | 141 | 142 | 143 | Save Preferences 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | Segoe UI 152 | 12 153 | 75 154 | true 155 | true 156 | 157 | 158 | 159 | Preferences 160 | 161 | 162 | Qt::AlignCenter 163 | 164 | 165 | 166 | 167 | 168 | 169 | Tears Fired: 170 | 171 | 172 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 173 | 174 | 175 | 176 | 177 | 178 | 179 | Show 180 | 181 | 182 | 183 | 184 | 185 | 186 | Reset to Defaults 187 | 188 | 189 | 190 | 191 | 192 | 193 | Shot Height: 194 | 195 | 196 | 197 | 198 | 199 | 200 | Show 201 | 202 | 203 | 204 | 205 | 206 | 207 | Split Devil/Angel room chance: 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | true 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 6 228 | 229 | 230 | QLayout::SetDefaultConstraint 231 | 232 | 233 | 234 | 235 | 236 | Segoe UI 237 | 238 | 239 | 240 | false 241 | 242 | 243 | color: #555; 244 | 245 | 246 | Check for updates... 247 | 248 | 249 | true 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | Segoe UI 258 | 259 | 260 | 261 | <a href="https://github.com/networkMe/missinghud2/releases">v1.4.4</a> 262 | 263 | 264 | Qt::RichText 265 | 266 | 267 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 268 | 269 | 270 | 5 271 | 272 | 273 | true 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | -------------------------------------------------------------------------------- /src/MHUD_MsgQueue.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "MHUD_MsgQueue.h" 16 | 17 | using namespace boost::interprocess; 18 | 19 | std::map MHUD::MsgQueue::instances_; 20 | 21 | MHUD::MsgQueue *MHUD::MsgQueue::GetInstance(std::string queue_name) 22 | { 23 | if (instances_.count(queue_name) == 0) 24 | instances_[queue_name] = new MsgQueue(queue_name); 25 | 26 | return instances_[queue_name]; 27 | } 28 | 29 | void MHUD::MsgQueue::Destroy(std::string queue_name) 30 | { 31 | if (instances_.count(queue_name) != 0) 32 | { 33 | delete instances_[queue_name]; 34 | instances_.erase(queue_name); 35 | } 36 | } 37 | 38 | void MHUD::MsgQueue::Remove(std::string queue_name) 39 | { 40 | if (instances_.count(queue_name) != 0) 41 | Destroy(queue_name); 42 | 43 | message_queue::remove(queue_name.c_str()); 44 | } 45 | 46 | MHUD::MsgQueue::MsgQueue(std::string queue_name) 47 | { 48 | // Set-up the Dll message queue (for IPC) 49 | permissions mq_permissions; 50 | mq_permissions.set_unrestricted(); 51 | 52 | mhud2_msgs_ = new message_queue(open_or_create, queue_name.c_str(), 50, sizeof(byte) * (MAX_MSG_SIZE_BYTES + 1), 53 | mq_permissions); 54 | } 55 | 56 | MHUD::MsgQueue::~MsgQueue() 57 | { 58 | delete mhud2_msgs_; 59 | } 60 | 61 | bool MHUD::MsgQueue::TryRecieve(MHUD::MHUDMsg *mhud_msg) 62 | { 63 | unsigned int msg_prio = 0; 64 | message_queue::size_type msg_size = 0; 65 | std::string msg_buffer(MAX_MSG_SIZE_BYTES + 1, 0); 66 | if (!mhud2_msgs_->try_receive(&msg_buffer[0], MAX_MSG_SIZE_BYTES + 1, msg_size, msg_prio)) 67 | return false; 68 | 69 | // Get message type 70 | byte message_type = msg_buffer[0]; 71 | msg_buffer.erase(0, 1); 72 | 73 | // Parse message 74 | switch(message_type) 75 | { 76 | case MHUD_IPC_LOG_MSG: 77 | { 78 | mhud_msg->msg_type = MHUD_IPC_LOG_MSG; 79 | mhud_msg->msg_size = msg_size; 80 | mhud_msg->msg_content = msg_buffer; 81 | } break; 82 | 83 | case MHUD_IPC_PREFS: 84 | { 85 | mhud_msg->msg_type = MHUD_IPC_PREFS; 86 | mhud_msg->msg_size = msg_size; 87 | mhud_msg->msg_content = msg_buffer; 88 | } break; 89 | 90 | default: 91 | { 92 | mhud_msg->msg_type = 255; 93 | mhud_msg->msg_size = 0; 94 | mhud_msg->msg_content = ""; 95 | } break; 96 | } 97 | 98 | return true; 99 | } 100 | 101 | void MHUD::MsgQueue::SendLog(mhud2::Log::LogType log_type, std::string message) 102 | { 103 | mhud2::Log log_proto; 104 | log_proto.set_log_type(log_type); 105 | log_proto.set_log_msg(message); 106 | 107 | unsigned int msg_prio = 0; 108 | std::string msg_buffer(MAX_MSG_SIZE_BYTES, 0); 109 | log_proto.SerializeToString(&msg_buffer); 110 | msg_buffer.insert(0, 1, MHUD_IPC_LOG_MSG); 111 | 112 | mhud2_msgs_->send(&msg_buffer[0], MAX_MSG_SIZE_BYTES + 1, msg_prio); 113 | } 114 | 115 | void MHUD::MsgQueue::SendPrefs(MHUD::Prefs mhud_prefs) 116 | { 117 | mhud2::Preferences prefs_proto; 118 | prefs_proto.set_show_tears_fired(mhud_prefs.show_tears_fired); 119 | prefs_proto.set_show_shot_height(mhud_prefs.show_shot_height); 120 | prefs_proto.set_split_deal_chance(mhud_prefs.split_deal_chance); 121 | prefs_proto.set_stat_precision(mhud_prefs.stat_precision); 122 | 123 | unsigned int msg_prio = 0; 124 | std::string msg_buffer(MAX_MSG_SIZE_BYTES, 0); 125 | prefs_proto.SerializeToString(&msg_buffer); 126 | msg_buffer.insert(0, 1, MHUD_IPC_PREFS); 127 | 128 | mhud2_msgs_->send(&msg_buffer[0], MAX_MSG_SIZE_BYTES + 1, msg_prio); 129 | } 130 | -------------------------------------------------------------------------------- /src/MHUD_MsgQueue.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_MHUD_MSGQUEUE_H 16 | #define MISSINGHUD2_MHUD_MSGQUEUE_H 17 | 18 | #include 19 | 20 | #include 21 | 22 | #include "mhud2.pb.h" 23 | #include "MHUD_Options.h" 24 | 25 | #define MAX_MSG_SIZE_BYTES 1024 26 | #define MSG_QUEUE_APP_TO_DLL "mhud2_app_to_dll" 27 | #define MSG_QUEUE_DLL_TO_APP "mhud2_dll_to_app" 28 | 29 | #define QUEUE_LOG(LEVEL, MSG) MHUD::MsgQueue::GetInstance(MSG_QUEUE_DLL_TO_APP)->SendLog(LEVEL, MSG); 30 | #define MHUD_IPC_LOG_MSG 0x1 31 | #define MHUD_IPC_PREFS 0x2 32 | 33 | typedef unsigned char byte; 34 | 35 | namespace MHUD 36 | { 37 | 38 | struct MHUDMsg 39 | { 40 | byte msg_type = 0; 41 | boost::interprocess::message_queue::size_type msg_size = 0; 42 | std::string msg_content = std::string(MAX_MSG_SIZE_BYTES, 0); 43 | }; 44 | 45 | class MsgQueue 46 | { 47 | public: 48 | static MHUD::MsgQueue* GetInstance(std::string queue_name); 49 | static void Destroy(std::string queue_name); 50 | static void Remove(std::string queue_name); 51 | 52 | bool TryRecieve(MHUDMsg* mhud_msg); 53 | 54 | void SendLog(mhud2::Log::LogType log_type, std::string message); 55 | void SendPrefs(MHUD::Prefs mhud_prefs); 56 | 57 | private: 58 | MsgQueue(std::string queue_name); 59 | ~MsgQueue(); 60 | 61 | private: 62 | static std::map instances_; 63 | 64 | boost::interprocess::message_queue *mhud2_msgs_ = nullptr; 65 | }; 66 | 67 | } 68 | 69 | #endif //MISSINGHUD2_MHUD_MSGQUEUE_H 70 | -------------------------------------------------------------------------------- /src/MHUD_Options.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "MHUD_Options.h" 16 | 17 | std::map MHUD::Options::cfg_files_; 18 | 19 | MHUD::Prefs MHUD::Options::ReadCfgFile(std::string cfg_file_name, bool use_cache) 20 | { 21 | if (cfg_files_.count(cfg_file_name) > 0 && use_cache) 22 | return cfg_files_[cfg_file_name]; 23 | 24 | // Read the preferences structure from the config file (if it exists) 25 | QSettings cfg_file(QString::fromStdString(cfg_file_name), QSettings::IniFormat); 26 | MHUD::Prefs cfg_prefs; 27 | cfg_prefs.show_tears_fired = cfg_file.value("show_tears_fired", false).toBool(); 28 | cfg_prefs.show_shot_height = cfg_file.value("show_shot_height", false).toBool(); 29 | cfg_prefs.split_deal_chance = cfg_file.value("split_deal_chance", true).toBool(); 30 | cfg_prefs.stat_precision = cfg_file.value("stat_precision", 2).toInt(); 31 | 32 | // Cache the result 33 | cfg_files_[cfg_file_name] = cfg_prefs; 34 | return cfg_prefs; 35 | } 36 | 37 | void MHUD::Options::SaveCfgFile(std::string cfg_file_name, MHUD::Prefs new_prefs) 38 | { 39 | // Update cache for the file if it exists 40 | if (cfg_files_.count(cfg_file_name) > 0) 41 | { 42 | cfg_files_[cfg_file_name] = new_prefs; 43 | } 44 | 45 | QSettings cfg_file(QString::fromStdString(cfg_file_name), QSettings::IniFormat); 46 | cfg_file.setValue("show_tears_fired", new_prefs.show_tears_fired); 47 | cfg_file.setValue("show_shot_height", new_prefs.show_shot_height); 48 | cfg_file.setValue("split_deal_chance", new_prefs.split_deal_chance); 49 | cfg_file.setValue("stat_precision", new_prefs.stat_precision); 50 | } 51 | -------------------------------------------------------------------------------- /src/MHUD_Options.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_MHUDPREFS_H 16 | #define MISSINGHUD2_MHUDPREFS_H 17 | 18 | #include 19 | 20 | #ifdef QT_CORE_LIB 21 | #include 22 | #include 23 | #endif 24 | 25 | #define CFG_FILENAME "MHUD2.prefs" 26 | 27 | #define CFG_USE_CACHE true 28 | #define CFG_NO_CACHE false 29 | 30 | namespace MHUD 31 | { 32 | 33 | struct Prefs 34 | { 35 | bool show_tears_fired = false; 36 | bool show_shot_height = false; 37 | bool split_deal_chance = true; 38 | int stat_precision = 2; 39 | }; 40 | 41 | class Options 42 | { 43 | public: 44 | static MHUD::Prefs ReadCfgFile(std::string cfg_file_name, bool use_cache = true); 45 | static void SaveCfgFile(std::string cfg_file_name, MHUD::Prefs new_prefs); 46 | 47 | private: 48 | static std::map cfg_files_; 49 | }; 50 | 51 | } 52 | 53 | #endif //MISSINGHUD2_MHUDPREFS_H 54 | -------------------------------------------------------------------------------- /src/MissingHUD2.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "LoaderGUI.h" 20 | #include "BoIInjector.h" 21 | #include "mhud2_version.h" 22 | 23 | INITIALIZE_EASYLOGGINGPP 24 | void InitializeEasyLogging(int argc, char* argv[]); 25 | std::unique_ptr ParseCommandLine(QApplication *app); 26 | 27 | #ifdef QT_STATIC 28 | Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); 29 | #endif 30 | 31 | int main(int argc, char* argv[]) 32 | { 33 | // Qt5 34 | QApplication app(argc, argv); 35 | 36 | // Initialize file logger 37 | InitializeEasyLogging(argc, argv); 38 | LOG(INFO) << "========== Missing HUD 2 " << MHUD2_VERSION << " =========="; 39 | 40 | // Initialize BoI DLL Injector 41 | BoIInjector injector; 42 | 43 | // Load preferences 44 | MHUD::Prefs mhud_prefs = MHUD::Options::ReadCfgFile(CFG_FILENAME); 45 | 46 | // Handle command line options 47 | std::unique_ptr cmd_line = ParseCommandLine(&app); 48 | 49 | // Show GUI 50 | LoaderGUI gui; 51 | gui.ConnectSlots(injector); 52 | gui.UpdatePrefs(mhud_prefs); 53 | gui.show(); 54 | 55 | // Open Isaac automatically if cmd_line says too 56 | if (cmd_line->isSet("openisaac")) 57 | gui.RunSteamIsaac(); 58 | 59 | // Start the DLL monitoring thread 60 | injector.Start(); 61 | 62 | int ret_code = app.exec(); 63 | LOG(INFO) << "Missing HUD 2 exiting with exit code " << ret_code << "."; 64 | return ret_code; 65 | } 66 | 67 | void InitializeEasyLogging(int argc, char* argv[]) 68 | { 69 | START_EASYLOGGINGPP(argc, argv); 70 | 71 | // EasyLogging++ does not support unicode file paths, we workaround this by changing the working directory 72 | std::wstring app_dir = QCoreApplication::applicationDirPath().toStdWString(); 73 | SetCurrentDirectoryW(app_dir.c_str()); 74 | 75 | // Initialize the file logging module 76 | std::string log_file = "MHUD2.log"; 77 | std::string conf_file = "MHUD2.log.conf"; 78 | el::Configurations logger_conf(conf_file); 79 | logger_conf.setGlobally(el::ConfigurationType::Filename, log_file); 80 | logger_conf.setGlobally(el::ConfigurationType::ToFile, "true"); 81 | el::Loggers::setDefaultConfigurations(logger_conf, true); 82 | } 83 | 84 | std::unique_ptr ParseCommandLine(QApplication *app) 85 | { 86 | std::unique_ptr cmd_parser(new QCommandLineParser()); 87 | 88 | QCommandLineOption run_isaac("openisaac", "Open Isaac Steam edition automatically when MHUD2 starts."); 89 | cmd_parser->addOption(run_isaac); 90 | 91 | cmd_parser->process(*app); 92 | return cmd_parser; 93 | } -------------------------------------------------------------------------------- /src/dll/AfterbirthMemReader.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "AfterbirthMemReader.h" 16 | 17 | AfterbirthMemReader::AfterbirthMemReader() 18 | { 19 | GetAfterbirthModuleInfo(); 20 | } 21 | 22 | AfterbirthMemReader::~AfterbirthMemReader() 23 | { 24 | } 25 | 26 | void AfterbirthMemReader::GetAfterbirthModuleInfo() 27 | { 28 | // Find the static address pointer of the Rebirth PlayerManager instance 29 | std::vector player_manager_inst_address_p_bytes_ = SearchMemForVal(PlayerManagerInstAddr); 30 | if (player_manager_inst_address_p_bytes_.size() < 4) 31 | throw std::runtime_error("Couldn't find the PlayerManager static instance address"); 32 | player_manager_inst_p_addr_ = *((DWORD*)player_manager_inst_address_p_bytes_.data()); 33 | 34 | std::stringstream ss; 35 | ss << "PlayerManager Instance **: " << std::hex << player_manager_inst_p_addr_; 36 | QUEUE_LOG(mhud2::Log::LOG_INFO, ss.str()); 37 | ss.str(""); ss.clear(); 38 | 39 | // Find the offset of the Players list relative to the PlayerManager instance 40 | *((DWORD*)(PlayerManagerPlayerListOffset.signature + 2)) = player_manager_inst_p_addr_; 41 | std::vector player_manager_player_list_offset_bytes_ = SearchMemForVal(PlayerManagerPlayerListOffset); 42 | if (player_manager_player_list_offset_bytes_.size() < 2) 43 | throw std::runtime_error("Couldn't find the PlayerManager PlayerList offset"); 44 | player_manager_player_list_offset_ = *((WORD*)player_manager_player_list_offset_bytes_.data()); 45 | 46 | ss << "PlayerManager PlayerList offset: " << std::hex << player_manager_player_list_offset_; 47 | QUEUE_LOG(mhud2::Log::LOG_INFO, ss.str()); 48 | ss.str(""); ss.clear(); 49 | 50 | // Find the map address for the RNG fucntion 51 | std::vector rng_map_address = SearchMemForVal(AfterbirthRNGMap); 52 | if (rng_map_address.size() < 4) 53 | throw std::runtime_error("Couldn't find the RNG map address!"); 54 | rng_map_addr_ = *((DWORD*)rng_map_address.data()); 55 | 56 | ss << "RNG Map offset: " << std::hex << rng_map_addr_; 57 | QUEUE_LOG(mhud2::Log::LOG_INFO, ss.str()); 58 | ss.str(""); ss.clear(); 59 | 60 | // Find the RNG addresses for the RNG function 61 | std::vector rng_value_addresses = SearchMemForVal(AfterbirthRNGVals); 62 | if (rng_value_addresses.size() < 12) 63 | throw std::runtime_error("Couldn't find the RNG value addresses!"); 64 | rng_value_1_addr_ = *((DWORD*)rng_value_addresses.data()); 65 | rng_value_2_addr_ = *((DWORD*)(rng_value_addresses.data() + 0x4)); 66 | rng_value_3_addr_ = *((DWORD*)(rng_value_addresses.data() + 0x8)); 67 | 68 | ss << "RNG Value 1 offset: " << std::hex << rng_value_1_addr_; 69 | QUEUE_LOG(mhud2::Log::LOG_INFO, ss.str()); 70 | ss.str(""); ss.clear(); 71 | 72 | ss << "RNG Value 2 offset: " << std::hex << rng_value_2_addr_; 73 | QUEUE_LOG(mhud2::Log::LOG_INFO, ss.str()); 74 | ss.str(""); ss.clear(); 75 | 76 | ss << "RNG Value 3 offset: " << std::hex << rng_value_3_addr_; 77 | QUEUE_LOG(mhud2::Log::LOG_INFO, ss.str()); 78 | ss.str(""); ss.clear(); 79 | } 80 | 81 | bool AfterbirthMemReader::IsRunActive() 82 | { 83 | // To check whether a run is currently active I check how many players there are (just like Afterbirth actually does) 84 | // When there are 0 players, then we are not in a run 85 | DWORD player_list = GetPlayerListMemAddr(); 86 | if (player_list == 0) 87 | return false; 88 | 89 | int num_players = (int)((*(DWORD*)(player_list + sizeof(DWORD))) - (*(DWORD*) player_list)); 90 | return (num_players > 0); 91 | } 92 | 93 | float AfterbirthMemReader::GetPlayerStatf(PlayerStat player_stat) 94 | { 95 | if (player_stat == PlayerStat::kDealDoorChance) 96 | { 97 | float door_chance = GetDealDoorChance(); 98 | SaveStat(PlayerStat::kDealDoorChance, door_chance); 99 | return door_chance; 100 | } 101 | else if (player_stat == PlayerStat::kDealWithDevil) 102 | { 103 | float devil_chance = GetDealWithDevilChance(); 104 | SaveStat(PlayerStat::kDealWithDevil, devil_chance); 105 | return devil_chance; 106 | } 107 | else if (player_stat == PlayerStat::kDealWithAngel) 108 | { 109 | float angel_chance = GetDealWithAngelChance(); 110 | SaveStat(PlayerStat::kDealWithAngel, angel_chance); 111 | return angel_chance; 112 | } 113 | 114 | DWORD player_class = GetPlayerMemAddr(); 115 | if (player_class == 0) 116 | return 0.0f; 117 | 118 | float stat_val; 119 | switch (player_stat) 120 | { 121 | case PlayerStat::kSpeed: 122 | stat_val = *((float*)(player_class + AB_STAT_SPEED)); 123 | break; 124 | 125 | case PlayerStat::kRange: 126 | stat_val = *((float*)(player_class + AB_STAT_RANGE)); 127 | break; 128 | 129 | case PlayerStat::kShotSpeed: 130 | stat_val = *((float*)(player_class + AB_STAT_SHOTSPEED)); 131 | break; 132 | 133 | case PlayerStat::kShotHeight: 134 | stat_val = *((float*)(player_class + AB_STAT_SHOTHEIGHT)); 135 | break; 136 | 137 | case PlayerStat::kDamage: 138 | stat_val = *((float*)(player_class + AB_STAT_DAMAGE)); 139 | break; 140 | 141 | case PlayerStat::kLuck: 142 | stat_val = *((float*)(player_class + AB_STAT_LUCK)); 143 | break; 144 | 145 | default: 146 | stat_val = 0.00f; 147 | } 148 | 149 | if (player_stat == PlayerStat::kRange) 150 | { 151 | stat_val *= -1; // Range is stored as a negative, but we show it positive on the HUD 152 | } 153 | 154 | // Save the stat into our memory 155 | SaveStat(player_stat, stat_val); 156 | return stat_val; 157 | } 158 | 159 | int AfterbirthMemReader::GetPlayerStati(PlayerStat player_stat) 160 | { 161 | DWORD player_class = GetPlayerMemAddr(); 162 | if (player_class == 0) 163 | return 0; 164 | 165 | int stat_val; 166 | switch (player_stat) 167 | { 168 | case PlayerStat::kTearsDelay: 169 | stat_val = *((int*)(player_class + AB_STAT_TEARS)); 170 | break; 171 | 172 | case PlayerStat::kTearsFired: 173 | stat_val = *((int*)(player_class + AB_STAT_TEARSFIRED)); 174 | break; 175 | 176 | default: 177 | stat_val = 0; 178 | } 179 | 180 | // Save the stat into our memory 181 | SaveStat(player_stat, (float)stat_val); 182 | return stat_val; 183 | } 184 | 185 | float AfterbirthMemReader::GetDealWithDevilChance() 186 | { 187 | return GetDealDoorChance() * (1.00f - GetDealWithAngelMultiplier()); 188 | } 189 | 190 | float AfterbirthMemReader::GetDealWithAngelChance() 191 | { 192 | return GetDealDoorChance() * GetDealWithAngelMultiplier(); 193 | } 194 | 195 | float AfterbirthMemReader::GetDealWithAngelMultiplier() 196 | { 197 | // If you haven't seen a devil deal yet, or you have taken a devil deal via health payment 198 | // you can't receive an Angel room 199 | DWORD player_manager_inst = GetPlayerManagerMemAddr(); 200 | if (player_manager_inst == 0) 201 | return 0.0f; 202 | 203 | float angel_chance = 0.0f; 204 | 205 | // "Feeling blessed" player status can override the Deal with the Devil precedent conditions 206 | float feeling_blessed_chance = 0.0f; 207 | *(DWORD*)(&feeling_blessed_chance) = *((DWORD*)(player_manager_inst + AB_PLAYER_MANAGER_YOU_FEEL_BLESSED)); 208 | if (feeling_blessed_chance > 0.0f) 209 | angel_chance = feeling_blessed_chance; // It's always 50% in the cases I tested, 210 | // possibly could depend on the amount of "Feeling blessed" buffs you receive 211 | 212 | DWORD seen_devil = *((DWORD*)(player_manager_inst + AB_PLAYER_MANAGER_SEEN_DEVIL)); 213 | if (((seen_devil & 0x20) | 0) == 0) 214 | return angel_chance; // Haven't seen a Devil room, can't get Angel room yet 215 | 216 | DWORD paid_devil = *((DWORD*)(player_manager_inst + AB_PLAYER_MANAGER_PAID_DEVIL)); 217 | if (paid_devil != 0) 218 | return angel_chance; // Paid for a Devil deal with red health, can't get Angel room 219 | 220 | angel_chance = 0.50f; // Default chance to replace Devil room with Angel room is 50% 221 | 222 | if (PlayerHasItem(AB_PASSIVE_ITEM_KEYPIECE_1)) 223 | angel_chance = angel_chance + ((1.00f - angel_chance) * 0.25f); // Having Key Piece #1 gives a 25% chance roll 224 | 225 | if (PlayerHasItem(AB_PASSIVE_ITEM_KEYPIECE_2)) 226 | angel_chance = angel_chance + ((1.00f - angel_chance) * 0.25f); // Having Key Piece #2 gives a 25% chance roll 227 | 228 | if (PlayerHasTrinket(AB_PASSIVE_TRINKET_ROSARYBEAD)) 229 | angel_chance = angel_chance + ((1.00f - angel_chance) * 0.50f); // Holding the Rosary Bead trinket gives 50% chance roll 230 | 231 | if (*((DWORD*)(player_manager_inst + AB_PLAYER_MANAGER_AMOUNT_DONATED)) >= 10) 232 | angel_chance = angel_chance + ((1.00f - angel_chance) * 0.50f); // Donating 10 or more coins on a floor gives 50% chance roll 233 | 234 | DWORD floor_flags = *((DWORD*)(player_manager_inst + AB_PLAYER_MANAGER_FLOOR_FLAGS)); 235 | if (((floor_flags >> 1) & 0xff) & 1) 236 | angel_chance = angel_chance + ((1.00f - angel_chance) * 0.25f); // Devil Beggar blown up gives 25% chance roll 237 | 238 | if (((floor_flags >> 3) & 0xff) & 1) 239 | angel_chance = angel_chance + ((1.00f - angel_chance) * 0.10f); // Paying out a normal beggar gives 10% chance roll 240 | 241 | if (((floor_flags >> 4) & 0xff) & 1) 242 | angel_chance = angel_chance - ((1.00f - angel_chance) * 0.10f); // Paying out a devil beggar removes 10% chance roll 243 | 244 | if (feeling_blessed_chance > 0.0f) 245 | angel_chance = angel_chance + ((1.00f - angel_chance) * feeling_blessed_chance); // Having the "Feeling blessed" status gives another Angel room roll 246 | 247 | return angel_chance; 248 | } 249 | 250 | float AfterbirthMemReader::GetDealDoorChance() 251 | { 252 | if (PlayingGreed()) 253 | return 1.00f; // Greed mode lets players pick if they want a Deal or not 254 | 255 | DWORD player_manager_inst = GetPlayerManagerMemAddr(); 256 | if (player_manager_inst == 0) 257 | return 0.0f; 258 | 259 | int current_floor = *((int*)player_manager_inst); 260 | int labyrinth_flag = *((int*)((DWORD)player_manager_inst + AB_PLAYER_MANAGER_CURSE_FLAGS)); // Need to take into account whether the floor is a labyrinth cursed floor 261 | current_floor_ = current_floor; 262 | if (labyrinth_flag == AB_LABYRINTH_CURSE) 263 | ++current_floor_; 264 | if (current_floor_ == 1 || current_floor_ > 8) // In-eligible for natural deal on these floors (even with Goat Head) 265 | return 0.0f; 266 | 267 | DWORD player = GetPlayerMemAddr(); 268 | float deal_chance = 0.01f; // Default 1% chance 269 | 270 | if (PlayerHasItem(AB_PASSIVE_ITEM_PENTAGRAM)) // Pentagram adds 10% chance 271 | deal_chance += 0.10f; 272 | 273 | if (PlayerHasItem(AB_PASSIVE_ITEM_BLACKCANDLE)) // Black Candle adds 15% chance 274 | deal_chance += 0.15f; 275 | 276 | DWORD pentagram_count = *((DWORD*)(player + AB_PASSIVE_ITEM_PENTAGRAM_COUNT)); 277 | 278 | // Zodiac has a chance to give you a Pentagram effect 279 | if (PlayerHasItem(AB_PASSIVE_ITEM_ZODIAC)) 280 | { 281 | DWORD zodiac_result = ZodiacItemRNGFunc(); 282 | if (zodiac_result == AB_PASSIVE_ITEM_PENTAGRAM) // Is Zodiac randomly providing a Pentagram? 283 | ++pentagram_count; 284 | } 285 | 286 | if (pentagram_count > 1) 287 | deal_chance += 0.05f; // More than one Pentagram adds another 5% chance 288 | // Zodiac can not give you the first Pentagram, only subsequent ones 289 | 290 | DWORD player_active_item = *((DWORD*)(player + AB_ITEM_ACTIVE_SLOT)); 291 | if (player_active_item == AB_ACTIVE_ITEM_BOOKOFREVELATIONS) // Holding Book of Revelations adds 17.5% chance 292 | deal_chance += 0.175f; 293 | else if (player_active_item == AB_ACTIVE_ITEM_BOOKOFBELIAL) // Holding Book of Belial adds 12.5% chance 294 | deal_chance += 0.125f; 295 | 296 | BYTE floor_flag = *((BYTE*)(player_manager_inst + AB_PLAYER_MANAGER_FLOOR_FLAGS)); 297 | if (floor_flag & 1) // Killing a normal beggar adds 35% chance 298 | deal_chance += 0.35f; 299 | 300 | DWORD floor_flags = *((DWORD*)(player_manager_inst + AB_PLAYER_MANAGER_FLOOR_FLAGS)); 301 | if (((floor_flags >> 2) & 1) <= 0) // Not taking red heart damage on the entire floor adds 99% chance 302 | deal_chance += 0.99f; 303 | if (((floor_flags >> 6) & 1) > 0) // Blowing up a dead shopkeeper adds 10% chance 304 | deal_chance += 0.10f; 305 | 306 | // Not taking damage from the floor's boss room adds 35% chance 307 | DWORD current_room = GetCurrentRoom(); 308 | DWORD boss_room = *((DWORD*)(player_manager_inst + AB_PLAYER_MANAGER_BOSS_ROOM_CODE)); 309 | if (current_room == boss_room) 310 | { 311 | DWORD boss_fight = *((DWORD*)(player_manager_inst + AB_PLAYER_MANAGER_FLOOR_BOSS_FIGHT)); 312 | BYTE boss_dmg_flag = *((BYTE*)(boss_fight + AB_BOSS_FIGHT_TOOK_RED_DMG)); 313 | if (boss_dmg_flag == 1) 314 | boss_fight_took_dmg_ = true; 315 | } 316 | else 317 | { 318 | // This works to replicate how Rebirth handles boss fight damage. 319 | // On the first roll of the DWD chance it takes into account whether you took damage or not. 320 | // However, subsequent rolls (to keep the door open) ALWAYS add the +35% chance, 321 | // regardless of whether you took boss damage or not. 322 | if (boss_fight_took_dmg_) 323 | boss_fight_took_dmg_ = false; 324 | } 325 | 326 | if (!boss_fight_took_dmg_) // Not taking damage from the boss fight adds 35% chance 327 | deal_chance += 0.35f; 328 | 329 | DWORD deal_prev_floor = *((DWORD*)(player_manager_inst + AB_PLAYER_MANAGER_DEAL_PREV_FLOOR)); 330 | if (deal_prev_floor > 0) 331 | { 332 | int deal_num_floors_ago = current_floor - (int) deal_prev_floor; // Rebirth uses the non-labyrinthed current_floor value 333 | if (deal_num_floors_ago <= 1) 334 | { 335 | deal_chance *= 0.25f; // Player has seen a Deal with Devil door less than 2 floors ago, reduce overall chance by 75% 336 | } 337 | else if (deal_num_floors_ago == 2) 338 | { 339 | deal_chance *= 0.5f; // Player has seen a Deal with Devil door 2 floors ago, reduce overall chance by 50% 340 | } 341 | } 342 | 343 | if (PlayerHasItem(AB_PASSIVE_ITEM_GOATHEAD)) // Goat Head adds 6660% chance (nice!) 344 | deal_chance += 66.6; 345 | 346 | if (deal_chance > 1.00f) 347 | deal_chance = 1.00f; 348 | 349 | return deal_chance; 350 | } 351 | 352 | void AfterbirthMemReader::SaveStat(PlayerStat player_stat, float stat_val) 353 | { 354 | // If the stat is different from the one in our memory, note it 355 | if (stat_change_.count(player_stat) > 0) 356 | { 357 | if (stat_change_[player_stat].new_stat_val != stat_val) 358 | { 359 | stat_change_[player_stat].prev_stat_val = stat_change_[player_stat].new_stat_val; 360 | stat_change_[player_stat].new_stat_val = stat_val; 361 | stat_change_[player_stat].stat_diff = stat_change_[player_stat].new_stat_val - stat_change_[player_stat].prev_stat_val; 362 | stat_change_[player_stat].time_changed = std::chrono::system_clock::now(); 363 | } 364 | } 365 | else 366 | { 367 | RecentStatChange new_stat_change; 368 | new_stat_change.stat = player_stat; 369 | new_stat_change.prev_stat_val = stat_val; 370 | new_stat_change.new_stat_val = stat_val; 371 | new_stat_change.time_changed = new_stat_change.time_changed - new_stat_change.show_timeout; // We don't want the initial 0->X change to show 372 | stat_change_[player_stat] = new_stat_change; 373 | } 374 | } 375 | 376 | float AfterbirthMemReader::GetPlayerRecentStatChangef(PlayerStat player_stat) 377 | { 378 | float recent_stat_change = 0.0f; 379 | 380 | if (stat_change_.count(player_stat) > 0) 381 | { 382 | if (stat_change_[player_stat].time_changed > (std::chrono::system_clock::now() - stat_change_[player_stat].show_timeout)) 383 | { 384 | return stat_change_[player_stat].stat_diff; 385 | } 386 | } 387 | 388 | return recent_stat_change; 389 | } 390 | 391 | int AfterbirthMemReader::GetPlayerRecentStatChangei(PlayerStat player_stat) 392 | { 393 | int recent_stat_change = 0; 394 | 395 | if (stat_change_.count(player_stat) > 0) 396 | { 397 | if (stat_change_[player_stat].time_changed > (std::chrono::system_clock::now() - stat_change_[player_stat].show_timeout)) 398 | { 399 | return (int)stat_change_[player_stat].stat_diff; 400 | } 401 | } 402 | 403 | return recent_stat_change; 404 | } 405 | 406 | DWORD AfterbirthMemReader::GetPlayerManagerMemAddr() 407 | { 408 | DWORD player_manager_inst = *((DWORD*)(player_manager_inst_p_addr_)); 409 | if (player_manager_inst == 0) 410 | return 0; // Player manager hasn't been initialized by Afterbirth yet 411 | else 412 | return player_manager_inst; 413 | } 414 | 415 | 416 | DWORD AfterbirthMemReader::GetPlayerListMemAddr() 417 | { 418 | DWORD player_manager_inst = GetPlayerManagerMemAddr(); 419 | if (player_manager_inst == 0) 420 | return 0; 421 | 422 | if (player_manager_player_list_offset_ == 0) 423 | return 0; 424 | 425 | return (player_manager_inst + player_manager_player_list_offset_); 426 | } 427 | 428 | DWORD AfterbirthMemReader::GetPlayerMemAddr() 429 | { 430 | // Here we follow the Afterbirth memory address chain to get 431 | // the current player class associated with the current run 432 | DWORD player_list = GetPlayerListMemAddr(); 433 | if (player_list == 0) 434 | return 0; 435 | 436 | DWORD player_p = *((DWORD*)player_list); 437 | return *((DWORD*)player_p); 438 | } 439 | 440 | DWORD AfterbirthMemReader::GetCurrentRoom() 441 | { 442 | DWORD player_manager_inst = GetPlayerManagerMemAddr(); 443 | if (player_manager_inst == 0) 444 | return 0; 445 | 446 | DWORD room_code = *((DWORD*)(player_manager_inst + AB_PLAYER_MANAGER_ROOM_CODE)); 447 | DWORD room_num = *((DWORD*)(player_manager_inst + (room_code * 4) + AB_PLAYER_MANAGER_ROOM_CODE_FORMULA_OFFSET)); 448 | 449 | return room_num; 450 | } 451 | 452 | bool AfterbirthMemReader::PlayerHasItem(int item_id) 453 | { 454 | DWORD player = GetPlayerMemAddr(); 455 | DWORD item_flag = *((DWORD*)(player + (item_id * 4) + AB_PLAYER_HAS_ITEM_FORM_OFFSET)); 456 | 457 | return (item_flag != 0); 458 | } 459 | 460 | bool AfterbirthMemReader::PlayerHasTrinket(int trinket_id) 461 | { 462 | int num_trinkets = 1; 463 | if (PlayerHasItem(AB_PASSIVE_ITEM_MUMS_PURSE)) 464 | num_trinkets++; // Mum's Purse lets a player have 2 possible trinkets 465 | 466 | DWORD player = GetPlayerMemAddr(); 467 | DWORD trinket_offset = 0; 468 | for (int i = 0; i < num_trinkets; ++i) 469 | { 470 | DWORD trinket_flag = *((DWORD*)(player + AB_PLAYER_HAS_TRINKET_OFFSET + trinket_offset)); 471 | if (trinket_flag == trinket_id) 472 | return true; 473 | 474 | trinket_offset += 0x4; 475 | } 476 | 477 | // Player clearly doesn't have that trinket! 478 | return false; 479 | } 480 | 481 | bool AfterbirthMemReader::PlayingGreed() 482 | { 483 | DWORD player_manager_inst = GetPlayerManagerMemAddr(); 484 | if (player_manager_inst == 0) 485 | return false; 486 | 487 | int game_mode_flag = *((int*)((DWORD)player_manager_inst + AB_PLAYER_MANAGER_GAME_MODE_FLAG)); 488 | return (game_mode_flag == AB_GREED_GAME_MODE); 489 | } 490 | 491 | DWORD AfterbirthMemReader::ZodiacItemRNGFunc() 492 | { 493 | if (!PlayerHasItem(AB_PASSIVE_ITEM_ZODIAC)) 494 | return 0; 495 | 496 | DWORD player_manager_inst = GetPlayerManagerMemAddr(); 497 | if (player_manager_inst == 0) 498 | return 0; 499 | 500 | DWORD game_seed = *((DWORD*)(player_manager_inst + 0xB848)); 501 | if (game_seed == 0) 502 | return 0; 503 | 504 | DWORD current_floor = *((DWORD*)(player_manager_inst)); 505 | DWORD rng_addr = ((DWORD)(player_manager_inst + 0xB844)); 506 | DWORD rng_seed = *((DWORD*)(rng_addr + (current_floor * 0x4) + 0x18)); 507 | DWORD rng_seed_1 = *(DWORD*)rng_value_1_addr_; 508 | DWORD rng_seed_2 = *(DWORD*)rng_value_2_addr_; 509 | DWORD rng_seed_3 = *(DWORD*)rng_value_3_addr_; 510 | 511 | // RNG algorithm 512 | DWORD rng_1 = (rng_seed >> rng_seed_2) ^ rng_seed; 513 | DWORD rng_2 = rng_1 ^ (rng_1 << rng_seed_3); 514 | DWORD rng_3 = rng_2 ^ (rng_2 >> rng_seed_1); 515 | uint64_t rng_multiplied = rng_3 * (uint64_t)0xAAAAAAAB; 516 | DWORD rng_4 = (rng_multiplied >> 32) >> 3; 517 | DWORD rng_map_base = rng_4 + (rng_4 * 2); 518 | rng_map_base += rng_map_base; 519 | rng_map_base += rng_map_base; 520 | DWORD map_modifier = rng_3 - rng_map_base; 521 | 522 | DWORD rng_result = *((DWORD*)((map_modifier * 0x4) + rng_map_addr_)); 523 | return rng_result; 524 | } 525 | -------------------------------------------------------------------------------- /src/dll/AfterbirthMemReader.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_AFTERBIRTHMEMREADER_H 16 | #define MISSINGHUD2_AFTERBIRTHMEMREADER_H 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include "MemReader.h" 26 | #include "IsaacMemSignatures.h" 27 | #include "src/MHUD_MsgQueue.h" 28 | 29 | #define AB_ITEM_ACTIVE_SLOT 0x1CF4 30 | #define AB_ACTIVE_ITEM_BOOKOFREVELATIONS 0x4E 31 | #define AB_ACTIVE_ITEM_BOOKOFBELIAL 0x22 32 | 33 | #define AB_PLAYER_HAS_ITEM_FORM_OFFSET 0x1DA4 34 | #define AB_PASSIVE_ITEM_PENTAGRAM 0x33 35 | #define AB_PASSIVE_ITEM_BLACKCANDLE 0x104 36 | #define AB_PASSIVE_ITEM_GOATHEAD 0xD7 37 | #define AB_PASSIVE_ITEM_ZODIAC 0x188 38 | #define AB_PASSIVE_ITEM_PENTAGRAM_COUNT 0x1E70 39 | #define AB_PASSIVE_ITEM_KEYPIECE_1 0xEE 40 | #define AB_PASSIVE_ITEM_KEYPIECE_2 0xEF 41 | #define AB_PASSIVE_ITEM_MUMS_PURSE 0x8B 42 | 43 | #define AB_PLAYER_HAS_TRINKET_OFFSET 0x1D9C 44 | #define AB_PASSIVE_TRINKET_ROSARYBEAD 0x7 45 | 46 | #define AB_PLAYER_MANAGER_FLOOR_FLAGS 0x7090 47 | #define AB_PLAYER_MANAGER_FLOOR_BOSS_FIGHT 0x7018 48 | #define AB_PLAYER_MANAGER_ROOM_CODE 0x701C 49 | #define AB_PLAYER_MANAGER_ROOM_CODE_FORMULA_OFFSET 0x6D40 50 | #define AB_PLAYER_MANAGER_BOSS_ROOM_CODE 0x7024 51 | #define AB_PLAYER_MANAGER_DEAL_PREV_FLOOR 0x1B4E0C 52 | #define AB_PLAYER_MANAGER_AMOUNT_DONATED 0x1B4E24 53 | #define AB_PLAYER_MANAGER_SEEN_DEVIL 0x1B4E00 54 | #define AB_PLAYER_MANAGER_PAID_DEVIL 0x1B4E1C 55 | #define AB_PLAYER_MANAGER_YOU_FEEL_BLESSED 0x700C 56 | 57 | #define AB_BOSS_FIGHT_TOOK_RED_DMG 0xE8C 58 | 59 | #define AB_PLAYER_MANAGER_CURSE_FLAGS 0xC 60 | #define AB_PLAYER_MANAGER_GAME_MODE_FLAG 0x4 61 | #define AB_GREED_GAME_MODE 0x3 62 | #define AB_LABYRINTH_CURSE 0x2 63 | 64 | #define AB_STAT_SPEED 0x1CDC 65 | #define AB_STAT_RANGE 0x1C14 66 | #define AB_STAT_TEARS 0x1C00 67 | #define AB_STAT_SHOTSPEED 0x1C04 68 | #define AB_STAT_SHOTHEIGHT 0x1C18 69 | #define AB_STAT_DAMAGE 0x1C10 70 | #define AB_STAT_LUCK 0x1CE0 71 | #define AB_STAT_TEARSFIRED 0x1C0C 72 | 73 | class AfterbirthMemReader : public MemReader 74 | { 75 | public: 76 | AfterbirthMemReader(); 77 | ~AfterbirthMemReader(); 78 | 79 | bool IsRunActive(); 80 | bool PlayingGreed(); 81 | 82 | float GetPlayerStatf(PlayerStat player_stat); 83 | int GetPlayerStati(PlayerStat player_stat); 84 | 85 | float GetPlayerRecentStatChangef(PlayerStat player_stat); 86 | int GetPlayerRecentStatChangei(PlayerStat player_stat); 87 | 88 | private: 89 | void GetAfterbirthModuleInfo(); 90 | 91 | DWORD GetPlayerManagerMemAddr(); 92 | DWORD GetPlayerListMemAddr(); 93 | DWORD GetPlayerMemAddr(); 94 | 95 | float GetDealDoorChance(); 96 | float GetDealWithDevilChance(); 97 | float GetDealWithAngelChance(); 98 | float GetDealWithAngelMultiplier(); 99 | DWORD GetCurrentRoom(); 100 | 101 | bool PlayerHasItem(int item_id); 102 | bool PlayerHasTrinket(int trinket_id); 103 | DWORD ZodiacItemRNGFunc(); 104 | 105 | void SaveStat(PlayerStat player_stat, float stat_val); 106 | 107 | private: 108 | DWORD player_manager_inst_p_addr_ = 0; 109 | DWORD player_manager_player_list_offset_ = 0; 110 | 111 | int current_floor_ = 0; 112 | bool boss_fight_took_dmg_ = false; 113 | std::map stat_change_; 114 | 115 | DWORD rng_map_addr_ = 0; 116 | DWORD rng_value_1_addr_ = 0; 117 | DWORD rng_value_2_addr_ = 0; 118 | DWORD rng_value_3_addr_ = 0; 119 | }; 120 | 121 | #endif //MISSINGHUD2_AFTERBIRTHMEMREADER_H 122 | -------------------------------------------------------------------------------- /src/dll/DLLPreferences.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "DLLPreferences.h" 16 | 17 | DLLPreferences *DLLPreferences::instance_ = nullptr; 18 | 19 | DLLPreferences *DLLPreferences::GetInstance() 20 | { 21 | if (instance_ == nullptr) 22 | instance_ = new DLLPreferences(); 23 | 24 | return instance_; 25 | } 26 | 27 | void DLLPreferences::Destroy() 28 | { 29 | if (instance_ != nullptr) 30 | delete instance_; 31 | 32 | instance_ = nullptr; 33 | } 34 | 35 | DLLPreferences::DLLPreferences() 36 | { 37 | monitor_thread_ = std::thread(MsgMonitor, this); 38 | } 39 | 40 | DLLPreferences::~DLLPreferences() 41 | { 42 | quit_monitoring_ = true; 43 | if (monitor_thread_.joinable()) 44 | monitor_thread_.join(); 45 | } 46 | 47 | void DLLPreferences::MsgMonitor() 48 | { 49 | while (!quit_monitoring_) 50 | { 51 | MHUD::MHUDMsg mhud_msg; 52 | while (MHUD::MsgQueue::GetInstance(MSG_QUEUE_APP_TO_DLL)->TryRecieve(&mhud_msg)) 53 | { 54 | switch (mhud_msg.msg_type) 55 | { 56 | case MHUD_IPC_PREFS: 57 | { 58 | mhud2::Preferences prefs_proto; 59 | prefs_proto.ParseFromString(mhud_msg.msg_content); 60 | 61 | current_prefs_.show_tears_fired = prefs_proto.show_tears_fired(); 62 | current_prefs_.show_shot_height = prefs_proto.show_shot_height(); 63 | current_prefs_.split_deal_chance = prefs_proto.split_deal_chance(); 64 | current_prefs_.stat_precision = prefs_proto.stat_precision(); 65 | } break; 66 | } 67 | } 68 | 69 | std::this_thread::sleep_for(std::chrono::seconds(1)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/dll/DLLPreferences.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_DLLPREFERENCES_H 16 | #define MISSINGHUD2_DLLPREFERENCES_H 17 | 18 | #include 19 | #include 20 | 21 | #include "../MHUD_Options.h" 22 | #include "../MHUD_MsgQueue.h" 23 | 24 | class DLLPreferences 25 | { 26 | public: 27 | static DLLPreferences * GetInstance(); 28 | static void Destroy(); 29 | 30 | MHUD::Prefs GetPrefs() 31 | { 32 | return current_prefs_; 33 | }; 34 | 35 | private: 36 | DLLPreferences(); 37 | ~DLLPreferences(); 38 | 39 | void MsgMonitor(); 40 | 41 | private: 42 | static DLLPreferences *instance_; 43 | 44 | bool quit_monitoring_ = false; 45 | std::thread monitor_thread_; 46 | MHUD::Prefs current_prefs_; 47 | }; 48 | 49 | #endif //MISSINGHUD2_DLLPREFERENCES_H 50 | -------------------------------------------------------------------------------- /src/dll/DllMain.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include "IATHook.h" 20 | #include "MemReader.h" 21 | #include "GDISwapBuffers.h" 22 | #include "ResourceLoader.h" 23 | #include "DLLPreferences.h" 24 | 25 | #define DLL_PUBLIC __declspec(dllexport) 26 | 27 | static HMODULE dll_handle = 0; 28 | 29 | BOOL WINAPI gdiSwapBuffersDetour(HDC hdc); 30 | 31 | int APIENTRY DllMain(HMODULE h_dll, DWORD reason, LPVOID reserved) 32 | { 33 | if (reason == DLL_PROCESS_ATTACH) 34 | dll_handle = h_dll; 35 | 36 | return TRUE; 37 | } 38 | 39 | extern "C" DLL_PUBLIC void MHUD2_Start() 40 | { 41 | try 42 | { 43 | QUEUE_LOG(mhud2::Log::LOG_INFO, "MissingHUD2 injected and starting."); 44 | 45 | // Initialize any static objects we require that don't involve an OpenGL context 46 | DLLPreferences::GetInstance(); 47 | ResourceLoader::Initialize(dll_handle); 48 | MemReader::GetMemoryReader(); 49 | 50 | // Hook the OpenGL SwapBuffers function via IAT redirection 51 | GDISwapBuffers *gdi_swapbuffers = GDISwapBuffers::GetInstance(); 52 | IATHook::InitIATHook(GetModuleHandleW(WCHAR_ISAAC_MODULE_NAME), "gdi32.dll", "SwapBuffers", 53 | (LPVOID)&gdiSwapBuffersDetour); 54 | 55 | // Enable the IAT hooks 56 | IATHook::EnableIATHook("SwapBuffers", (LPVOID*)gdi_swapbuffers->GetEndpointAddr()); 57 | } 58 | catch (std::runtime_error &e) 59 | { 60 | QUEUE_LOG(mhud2::Log::LOG_ERROR, "Error occured during MHUD2 initilization: " + std::string(e.what())); 61 | FreeLibraryAndExitThread(dll_handle, EXIT_FAILURE); 62 | } 63 | catch (boost::interprocess::interprocess_exception &ie) 64 | { 65 | FreeLibraryAndExitThread(dll_handle, EXIT_FAILURE); 66 | } 67 | } 68 | 69 | extern "C" DLL_PUBLIC void MHUD2_Stop() 70 | { 71 | QUEUE_LOG(mhud2::Log::LOG_INFO, "MissingHUD2 exiting."); 72 | 73 | // Tell our SwapBuffers detour to cleanup it's OpenGL stuff 74 | GDISwapBuffers *gdi_swapbuffers = GDISwapBuffers::GetInstance(); 75 | gdi_swapbuffers->WaitForCleanup(); 76 | 77 | try 78 | { 79 | // Disable IAT hooks 80 | IATHook::DisableIATHook("SwapBuffers"); 81 | 82 | // Wait for the hooks to be no longer active 83 | // aka. for Rebirth to exit our detours for the last time 84 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 85 | 86 | // Clean-up global static objects 87 | DLLPreferences::Destroy(); 88 | GDISwapBuffers::Destroy(); 89 | ResourceLoader::Destroy(); 90 | MemReader::Destroy(); 91 | MHUD::MsgQueue::Destroy(MSG_QUEUE_DLL_TO_APP); 92 | } 93 | catch (std::runtime_error &e) 94 | { 95 | QUEUE_LOG(mhud2::Log::LOG_ERROR, "Error occured during MHUD2 cleanup: " + std::string(e.what())); 96 | FreeLibraryAndExitThread(dll_handle, EXIT_FAILURE); 97 | } 98 | 99 | // Exit our DLL, Rebirth should be a lot less informative now! 100 | FreeLibraryAndExitThread(dll_handle, EXIT_SUCCESS); 101 | } 102 | 103 | static bool frame_failed = false; 104 | BOOL WINAPI gdiSwapBuffersDetour(HDC hdc) 105 | { 106 | GDISwapBuffers *gdi_swapbuffers = GDISwapBuffers::GetInstance(); 107 | 108 | // Cleanup needs to be called from within the thread with the GL context 109 | // It cleans up many resources that require OpenGL access (glDeleteXXXXXX) 110 | if (gdi_swapbuffers->ShouldCleanup()) 111 | { 112 | gdi_swapbuffers->Cleanup(); 113 | } 114 | else if (!frame_failed && hdc != NULL) // NULL hdc is illegal (according to Microsoft) 115 | { 116 | if (!gdi_swapbuffers->CustomizeFrame(hdc)) 117 | { 118 | frame_failed = true; 119 | 120 | std::thread mhud_stop = std::thread(MHUD2_Stop); 121 | mhud_stop.detach(); 122 | } 123 | } 124 | 125 | // Pass control back off to the original SwapBuffers Win32 function (and thus Rebirth...) 126 | return (*gdi_swapbuffers->GetEndpointAddr())(hdc); 127 | } 128 | -------------------------------------------------------------------------------- /src/dll/GDISwapBuffers.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "GDISwapBuffers.h" 16 | 17 | GDISwapBuffers *GDISwapBuffers::instance_ = nullptr; 18 | 19 | GDISwapBuffers::GDISwapBuffers() 20 | { 21 | } 22 | 23 | GDISwapBuffers::~GDISwapBuffers() 24 | { 25 | } 26 | 27 | GDISwapBuffers *GDISwapBuffers::GetInstance() 28 | { 29 | if(!instance_) 30 | instance_ = new GDISwapBuffers(); 31 | 32 | return instance_; 33 | } 34 | 35 | void GDISwapBuffers::Destroy() 36 | { 37 | if (instance_) 38 | delete instance_; 39 | 40 | instance_ = nullptr; 41 | } 42 | 43 | void GDISwapBuffers::Cleanup() 44 | { 45 | std::lock_guard lock_guard(cleanup_mutex); 46 | 47 | if (cleanup_resources_ && !cleanup_complete_) 48 | { 49 | HUDOverlay::Destroy(); 50 | ShaderProgram::DestroyAll(); 51 | SpriteSheet::DestroyAllSpriteSheets(); 52 | cleanup_complete_ = true; 53 | cleanup_complete_cv_.notify_all(); 54 | } 55 | } 56 | 57 | void GDISwapBuffers::WaitForCleanup() 58 | { 59 | std::unique_lock uni_lock(cleanup_mutex); 60 | cleanup_resources_ = true; 61 | cleanup_complete_cv_.wait_for(uni_lock, std::chrono::seconds(5)); 62 | }; 63 | 64 | LPVOID GDISwapBuffers::GetGDI32HookAddr() 65 | { 66 | if (orig_swap_buffers_addr_ == NULL) 67 | { 68 | HMODULE gdi32 = GetModuleHandleW(L"gdi32.dll"); 69 | orig_swap_buffers_addr_ = (LPVOID)GetProcAddress(gdi32, "SwapBuffers"); 70 | } 71 | 72 | return orig_swap_buffers_addr_; 73 | } 74 | 75 | bool GDISwapBuffers::CustomizeFrame(HDC hdc) 76 | { 77 | // Initialize the GLEW OpenGL library on our first frame render (it requires a valid OpenGL context) 78 | if (!glew_ready_) 79 | { 80 | if (!InitializeGLEW()) 81 | return false; 82 | } 83 | 84 | // Disable DEPTH_TEST so we can draw over the top of Rebirth 85 | GLboolean orig_depthtest_val; 86 | glGetBooleanv(GL_DEPTH_TEST, &orig_depthtest_val); 87 | if (orig_depthtest_val) 88 | glDisable(GL_DEPTH_TEST); 89 | 90 | // Enable BLEND so that we can use partially transparent PNG's 91 | GLboolean orig_blend_val; 92 | glGetBooleanv(GL_BLEND, &orig_blend_val); 93 | if (!orig_blend_val) 94 | glEnable (GL_BLEND); 95 | 96 | // Set our OpenGL BlendFunc for alpha transparency 97 | GLint orig_blend_src; 98 | glGetIntegerv(GL_BLEND_SRC, &orig_blend_src); 99 | GLint orig_blend_dst; 100 | glGetIntegerv(GL_BLEND_DST, &orig_blend_dst); 101 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 102 | 103 | // Draw the HUD 104 | HUDOverlay *hud = HUDOverlay::GetInstance(); 105 | hud->DrawHUD(hdc); 106 | 107 | // Restore whatever the original BlendFunc was 108 | glBlendFunc(orig_blend_src, orig_blend_dst); 109 | 110 | // Disable BLEND if it was disabled 111 | if (!orig_blend_val) 112 | glDisable(GL_BLEND); 113 | 114 | // Re-enable DEPTH_TEST if it was enabled 115 | if (orig_depthtest_val) 116 | glEnable(GL_DEPTH_TEST); 117 | 118 | return true; 119 | } 120 | 121 | bool GDISwapBuffers::InitializeGLEW() 122 | { 123 | if (!glew_ready_) 124 | { 125 | glewExperimental = GL_TRUE; 126 | GLenum glew_result = glewInit(); 127 | if (glew_result != GLEW_OK) 128 | { 129 | std::stringstream ss; 130 | ss << "glewInit failed with error: " << glew_result << " (" << glewGetErrorString(glew_result) << ")"; 131 | QUEUE_LOG(mhud2::Log::LOG_ERROR, ss.str()); 132 | 133 | return false; 134 | } 135 | 136 | // MHUD2 requires minimum OpenGL 2.0 support (it was released mid 2004) 137 | if (!GLEW_VERSION_2_0) 138 | { 139 | QUEUE_LOG(mhud2::Log::LOG_ERROR, "MHUD2 requires OpenGL 2.0 graphics driver support."); 140 | return false; 141 | } 142 | 143 | glew_ready_ = true; 144 | } 145 | 146 | return true; 147 | } 148 | -------------------------------------------------------------------------------- /src/dll/GDISwapBuffers.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_GLSWAPBUFFERS_H 16 | #define MISSINGHUD2_GLSWAPBUFFERS_H 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | #include "GLStructs.h" 27 | #include "HUDOverlay.h" 28 | #include "src/MHUD_MsgQueue.h" 29 | #include "res/DllResources.h" 30 | 31 | typedef BOOL WINAPI (*GDISWAPBUFFERSFUNC)(HDC); 32 | 33 | class GDISwapBuffers 34 | { 35 | public: 36 | static GDISwapBuffers *GetInstance(); 37 | static void Destroy(); 38 | 39 | bool CustomizeFrame(HDC hdc); 40 | 41 | void Cleanup(); 42 | void WaitForCleanup(); 43 | inline bool ShouldCleanup() 44 | { 45 | std::lock_guard lock_guard(cleanup_mutex); 46 | return cleanup_resources_; 47 | }; 48 | 49 | LPVOID GetGDI32HookAddr(); 50 | inline GDISWAPBUFFERSFUNC *GetEndpointAddr() 51 | { 52 | return &endpoint_addr_; 53 | }; 54 | 55 | private: 56 | GDISwapBuffers(); 57 | ~GDISwapBuffers(); 58 | 59 | bool InitializeGLEW(); 60 | 61 | private: 62 | static GDISwapBuffers *instance_; 63 | 64 | std::mutex cleanup_mutex; 65 | bool cleanup_resources_ = false; 66 | bool cleanup_complete_ = false; 67 | std::condition_variable cleanup_complete_cv_; 68 | 69 | bool glew_ready_ = false; 70 | 71 | LPVOID orig_swap_buffers_addr_ = NULL; 72 | GDISWAPBUFFERSFUNC endpoint_addr_ = nullptr; 73 | }; 74 | 75 | 76 | #endif //MISSINGHUD2_GLSWAPBUFFERS_H 77 | -------------------------------------------------------------------------------- /src/dll/GLStructs.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "GLStructs.h" 16 | 17 | bool operator !=(const STDSIZE & x, const STDSIZE & y) 18 | { 19 | return std::tie(x.width, x.height) != std::tie(y.width, y.height); 20 | } -------------------------------------------------------------------------------- /src/dll/GLStructs.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_GLSTRUCTS_H 16 | #define MISSINGHUD2_GLSTRUCTS_H 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | struct STDSIZE 24 | { 25 | STDSIZE() {}; 26 | STDSIZE(uint32_t _width, uint32_t _height) 27 | { 28 | width = _width; 29 | height = _height; 30 | }; 31 | 32 | uint32_t width = 0; 33 | uint32_t height = 0; 34 | }; 35 | bool operator !=(const STDSIZE & x, const STDSIZE & y); 36 | typedef STDSIZE VIEWSIZE; 37 | typedef STDSIZE SPRITESIZE; 38 | 39 | struct Color 40 | { 41 | Color() {}; 42 | Color(uint32_t _r, uint32_t _g, uint32_t _b) 43 | { 44 | r = _r; 45 | g = _g; 46 | b = _b; 47 | }; 48 | 49 | uint32_t r; 50 | uint32_t g; 51 | uint32_t b; 52 | }; 53 | 54 | #endif //MISSINGHUD2_GLSTRUCTS_H 55 | -------------------------------------------------------------------------------- /src/dll/HUDOverlay.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "HUDOverlay.h" 16 | 17 | HUDOverlay *HUDOverlay::instance_ = nullptr; 18 | 19 | HUDOverlay *HUDOverlay::GetInstance() 20 | { 21 | if (instance_ == nullptr) 22 | instance_ = new HUDOverlay(); 23 | 24 | return instance_; 25 | } 26 | 27 | void HUDOverlay::Destroy() 28 | { 29 | if (instance_) 30 | delete instance_; 31 | 32 | instance_ = nullptr; 33 | } 34 | 35 | VIEWSIZE HUDOverlay::GetViewportSize() 36 | { 37 | // Check viewport size every 2 seconds (to avoid spamming glGetIntegerv) 38 | if ((viewport_size_.width != 0 || viewport_size_.height != 0) && 39 | (viewport_updated_ > std::chrono::system_clock::now() - std::chrono::milliseconds(2000))) 40 | return viewport_size_; 41 | 42 | GLint viewport_vals[4] = { 0 }; 43 | glGetIntegerv(GL_VIEWPORT, viewport_vals); 44 | int viewport_width = viewport_vals[2]; 45 | int viewport_height = viewport_vals[3]; 46 | 47 | viewport_size_ = VIEWSIZE(viewport_width, viewport_height); 48 | viewport_updated_ = std::chrono::system_clock::now(); 49 | 50 | return viewport_size_; 51 | } 52 | 53 | float HUDOverlay::GetHUDSizeMultiplier() 54 | { 55 | VIEWSIZE vp_size = HUDOverlay::GetViewportSize(); 56 | float hud_scale = 1.0f; 57 | if (vp_size.height <= 720 || vp_size.width <= 1200) 58 | return hud_scale; 59 | 60 | float scale_height = 0.5f * std::ceil((vp_size.height - 720 + 1) / 240.0f); 61 | float scale_width = 0.5f * std::ceil((vp_size.width - 1200 + 1) / 400.0f); 62 | 63 | return hud_scale + std::min(scale_height, scale_width); 64 | } 65 | 66 | HUDOverlay::HUDOverlay() 67 | { 68 | SpriteSheet::LoadSpriteSheet(MHUD2_STAT_ICONS, MHUD2_STAT_ICONS_TEXMAP, SPRITESIZE(32, 32), true); 69 | } 70 | 71 | HUDOverlay::~HUDOverlay() 72 | { 73 | // We don't need to text render if we have no overlay 74 | TextRenderer::DestroyAll(); 75 | 76 | // No need for the below spritesheets either 77 | SpriteSheet::DestroySpriteSheet(MHUD2_STAT_ICONS); 78 | } 79 | 80 | void HUDOverlay::DrawHUD(HDC hdc) 81 | { 82 | // Get the Rebirth memory reader to get the HUD stat values 83 | MemReader *mem_reader = MemReader::GetMemoryReader(); 84 | if (!mem_reader->IsRunActive()) 85 | return; // We don't want to draw the HUD if the player isn't in a Rebirth run 86 | 87 | // Draw the HUD stats info bar 88 | glm::vec2 base_hud_stats_menu; 89 | base_hud_stats_menu.x = 0.0f; 90 | 91 | VIEWSIZE vp_size = GetViewportSize(); 92 | base_hud_stats_menu.y = (vp_size.height / 24) * 15; 93 | 94 | // Show the actual HUD stats 95 | if (DLLPreferences::GetInstance()->GetPrefs().show_tears_fired) 96 | { 97 | HUDStat tears_fired_stat(MHUDSTAT::kStat_TearsFired); 98 | tears_fired_stat.Draw(base_hud_stats_menu, mem_reader->GetPlayerStati(PlayerStat::kTearsFired), 99 | NO_RECENT_STAT_CHANGES_I); 100 | 101 | base_hud_stats_menu.y -= (25.0f * GetHUDSizeMultiplier()); 102 | } 103 | 104 | HUDStat speed_stat(MHUDSTAT::kStat_Speed); 105 | speed_stat.Draw(base_hud_stats_menu, mem_reader->GetPlayerStatf(PlayerStat::kSpeed), 106 | mem_reader->GetPlayerRecentStatChangef(PlayerStat::kSpeed)); 107 | 108 | base_hud_stats_menu.y -= (25.0f * GetHUDSizeMultiplier()); 109 | HUDStat range_stat(MHUDSTAT::kStat_Range); 110 | range_stat.Draw(base_hud_stats_menu, mem_reader->GetPlayerStatf(PlayerStat::kRange), 111 | mem_reader->GetPlayerRecentStatChangef(PlayerStat::kRange)); 112 | 113 | base_hud_stats_menu.y -= (25.0f * GetHUDSizeMultiplier()); 114 | HUDStat firerate_stat(MHUDSTAT::kStat_FireRate); 115 | firerate_stat.Draw(base_hud_stats_menu, mem_reader->GetPlayerStati(PlayerStat::kTearsDelay), 116 | mem_reader->GetPlayerRecentStatChangei(PlayerStat::kTearsDelay)); 117 | 118 | base_hud_stats_menu.y -= (25.0f * GetHUDSizeMultiplier()); 119 | HUDStat shotspeed_stat(MHUDSTAT::kStat_ShotSpeed); 120 | shotspeed_stat.Draw(base_hud_stats_menu, mem_reader->GetPlayerStatf(PlayerStat::kShotSpeed), 121 | mem_reader->GetPlayerRecentStatChangef(PlayerStat::kShotSpeed)); 122 | 123 | if (DLLPreferences::GetInstance()->GetPrefs().show_shot_height) 124 | { 125 | base_hud_stats_menu.y -= (25.0f * GetHUDSizeMultiplier()); 126 | HUDStat shot_height_stat(MHUDSTAT::kStat_ShotHeight); 127 | shot_height_stat.Draw(base_hud_stats_menu, mem_reader->GetPlayerStatf(PlayerStat::kShotHeight), 128 | mem_reader->GetPlayerRecentStatChangef(PlayerStat::kShotHeight)); 129 | } 130 | 131 | base_hud_stats_menu.y -= (25.0f * GetHUDSizeMultiplier()); 132 | HUDStat damage_stat(MHUDSTAT::kStat_Damage); 133 | damage_stat.Draw(base_hud_stats_menu, mem_reader->GetPlayerStatf(PlayerStat::kDamage), 134 | mem_reader->GetPlayerRecentStatChangef(PlayerStat::kDamage)); 135 | 136 | base_hud_stats_menu.y -= (25.0f * GetHUDSizeMultiplier()); 137 | HUDStat luck_stat(MHUDSTAT::kStat_Luck); 138 | luck_stat.Draw(base_hud_stats_menu, mem_reader->GetPlayerStatf(PlayerStat::kLuck), 139 | mem_reader->GetPlayerRecentStatChangef(PlayerStat::kLuck)); 140 | 141 | if (DLLPreferences::GetInstance()->GetPrefs().split_deal_chance) 142 | { 143 | base_hud_stats_menu.y -= (25.0f * GetHUDSizeMultiplier()); 144 | HUDStat dwd_stat(MHUDSTAT::kStat_DealWithDevil); 145 | dwd_stat.Draw(base_hud_stats_menu, mem_reader->GetPlayerStatf(PlayerStat::kDealWithDevil), 146 | mem_reader->GetPlayerRecentStatChangef(PlayerStat::kDealWithDevil), SHOW_AS_PERCENTAGE); 147 | 148 | base_hud_stats_menu.y -= (25.0f * GetHUDSizeMultiplier()); 149 | HUDStat dwa_stat(MHUDSTAT::kStat_DealWithAngel); 150 | dwa_stat.Draw(base_hud_stats_menu, mem_reader->GetPlayerStatf(PlayerStat::kDealWithAngel), 151 | mem_reader->GetPlayerRecentStatChangef(PlayerStat::kDealWithAngel), SHOW_AS_PERCENTAGE); 152 | } 153 | else 154 | { 155 | if (!mem_reader->PlayingGreed()) // Non-split deal chance is irrelevant on Greed mode 156 | { 157 | base_hud_stats_menu.y -= (25.0f * GetHUDSizeMultiplier()); 158 | HUDStat dwd_stat(MHUDSTAT::kStat_DealDoorChance); 159 | dwd_stat.Draw(base_hud_stats_menu, mem_reader->GetPlayerStatf(PlayerStat::kDealDoorChance), 160 | mem_reader->GetPlayerRecentStatChangef(PlayerStat::kDealDoorChance), SHOW_AS_PERCENTAGE); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/dll/HUDOverlay.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_HUDOVERLAY_H 16 | #define MISSINGHUD2_HUDOVERLAY_H 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include "GLStructs.h" 24 | #include "TextRenderer.h" 25 | #include "HUDStat.h" 26 | #include "MemReader.h" 27 | 28 | class HUDOverlay 29 | { 30 | public: 31 | static HUDOverlay *GetInstance(); 32 | static void Destroy(); 33 | 34 | void DrawHUD(HDC hdc); 35 | 36 | VIEWSIZE GetViewportSize(); 37 | float GetHUDSizeMultiplier(); 38 | 39 | private: 40 | HUDOverlay(); 41 | ~HUDOverlay(); 42 | 43 | private: 44 | static HUDOverlay *instance_; 45 | 46 | VIEWSIZE viewport_size_; 47 | std::chrono::time_point viewport_updated_ = std::chrono::system_clock::now(); 48 | }; 49 | 50 | #endif //MISSINGHUD2_HUDOVERLAY_H 51 | -------------------------------------------------------------------------------- /src/dll/HUDStat.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "HUDStat.h" 16 | 17 | HUDStat::HUDStat(MHUDSTAT mhud_stat) 18 | { 19 | hud_stat_ = mhud_stat; 20 | 21 | Color green(0, 200, 0); 22 | Color red(220, 0, 0); 23 | switch (mhud_stat) 24 | { 25 | case MHUDSTAT::kStat_FireRate: 26 | { 27 | negative_color_ = green; 28 | positive_color_ = red; 29 | } break; 30 | 31 | default: 32 | { 33 | positive_color_ = green; 34 | negative_color_ = red; 35 | } 36 | } 37 | } 38 | 39 | HUDStat::~HUDStat() 40 | { 41 | } 42 | 43 | void HUDStat::Draw(glm::vec2 position, float stat_value, float stat_change, bool percentage) 44 | { 45 | // Draw icon sprite 46 | glm::vec2 icon_size(32.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier(), 32.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier()); 47 | SpriteSheet *icon_sprite = SpriteSheet::GetSpriteSheet(MHUD2_STAT_ICONS); 48 | icon_sprite->DrawSprite(position, icon_size, MHUD2STAT_STRING[hud_stat_]); 49 | 50 | // Draw statistic value 51 | position.x += 32.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier(); 52 | position.y -= 8.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier(); 53 | TextRenderer *isaac_text = TextRenderer::GetRenderer(MHUD2_ISAAC_FONT_PNG, MHUD2_ISAAC_FONT_CHARMAP); 54 | glm::vec2 text_render_size = isaac_text->RenderText(position, NumToStr(stat_value, percentage), Color(255, 255, 255)); 55 | 56 | // Draw the change in statistic value if it recently changed 57 | position.x += text_render_size.x; 58 | if (stat_change > 0.0f) 59 | isaac_text->RenderText(position, "+" + NumToStr(stat_change, percentage), positive_color_); 60 | else if (stat_change < 0.0f) 61 | isaac_text->RenderText(position, NumToStr(stat_change, percentage), negative_color_); // Negative symbol automatic via C++ 62 | } 63 | 64 | void HUDStat::Draw(glm::vec2 position, int stat_value, int stat_change) 65 | { 66 | // Draw icon sprite 67 | glm::vec2 icon_size(32.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier(), 32.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier()); 68 | SpriteSheet *icon_sprite = SpriteSheet::GetSpriteSheet(MHUD2_STAT_ICONS); 69 | icon_sprite->DrawSprite(position, icon_size, MHUD2STAT_STRING[hud_stat_]); 70 | 71 | // Draw statistic value 72 | position.x += 32.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier(); 73 | position.y -= 8.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier(); 74 | TextRenderer *isaac_text = TextRenderer::GetRenderer(MHUD2_ISAAC_FONT_PNG, MHUD2_ISAAC_FONT_CHARMAP); 75 | glm::vec2 text_render_size = isaac_text->RenderText(position, NumToStr(stat_value), Color(255, 255, 255)); 76 | 77 | // Draw the change in statistic value if it recently changed 78 | position.x += text_render_size.x; 79 | if (stat_change > 0) 80 | isaac_text->RenderText(position, "+" + NumToStr(stat_change), positive_color_); 81 | else if (stat_change < 0) 82 | isaac_text->RenderText(position, NumToStr(stat_change), negative_color_); // Negative symbol automatic via C++ 83 | } 84 | 85 | std::string HUDStat::NumToStr(float number, bool percentage) 86 | { 87 | std::stringstream ss; 88 | 89 | if (percentage) 90 | { 91 | if ((float)number != (int)number) // float has decimal part 92 | { 93 | ss.setf(std::ios::fixed, std::ios::floatfield); 94 | ss.precision(DLLPreferences::GetInstance()->GetPrefs().stat_precision); 95 | } 96 | 97 | ss << (number * 100) << "%"; 98 | } 99 | else 100 | { 101 | ss.setf(std::ios::fixed, std::ios::floatfield); 102 | ss.precision(DLLPreferences::GetInstance()->GetPrefs().stat_precision); 103 | ss << number; 104 | } 105 | 106 | return ss.str(); 107 | } 108 | 109 | std::string HUDStat::NumToStr(int number) 110 | { 111 | std::stringstream ss; 112 | ss << number; 113 | return ss.str(); 114 | } 115 | -------------------------------------------------------------------------------- /src/dll/HUDStat.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_HUDSTAT_H 16 | #define MISSINGHUD2_HUDSTAT_H 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include "GLStructs.h" 28 | #include "SpriteSheet.h" 29 | #include "TextRenderer.h" 30 | #include "ResourceLoader.h" 31 | #include "ShaderProgram.h" 32 | #include "DLLPreferences.h" 33 | #include 34 | 35 | #define SHOW_AS_PERCENTAGE true 36 | #define NO_RECENT_STAT_CHANGES_I 0 37 | #define NO_RECENT_STAT_CHANGES_F 0.0 38 | 39 | enum MHUDSTAT { 40 | kStat_Speed, 41 | kStat_Range, 42 | kStat_FireRate, 43 | kStat_ShotSpeed, 44 | kStat_ShotHeight, 45 | kStat_Damage, 46 | kStat_Luck, 47 | kStat_DealDoorChance, 48 | kStat_TearsFired, 49 | kStat_DealWithAngel, 50 | kStat_DealWithDevil 51 | }; 52 | 53 | static const std::string MHUD2STAT_STRING[] = { 54 | "speed", 55 | "range", 56 | "firerate", 57 | "shotspeed", 58 | "shotheight", 59 | "damage", 60 | "luck", 61 | "deal_door_chance", 62 | "tears_fired", 63 | "deal_with_angel", 64 | "deal_with_devil" 65 | }; 66 | 67 | class HUDStat 68 | { 69 | public: 70 | HUDStat(MHUDSTAT mhud_stat); 71 | ~HUDStat(); 72 | 73 | void Draw(glm::vec2 position, float stat_value, float stat_change, bool percentage = false); 74 | void Draw(glm::vec2 position, int stat_value, int stat_change); 75 | 76 | std::string NumToStr(float number, bool percentage = false); 77 | std::string NumToStr(int number); 78 | 79 | private: 80 | MHUDSTAT hud_stat_; 81 | Color positive_color_; 82 | Color negative_color_; 83 | }; 84 | 85 | 86 | #endif //MISSINGHUD2_HUDSTAT_H 87 | -------------------------------------------------------------------------------- /src/dll/IATHook.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "IATHook.h" 16 | 17 | std::map IATHook::iat_hooks_; 18 | 19 | void IATHook::InitIATHook(HMODULE app_module, std::string target_module_name, std::string proc_name, LPVOID detour_proc) 20 | { 21 | // Get necessary PE information 22 | IMAGE_DOS_HEADER *dos_header = (IMAGE_DOS_HEADER*)app_module; 23 | 24 | MEMORY_BASIC_INFORMATION module_mem = { 0 }; 25 | if (!VirtualQuery((LPVOID)app_module, &module_mem, sizeof(module_mem))) 26 | throw std::runtime_error("Unable to retrieve memory information for app_module."); 27 | 28 | IMAGE_NT_HEADERS *pe_header = (IMAGE_NT_HEADERS*)((DWORD)dos_header->e_lfanew + (DWORD)module_mem.AllocationBase); 29 | if (dos_header->e_magic != IMAGE_DOS_SIGNATURE || pe_header->Signature != IMAGE_NT_SIGNATURE) 30 | throw std::runtime_error("The memory being read in app_module is incorrect."); 31 | 32 | IMAGE_IMPORT_DESCRIPTOR *import_descriptor = (IMAGE_IMPORT_DESCRIPTOR*)((DWORD)pe_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + (DWORD)module_mem.AllocationBase); 33 | if (!(import_descriptor->Name)) 34 | throw std::runtime_error("The IMAGE_IMPORT_DESCRIPTOR structure is invalid in the app_module."); 35 | 36 | std::transform(target_module_name.begin(), target_module_name.end(), target_module_name.begin(), tolower); 37 | while (import_descriptor->Name) 38 | { 39 | const char* imported_dll_name = (const char*)((DWORD)import_descriptor->Name + (DWORD)module_mem.AllocationBase); 40 | std::string imported_dll_str = std::string(imported_dll_name); 41 | std::transform(imported_dll_str.begin(), imported_dll_str.end(), imported_dll_str.begin(), tolower); 42 | if (imported_dll_str == target_module_name) 43 | { 44 | IMAGE_THUNK_DATA *thunk = (IMAGE_THUNK_DATA*)((DWORD)import_descriptor->FirstThunk + (DWORD)module_mem.AllocationBase); 45 | IMAGE_THUNK_DATA *orig_thunk = (IMAGE_THUNK_DATA*)((DWORD)import_descriptor->OriginalFirstThunk + (DWORD)module_mem.AllocationBase); 46 | 47 | for(; orig_thunk->u1.Function != 0; ++orig_thunk, ++thunk) 48 | { 49 | IMAGE_IMPORT_BY_NAME *import_via_name = (IMAGE_IMPORT_BY_NAME*)((DWORD)orig_thunk->u1.AddressOfData + (DWORD)module_mem.AllocationBase); 50 | const char* import_proc_name = (const char*)(import_via_name->Name); 51 | if (std::string(import_proc_name) == proc_name) 52 | { 53 | // Found the proc to hook 54 | IATHookInfo iat_hook_info; 55 | iat_hook_info.app_module = app_module; 56 | iat_hook_info.target_module_name = target_module_name; 57 | iat_hook_info.proc_name = proc_name; 58 | iat_hook_info.detour_proc = detour_proc; 59 | iat_hook_info.orig_proc_addr = (LPVOID)thunk->u1.Function; 60 | iat_hook_info.thunk = thunk; 61 | iat_hooks_.insert(std::make_pair(proc_name, iat_hook_info)); 62 | 63 | return; 64 | } 65 | } 66 | 67 | // We didn't find the proc to hook 68 | throw std::runtime_error("Procedure " + proc_name + " could not be found in " + target_module_name); 69 | } 70 | 71 | ++import_descriptor; 72 | } 73 | 74 | // We didn't find the DLL to hook :( 75 | throw std::runtime_error("Unable to find " + target_module_name + " in the IAT."); 76 | } 77 | 78 | void IATHook::EnableIATHook(std::string proc_name, LPVOID *orig_proc_addr) 79 | { 80 | if (iat_hooks_.count(proc_name) == 0) 81 | throw std::runtime_error("The IAT hook for " + proc_name + " has not been initialized and cannot be enabled."); 82 | 83 | IATHookInfo iathook_info = iat_hooks_[proc_name]; 84 | 85 | // Make the import memory page writable 86 | MEMORY_BASIC_INFORMATION thunk_mem_info; 87 | if (!VirtualQuery(iathook_info.thunk, &thunk_mem_info, sizeof(MEMORY_BASIC_INFORMATION))) 88 | throw std::runtime_error("Could not query thunk_mem_info when enabling IAT hook for " + proc_name); 89 | if (!VirtualProtect(thunk_mem_info.BaseAddress, thunk_mem_info.RegionSize, PAGE_READWRITE, &thunk_mem_info.Protect)) 90 | throw std::runtime_error("Error occurred setting the IAT to have read/write privileges."); 91 | 92 | std::stringstream ss; 93 | ss << "Enabling IAT hook for " << proc_name << " in module " << iathook_info.target_module_name; 94 | QUEUE_LOG(mhud2::Log::LOG_INFO, ss.str()); 95 | 96 | // Replace the IAT function pointer 97 | *(orig_proc_addr) = iathook_info.orig_proc_addr; 98 | iathook_info.thunk->u1.Function = (DWORD)iathook_info.detour_proc; 99 | 100 | // Restore the import memory page permissions 101 | DWORD dummy; 102 | VirtualProtect(thunk_mem_info.BaseAddress, thunk_mem_info.RegionSize, thunk_mem_info.Protect, &dummy); 103 | } 104 | 105 | void IATHook::DisableIATHook(std::string proc_name) 106 | { 107 | if (iat_hooks_.count(proc_name) == 0) 108 | throw std::runtime_error("The IAT hook for " + proc_name + " has not been initialized and cannot be disabled."); 109 | 110 | IATHookInfo iathook_info = iat_hooks_[proc_name]; 111 | 112 | // Make the import memory page writable 113 | MEMORY_BASIC_INFORMATION thunk_mem_info; 114 | if (!VirtualQuery(iathook_info.thunk, &thunk_mem_info, sizeof(MEMORY_BASIC_INFORMATION))) 115 | throw std::runtime_error("Could not query thunk_mem_info when disabling IAT hook for " + proc_name); 116 | if (!VirtualProtect(thunk_mem_info.BaseAddress, thunk_mem_info.RegionSize, PAGE_READWRITE, &thunk_mem_info.Protect)) 117 | throw std::runtime_error("Error occurred setting the IAT to have read/write privileges."); 118 | 119 | std::stringstream ss; 120 | ss << "Disabling IAT hook for " << proc_name << " in module " << iathook_info.target_module_name; 121 | QUEUE_LOG(mhud2::Log::LOG_INFO, ss.str()); 122 | 123 | // Replace the IAT function pointer with the old function 124 | iathook_info.thunk->u1.Function = (DWORD)iathook_info.orig_proc_addr; 125 | 126 | // Restore the import memory page permissions 127 | DWORD dummy; 128 | VirtualProtect(thunk_mem_info.BaseAddress, thunk_mem_info.RegionSize, thunk_mem_info.Protect, &dummy); 129 | } 130 | -------------------------------------------------------------------------------- /src/dll/IATHook.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_IATHOOK_H 16 | #define MISSINGHUD2_IATHOOK_H 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | #include "src/MHUD_MsgQueue.h" 27 | 28 | struct IATHookInfo 29 | { 30 | bool hook_enabled = false; 31 | 32 | HMODULE app_module = 0; 33 | std::string target_module_name; 34 | std::string proc_name; 35 | LPVOID detour_proc = nullptr; 36 | LPVOID orig_proc_addr = nullptr; 37 | 38 | IMAGE_THUNK_DATA *thunk = nullptr; 39 | }; 40 | 41 | class IATHook 42 | { 43 | public: 44 | static void InitIATHook(HMODULE app_module, std::string target_module_name, std::string proc_name, LPVOID detour_proc); 45 | 46 | static void EnableIATHook(std::string proc_name, LPVOID *orig_proc_addr); 47 | static void DisableIATHook(std::string proc_name); 48 | 49 | private: 50 | static std::map iat_hooks_; 51 | }; 52 | 53 | #endif //MISSINGHUD2_IATHOOK_H 54 | -------------------------------------------------------------------------------- /src/dll/IsaacMemSignatures.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_ISAACMEMSIGNATURES_H 16 | #define MISSINGHUD2_ISAACMEMSIGNATURES_H 17 | 18 | struct MemSig 19 | { 20 | const unsigned char* signature; // As a byte array { 0x11, 0x22, 0x00, etc } 21 | const char* search_mask; // As a string where "b" = actual byte to search for from the signature array, 22 | // "?" = a wildcard byte, 23 | // "v" = a value byte to return 24 | }; 25 | 26 | // ============================================================= 27 | // The below signature is used to determine whether the player is 28 | // playing Rebirth or Afterbirth 29 | // ============================================================= 30 | 31 | 32 | 33 | const static unsigned char AfterbirthCheckSig[] = { 'a', 'f', 't', 'e', 'r', 'b', 'i', 'r', 't', 'h', '.', 'a' }; 34 | const static MemSig AfterbirthCheck = { 35 | AfterbirthCheckSig, 36 | "bbbbbbbbbbbbv" 37 | }; 38 | 39 | 40 | 41 | // ============================================================= 42 | // The below signatures are valid for Isaac Rebirth & Afterbirth 43 | // ============================================================= 44 | 45 | 46 | 47 | // 33 C0 | xor eax,eax | 48 | // C7 45 FC FF FF FF FF | mov dword ptr ss:[ebp-4],FFFFFFFF | 49 | // A3 F4 A1 3E 01 | mov dword ptr ds:[13EA1F4],eax | <== 13EA1F4 is the address we want 50 | // E8 30 EF F7 FF | call isaac-ng.127C490 | 51 | const static unsigned char PlayerManagerInstAddrSig[] = 52 | { 53 | 0x33, 0xC0, 54 | 0xC7, 0x45, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 55 | 0xA3, 0x00, 0x00, 0x00, 0x00, 56 | 0xE8 57 | }; 58 | const static MemSig PlayerManagerInstAddr = { 59 | PlayerManagerInstAddrSig, 60 | "bbbbbbbbbbvvvvb" 61 | }; 62 | 63 | 64 | 65 | // 8B 35 F4 A1 33 01 | mov esi,dword ptr ds:[133A1F4] | 66 | // 8B 86 30 8A 00 00 | mov eax,dword ptr ds:[esi+8A30] | 67 | // 2B 86 2C 8A 00 00 | sub eax,dword ptr ds:[esi+8A2C] | <== 0x8A2C is the offset required 68 | static unsigned char PlayerManagerPlayerListOffsetSig[] = 69 | { 70 | 0x8B, 0x35, 0x00, 0x00, 0x00, 0x00, 71 | 0x8B, 0x86, 0x00, 0x00, 0x00, 0x00, 72 | 0x2B, 0x86, 0x00, 0x00, 0x00, 0x00 73 | }; 74 | const static MemSig PlayerManagerPlayerListOffset = { 75 | PlayerManagerPlayerListOffsetSig, 76 | "bb????bb????bbvv??" 77 | }; 78 | 79 | 80 | 81 | // 8B 74 BE 18 | mov esi,dword ptr ds:[esi+edi*4+18] | 82 | // 8B 15 F8 42 B9 00 | mov edx,dword ptr ds:[B942F8] | <== 83 | // 8B 3D F0 42 B9 00 | mov edi,dword ptr ds:[B942F0] | <== We want these 3 addresses 84 | // 8B 1D F4 42 B9 00 | mov ebx,dword ptr ds:[B942F4] | <== 85 | static unsigned char AfterbirthRNGValsSig[] = 86 | { 87 | 0x8B, 0x00, 0xBE, 0x18, 88 | 0x8B, 0x15, 0x00, 0x00, 0x00, 0x00, 89 | 0x8B, 0x3D, 0x00, 0x00, 0x00, 0x00, 90 | 0x8B, 0x1D, 0x00, 0x00, 0x00, 0x00 91 | }; 92 | const static MemSig AfterbirthRNGVals = { 93 | AfterbirthRNGValsSig, 94 | "b?bbb?vvvvb?vvvvb?vvvv" 95 | }; 96 | 97 | 98 | 99 | 100 | // 03 C0 | add eax, eax | 101 | // 03 C0 | add eax, eax | 102 | // 2B F0 | sub esi, eax | 103 | // 8B 04 B5 70 D3 B9 00 | mov eax,dword ptr ds:[esi*4+B9D370] | <== Need this address 104 | static unsigned char AfterbirthRNGMapSig[] = 105 | { 106 | 0x03, 0x00, 107 | 0x03, 0x00, 108 | 0x2B, 0x00, 109 | 0x8B, 0x04, 0xB5, 0x00, 0x00, 0x00, 0x00 110 | }; 111 | const static MemSig AfterbirthRNGMap = { 112 | AfterbirthRNGMapSig, 113 | "b?b?b?bbbvvvv" 114 | }; 115 | 116 | #endif //MISSINGHUD2_ISAACMEMSIGNATURES_H 117 | -------------------------------------------------------------------------------- /src/dll/MemReader.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "MemReader.h" 16 | #include "RebirthMemReader.h" 17 | #include "AfterbirthMemReader.h" 18 | 19 | MemReader *MemReader::instance_ = nullptr; 20 | ModuleInfo MemReader::module_info_; 21 | 22 | MemReader *MemReader::GetMemoryReader() 23 | { 24 | if (instance_ == nullptr) 25 | { 26 | if (GetIsaacExpansion() == Expansion::kAfterbirth) 27 | instance_ = new AfterbirthMemReader(); 28 | else 29 | instance_ = new RebirthMemReader(); 30 | } 31 | 32 | return instance_; 33 | } 34 | 35 | void MemReader::Destroy() 36 | { 37 | if (instance_ != nullptr) 38 | delete instance_; 39 | 40 | instance_ = nullptr; 41 | } 42 | 43 | Expansion MemReader::GetIsaacExpansion() 44 | { 45 | std::vector afterbirth_present = SearchMemForVal(AfterbirthCheck); 46 | if (afterbirth_present.size() > 0) 47 | return kAfterbirth; 48 | else 49 | return kRebirth; 50 | } 51 | 52 | ModuleInfo MemReader::GetModuleInfo() 53 | { 54 | if (module_info_.module_size > 0) // Have we already calculated the module information? 55 | return module_info_; 56 | 57 | // Get the base address of the Isaac module 58 | DWORD module_handle = (DWORD)GetModuleHandleW(WCHAR_ISAAC_MODULE_NAME); 59 | MEMORY_BASIC_INFORMATION isaac_mem = {0 }; 60 | if (VirtualQuery((LPVOID)module_handle, &isaac_mem, sizeof(isaac_mem)) == 0) 61 | throw std::runtime_error("Unable to get memory information for Isaac."); 62 | 63 | IMAGE_DOS_HEADER *dos_header = (IMAGE_DOS_HEADER*)module_handle; 64 | IMAGE_NT_HEADERS *pe_header = (IMAGE_NT_HEADERS*)((DWORD)dos_header->e_lfanew + (DWORD) isaac_mem.AllocationBase); 65 | if (dos_header->e_magic != IMAGE_DOS_SIGNATURE || pe_header->Signature != IMAGE_NT_SIGNATURE) 66 | throw std::runtime_error("The Isaac memory being accessed is incorrect."); 67 | 68 | module_info_.module_address = (DWORD) isaac_mem.AllocationBase; 69 | module_info_.module_size = pe_header->OptionalHeader.SizeOfImage; 70 | 71 | std::stringstream ss; 72 | ss << "Isaac module address: 0x" << std::hex << module_info_.module_address; 73 | QUEUE_LOG(mhud2::Log::LOG_INFO, ss.str()); 74 | ss.str(""); ss.clear(); 75 | 76 | ss << "Isaac module size: " << std::hex << module_info_.module_size; 77 | QUEUE_LOG(mhud2::Log::LOG_INFO, ss.str()); 78 | ss.str(""); ss.clear(); 79 | 80 | return module_info_; 81 | } 82 | 83 | std::vector MemReader::SearchMemForVal(MemSig mem_sig) 84 | { 85 | ModuleInfo module_info = GetModuleInfo(); 86 | return SearchMemForVal(module_info, mem_sig); 87 | } 88 | 89 | std::vector MemReader::SearchMemForVal(ModuleInfo module_info, MemSig mem_sig) 90 | { 91 | std::vector val_bytes; 92 | int sig_len = strlen(mem_sig.search_mask); 93 | unsigned char* p_search = (unsigned char*)module_info.module_address; 94 | unsigned char* p_search_end = (unsigned char*)(module_info.module_address + module_info.module_size - sig_len); 95 | 96 | while (p_search <= p_search_end) 97 | { 98 | int matching_bytes = 0; 99 | for (int i = 0; i < sig_len; ++i) 100 | { 101 | if (mem_sig.search_mask[i] != '?' && mem_sig.search_mask[i] != 'v' 102 | && p_search[i] != mem_sig.signature[i]) 103 | break; 104 | ++matching_bytes; 105 | } 106 | 107 | if (matching_bytes == sig_len) 108 | { 109 | // Found the signature, grab the return value bytes 110 | for (int i = 0; i < sig_len; ++i) 111 | { 112 | if (mem_sig.search_mask[i] == 'v') 113 | { 114 | val_bytes.push_back(p_search[i]); 115 | } 116 | } 117 | } 118 | 119 | ++p_search; 120 | } 121 | 122 | return val_bytes; 123 | } 124 | -------------------------------------------------------------------------------- /src/dll/MemReader.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_MEMREADER_H 16 | #define MISSINGHUD2_MEMREADER_H 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include "IsaacMemSignatures.h" 25 | #include "src/MHUD_MsgQueue.h" 26 | 27 | #define WCHAR_ISAAC_MODULE_NAME L"isaac-ng.exe" 28 | 29 | enum Expansion 30 | { 31 | kRebirth, 32 | kAfterbirth 33 | }; 34 | 35 | // These values are the offsets of the specific statistic from the core Player memory address 36 | enum PlayerStat 37 | { 38 | kSpeed, 39 | kRange, 40 | kTearsDelay, 41 | kShotSpeed, 42 | kShotHeight, 43 | kDamage, 44 | kLuck, 45 | kTearsFired, 46 | kDealDoorChance, // An advanced function is required for this statistic 47 | kDealWithDevil, // An advanced function is required for this statistic 48 | kDealWithAngel // An advanced function is required for this statistic 49 | }; 50 | 51 | struct RecentStatChange 52 | { 53 | PlayerStat stat; 54 | float prev_stat_val = 0.0f; 55 | float new_stat_val = 0.0f; 56 | float stat_diff = 0.0f; 57 | std::chrono::time_point time_changed = std::chrono::system_clock::now(); 58 | std::chrono::milliseconds show_timeout = std::chrono::milliseconds(3000); 59 | }; 60 | 61 | struct ModuleInfo 62 | { 63 | DWORD module_address = 0; 64 | DWORD module_size = 0; 65 | }; 66 | 67 | class MemReader 68 | { 69 | public: 70 | static MemReader *GetMemoryReader(); 71 | static void Destroy(); 72 | 73 | // Functions that the expansion-specific memory readers must implement 74 | virtual bool IsRunActive() =0; 75 | virtual float GetPlayerStatf(PlayerStat player_stat) =0; 76 | virtual int GetPlayerStati(PlayerStat player_stat) =0; 77 | virtual float GetPlayerRecentStatChangef(PlayerStat player_stat) =0; 78 | virtual int GetPlayerRecentStatChangei(PlayerStat player_stat) =0; 79 | 80 | // Functions that the expansion-specific memory readers can implement 81 | virtual bool PlayingGreed() { return false; }; 82 | 83 | protected: 84 | static ModuleInfo GetModuleInfo(); 85 | static Expansion GetIsaacExpansion(); 86 | 87 | // Note this function always returns the first signature it finds, so make sure it's unique 88 | static std::vector SearchMemForVal(MemSig mem_sig); 89 | static std::vector SearchMemForVal(ModuleInfo module_info, MemSig mem_sig); 90 | 91 | private: 92 | static MemReader *instance_; 93 | static ModuleInfo module_info_; 94 | }; 95 | 96 | #endif //MISSINGHUD2_MEMREADER_H 97 | -------------------------------------------------------------------------------- /src/dll/RebirthMemReader.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "RebirthMemReader.h" 16 | 17 | RebirthMemReader::RebirthMemReader() 18 | { 19 | GetRebirthModuleInfo(); 20 | } 21 | 22 | RebirthMemReader::~RebirthMemReader() 23 | { 24 | } 25 | 26 | void RebirthMemReader::GetRebirthModuleInfo() 27 | { 28 | // Find the static address pointer of the Rebirth PlayerManager instance 29 | std::vector player_manager_inst_address_p_bytes_ = SearchMemForVal(PlayerManagerInstAddr); 30 | if (player_manager_inst_address_p_bytes_.size() < 4) 31 | throw std::runtime_error("Couldn't find the PlayerManager static instance address"); 32 | player_manager_inst_p_addr_ = *((DWORD*)player_manager_inst_address_p_bytes_.data()); 33 | 34 | std::stringstream ss; 35 | ss << "PlayerManager Instance **: " << std::hex << player_manager_inst_p_addr_; 36 | QUEUE_LOG(mhud2::Log::LOG_INFO, ss.str()); 37 | ss.str(""); ss.clear(); 38 | 39 | // Find the offset of the Players list relative to the PlayerManager instance 40 | *((DWORD*)(PlayerManagerPlayerListOffset.signature + 2)) = player_manager_inst_p_addr_; 41 | std::vector player_manager_player_list_offset_bytes_ = SearchMemForVal(PlayerManagerPlayerListOffset); 42 | if (player_manager_player_list_offset_bytes_.size() < 2) 43 | throw std::runtime_error("Couldn't find the PlayerManager PlayerList offset"); 44 | player_manager_player_list_offset_ = *((WORD*)player_manager_player_list_offset_bytes_.data()); 45 | 46 | ss << "PlayerManager PlayerList offset: " << std::hex << player_manager_player_list_offset_; 47 | QUEUE_LOG(mhud2::Log::LOG_INFO, ss.str()); 48 | ss.str(""); ss.clear(); 49 | } 50 | 51 | bool RebirthMemReader::IsRunActive() 52 | { 53 | // To check whether a run is currently active I check how many players there are (just like Afterbirth actually does) 54 | // When there are 0 players, then we are not in a run 55 | DWORD player_list = GetPlayerListMemAddr(); 56 | if (player_list == 0) 57 | return false; 58 | 59 | int num_players = (int)((*(DWORD*)(player_list + sizeof(DWORD))) - (*(DWORD*) player_list)); 60 | return (num_players > 0); 61 | } 62 | 63 | DWORD RebirthMemReader::GetPlayerManagerMemAddr() 64 | { 65 | DWORD player_manager_inst = *((DWORD*)(player_manager_inst_p_addr_)); 66 | if (player_manager_inst == 0) 67 | return 0; // Player manager hasn't been initialized by Rebirth yet 68 | else 69 | return player_manager_inst; 70 | } 71 | 72 | DWORD RebirthMemReader::GetPlayerListMemAddr() 73 | { 74 | DWORD player_manager_inst = GetPlayerManagerMemAddr(); 75 | if (player_manager_inst == 0) 76 | return 0; 77 | 78 | if (player_manager_player_list_offset_ == 0) 79 | return 0; 80 | 81 | return (player_manager_inst + player_manager_player_list_offset_); 82 | } 83 | 84 | DWORD RebirthMemReader::GetPlayerMemAddr() 85 | { 86 | // Here we follow the Rebirth memory address chain to get 87 | // the current player class associated with the current run 88 | DWORD player_list = GetPlayerListMemAddr(); 89 | if (player_list == 0) 90 | return 0; 91 | 92 | DWORD player_p = *((DWORD*)player_list); 93 | return *((DWORD*)player_p); 94 | } 95 | 96 | float RebirthMemReader::GetPlayerStatf(PlayerStat player_stat) 97 | { 98 | if (player_stat == PlayerStat::kDealDoorChance) 99 | { 100 | float door_chance = GetDealDoorChance(); 101 | SaveStat(PlayerStat::kDealDoorChance, door_chance); 102 | return door_chance; 103 | } 104 | else if (player_stat == PlayerStat::kDealWithDevil) 105 | { 106 | float devil_chance = GetDealWithDevilChance(); 107 | SaveStat(PlayerStat::kDealWithDevil, devil_chance); 108 | return devil_chance; 109 | } 110 | else if (player_stat == PlayerStat::kDealWithAngel) 111 | { 112 | float angel_chance = GetDealWithAngelChance(); 113 | SaveStat(PlayerStat::kDealWithAngel, angel_chance); 114 | return angel_chance; 115 | } 116 | 117 | DWORD player_class = GetPlayerMemAddr(); 118 | if (player_class == 0) 119 | return 0.0f; 120 | 121 | float stat_val; 122 | switch (player_stat) 123 | { 124 | case PlayerStat::kSpeed: 125 | stat_val = *((float*)(player_class + RB_STAT_SPEED)); 126 | break; 127 | 128 | case PlayerStat::kRange: 129 | stat_val = *((float*)(player_class + RB_STAT_RANGE)); 130 | break; 131 | 132 | case PlayerStat::kShotSpeed: 133 | stat_val = *((float*)(player_class + RB_STAT_SHOTSPEED)); 134 | break; 135 | 136 | case PlayerStat::kShotHeight: 137 | stat_val = *((float*)(player_class + RB_STAT_SHOTHEIGHT)); 138 | break; 139 | 140 | case PlayerStat::kDamage: 141 | stat_val = *((float*)(player_class + RB_STAT_DAMAGE)); 142 | break; 143 | 144 | case PlayerStat::kLuck: 145 | stat_val = *((float*)(player_class + RB_STAT_LUCK)); 146 | break; 147 | 148 | default: 149 | stat_val = 0.00f; 150 | } 151 | 152 | if (player_stat == PlayerStat::kRange) 153 | { 154 | stat_val *= -1; // Range is stored as a negative, but we show it positive on the HUD 155 | } 156 | 157 | // Save the stat into our memory 158 | SaveStat(player_stat, stat_val); 159 | return stat_val; 160 | } 161 | 162 | int RebirthMemReader::GetPlayerStati(PlayerStat player_stat) 163 | { 164 | DWORD player_class = GetPlayerMemAddr(); 165 | if (player_class == 0) 166 | return 0; 167 | 168 | int stat_val; 169 | switch (player_stat) 170 | { 171 | case PlayerStat::kTearsDelay: 172 | stat_val = *((int*)(player_class + RB_STAT_TEARS)); 173 | break; 174 | 175 | case PlayerStat::kTearsFired: 176 | stat_val = *((int*)(player_class + RB_STAT_TEARSFIRED)); 177 | break; 178 | 179 | default: 180 | stat_val = 0; 181 | } 182 | 183 | // Save the stat into our memory 184 | SaveStat(player_stat, (float)stat_val); 185 | return stat_val; 186 | } 187 | 188 | float RebirthMemReader::GetPlayerRecentStatChangef(PlayerStat player_stat) 189 | { 190 | float recent_stat_change = 0.0f; 191 | 192 | if (stat_change_.count(player_stat) > 0) 193 | { 194 | if (stat_change_[player_stat].time_changed > (std::chrono::system_clock::now() - stat_change_[player_stat].show_timeout)) 195 | { 196 | return stat_change_[player_stat].stat_diff; 197 | } 198 | } 199 | 200 | return recent_stat_change; 201 | } 202 | 203 | int RebirthMemReader::GetPlayerRecentStatChangei(PlayerStat player_stat) 204 | { 205 | int recent_stat_change = 0; 206 | 207 | if (stat_change_.count(player_stat) > 0) 208 | { 209 | if (stat_change_[player_stat].time_changed > (std::chrono::system_clock::now() - stat_change_[player_stat].show_timeout)) 210 | { 211 | return (int)stat_change_[player_stat].stat_diff; 212 | } 213 | } 214 | 215 | return recent_stat_change; 216 | } 217 | 218 | void RebirthMemReader::SaveStat(PlayerStat player_stat, float stat_val) 219 | { 220 | // If the stat is different from the one in our memory, note it 221 | if (stat_change_.count(player_stat) > 0) 222 | { 223 | if (stat_change_[player_stat].new_stat_val != stat_val) 224 | { 225 | stat_change_[player_stat].prev_stat_val = stat_change_[player_stat].new_stat_val; 226 | stat_change_[player_stat].new_stat_val = stat_val; 227 | stat_change_[player_stat].stat_diff = stat_change_[player_stat].new_stat_val - stat_change_[player_stat].prev_stat_val; 228 | stat_change_[player_stat].time_changed = std::chrono::system_clock::now(); 229 | } 230 | } 231 | else 232 | { 233 | RecentStatChange new_stat_change; 234 | new_stat_change.stat = player_stat; 235 | new_stat_change.prev_stat_val = stat_val; 236 | new_stat_change.new_stat_val = stat_val; 237 | new_stat_change.time_changed = new_stat_change.time_changed - new_stat_change.show_timeout; // We don't want the initial 0->X change to show 238 | stat_change_[player_stat] = new_stat_change; 239 | } 240 | } 241 | 242 | float RebirthMemReader::GetDealWithDevilChance() 243 | { 244 | return GetDealDoorChance() * (1.00f - GetDealWithAngelMultiplier()); 245 | } 246 | 247 | float RebirthMemReader::GetDealWithAngelChance() 248 | { 249 | return GetDealDoorChance() * GetDealWithAngelMultiplier(); 250 | } 251 | 252 | float RebirthMemReader::GetDealDoorChance() 253 | { 254 | DWORD player_manager_inst = GetPlayerManagerMemAddr(); 255 | if (player_manager_inst == 0) 256 | return 0.0f; 257 | 258 | int current_floor = *((int*)player_manager_inst); 259 | int labyrinth_flag = *((int*)((DWORD)player_manager_inst + RB_PLAYER_MANAGER_CURSE_FLAGS)); // Need to take into account whether the floor is a labyrinth cursed floor 260 | current_floor_ = current_floor; 261 | if (labyrinth_flag == RB_LABYRINTH_CURSE) 262 | ++current_floor_; 263 | if (current_floor_ == 1 || current_floor_ > 8) // In-eligible for natural deal on these floors (even with Goat Head) 264 | return 0.0f; 265 | 266 | DWORD player = GetPlayerMemAddr(); 267 | if (PlayerHasItem(RB_PASSIVE_ITEM_GOATHEAD)) // Goat Head is a guaranteed deal (100%) 268 | return 1.0f; 269 | 270 | float deal_chance = 0.01f; // Default 1% chance 271 | 272 | if (PlayerHasItem(RB_PASSIVE_ITEM_PENTAGRAM)) // Pentagram adds 20% chance 273 | deal_chance += 0.20f; 274 | 275 | if (*((DWORD*)(player + RB_PASSIVE_ITEM_PENTAGRAM)) > 1) // Having more than one pentagram adds another 10% chance 276 | deal_chance += 0.10f; 277 | 278 | if (PlayerHasItem(RB_PASSIVE_ITEM_BLACKCANDLE)) // Black Candle adds 30% chance 279 | deal_chance += 0.30f; 280 | 281 | DWORD player_active_item = *((DWORD*)(player + RB_ITEM_ACTIVE_SLOT)); 282 | if (player_active_item == RB_ACTIVE_ITEM_BOOKOFREVELATIONS) // Holding Book of Revelations adds 35% chance 283 | deal_chance += 0.35f; 284 | else if (player_active_item == RB_ACTIVE_ITEM_BOOKOFBELIAL) // Holding Book of Belial adds 2500% chance 285 | deal_chance += 25.00f; 286 | 287 | BYTE floor_flag = *((BYTE*)(player_manager_inst + RB_PLAYER_MANAGER_FLOOR_FLAGS)); 288 | if (floor_flag & 1) // Killing a normal beggar adds 35% chance 289 | deal_chance += 0.35f; 290 | 291 | DWORD floor_flags = *((DWORD*)(player_manager_inst + RB_PLAYER_MANAGER_FLOOR_FLAGS)); 292 | if (((floor_flags >> 2) & 1) <= 0) // Not taking red heart damage on the entire floor adds 99% chance 293 | deal_chance += 0.99f; 294 | if (((floor_flags >> 6) & 1) > 0) // Blowing up a dead shopkeeper adds 10% chance 295 | deal_chance += 0.10f; 296 | 297 | // Not taking damage from the floor's boss room adds 35% chance to deal chance 298 | DWORD current_room = GetCurrentRoom(); 299 | DWORD boss_room = *((DWORD*)(player_manager_inst + RB_PLAYER_MANAGER_BOSS_ROOM_CODE)); 300 | if (current_room == boss_room) 301 | { 302 | DWORD boss_fight = *((DWORD*)(player_manager_inst + RB_PLAYER_MANAGER_FLOOR_BOSS_FIGHT)); 303 | BYTE boss_dmg_flag = *((BYTE*)(boss_fight + RB_BOSS_FIGHT_TOOK_RED_DMG)); 304 | if (boss_dmg_flag == 1) 305 | boss_fight_took_dmg_ = true; 306 | } 307 | else 308 | { 309 | // This works to replicate how Rebirth handles boss fight damage. 310 | // On the first roll of the deal chance it takes into account whether you took damage or not. 311 | // However, subsequent rolls (to keep the door open) ALWAYS add the +35% chance, 312 | // regardless of whether you took boss damage or not. 313 | if (boss_fight_took_dmg_) 314 | boss_fight_took_dmg_ = false; 315 | } 316 | 317 | if (!boss_fight_took_dmg_) // Not taking damage from the boss fight adds 35% chance 318 | deal_chance += 0.35f; 319 | 320 | DWORD deal_prev_floor = *((DWORD*)(player_manager_inst + RB_PLAYER_MANAGER_DEAL_PREV_FLOOR)); 321 | if (deal_prev_floor > 0) 322 | { 323 | int deal_num_floors_ago = current_floor - (int) deal_prev_floor; // Rebirth uses the non-labyrinthed current_floor value 324 | if (deal_num_floors_ago < 2) 325 | { 326 | deal_chance *= 0.25f; // Player has seen a Deal door less than 2 floors ago, reduce overall chance by 75% 327 | } 328 | else if (deal_num_floors_ago == 2) 329 | { 330 | deal_chance *= 0.5f; // Player has seen a Deal door 2 floors ago, reduce overall chance by 50% 331 | } 332 | } 333 | 334 | if (deal_chance > 1.00f) 335 | deal_chance = 1.00f; 336 | 337 | return deal_chance; 338 | } 339 | 340 | DWORD RebirthMemReader::GetCurrentRoom() 341 | { 342 | DWORD player_manager_inst = GetPlayerManagerMemAddr(); 343 | if (player_manager_inst == 0) 344 | return 0; 345 | 346 | DWORD room_code = *((DWORD*)(player_manager_inst + RB_PLAYER_MANAGER_ROOM_CODE)); 347 | DWORD room_num = *((DWORD*)(player_manager_inst + (room_code * 4) + RB_PLAYER_MANAGER_ROOM_CODE_FORMULA_OFFSET)); 348 | return room_num; 349 | } 350 | 351 | float RebirthMemReader::GetDealWithAngelMultiplier() 352 | { 353 | // If you haven't seen a devil deal yet, or you have taken a devil deal via health payment 354 | // you can't receive an Angel room 355 | DWORD player_manager_inst = GetPlayerManagerMemAddr(); 356 | if (player_manager_inst == 0) 357 | return 0.0f; 358 | 359 | DWORD seen_devil = *((DWORD*)(player_manager_inst + RB_PLAYER_MANAGER_SEEN_DEVIL)); 360 | if (((seen_devil & 0x20) | 0) == 0) 361 | return 0.0f; // Haven't seen a Devil room, can't get Angel room yet 362 | 363 | if (*((DWORD*)(player_manager_inst + RB_PLAYER_MANAGER_PAID_DEVIL)) != 0) 364 | return 0.0f; // Paid for a Devil deal with red health, can't get Angel room 365 | 366 | float angel_chance = 0.50f; // Default chance to replace Devil room with Angel room is 50% 367 | 368 | DWORD player = GetPlayerMemAddr(); 369 | if (PlayerHasItem(RB_PASSIVE_ITEM_KEYPIECE_1)) 370 | angel_chance = angel_chance + ((1.00f - angel_chance) * 0.25f); // Having Key Piece #1 gives a 25% chance roll 371 | 372 | if (PlayerHasItem(RB_PASSIVE_ITEM_KEYPIECE_2)) 373 | angel_chance = angel_chance + ((1.00f - angel_chance) * 0.25f); // Having Key Piece #2 gives a 25% chance roll 374 | 375 | if (PlayerHasTrinket(RB_PASSIVE_TRINKET_ROSARYBEAD)) 376 | angel_chance = angel_chance + ((1.00f - angel_chance) * 0.50f); // Holding the Rosary Bead trinket gives 50% chance roll 377 | 378 | if (*((DWORD*)(player_manager_inst + RB_PLAYER_MANAGER_FLOOR_DONATIONS)) >= 10) 379 | angel_chance = angel_chance + ((1.00f - angel_chance) * 0.50f); // Donating 10 or more coins on a floor gives 50% chance roll 380 | 381 | DWORD floor_flags = *((DWORD*)(player_manager_inst + RB_PLAYER_MANAGER_FLOOR_FLAGS)); 382 | if (((floor_flags >> 1) & 0xff) & 1 || ((floor_flags >> 3) & 0xff) & 1 || ((floor_flags >> 4) & 0xff) & 1) 383 | angel_chance = angel_chance + ((1.00f - angel_chance) * 0.25f); // Devil Beggar / Shell Beggar / Key Beggar blown up 384 | // Gives 25% chance roll 385 | 386 | return angel_chance; 387 | } 388 | 389 | bool RebirthMemReader::PlayerHasItem(DWORD item_offset) 390 | { 391 | DWORD player = GetPlayerMemAddr(); 392 | return (*((DWORD*)(player + item_offset)) != 0); 393 | } 394 | 395 | bool RebirthMemReader::PlayerHasTrinket(int trinket_id) 396 | { 397 | int num_trinkets = 1; 398 | if (PlayerHasItem(RB_PASSIVE_ITEM_MUMS_PURSE)) 399 | num_trinkets++; // Mum's Purse lets a player have 2 possible trinkets 400 | 401 | DWORD player = GetPlayerMemAddr(); 402 | DWORD trinket_offset = 0; 403 | for (int i = 0; i < num_trinkets; ++i) 404 | { 405 | DWORD trinket_flag = *((DWORD*)(player + RB_PLAYER_HAS_TRINKET_OFFSET + trinket_offset)); 406 | if (trinket_flag == trinket_id) 407 | return true; 408 | 409 | trinket_offset += 0x4; 410 | } 411 | 412 | // Player clearly doesn't have that trinket! 413 | return false; 414 | } 415 | -------------------------------------------------------------------------------- /src/dll/RebirthMemReader.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_REBIRTHMEMREADER_H 16 | #define MISSINGHUD2_REBIRTHMEMREADER_H 17 | 18 | #include "MemReader.h" 19 | 20 | #define RB_ITEM_ACTIVE_SLOT 0xCC8 21 | #define RB_ACTIVE_ITEM_BOOKOFREVELATIONS 0x4E 22 | #define RB_ACTIVE_ITEM_BOOKOFBELIAL 0x22 23 | 24 | #define RB_PASSIVE_ITEM_PENTAGRAM 0xE38 25 | #define RB_PASSIVE_ITEM_GOATHEAD 0x10C8 26 | #define RB_PASSIVE_ITEM_BLACKCANDLE 0x117C 27 | #define RB_PASSIVE_ITEM_KEYPIECE_1 0x1124 28 | #define RB_PASSIVE_ITEM_KEYPIECE_2 0x1128 29 | #define RB_PASSIVE_ITEM_MUMS_PURSE 0xF98 30 | 31 | #define RB_PLAYER_HAS_TRINKET_OFFSET 0xD64 32 | #define RB_PASSIVE_TRINKET_ROSARYBEAD 0x7 33 | 34 | #define RB_PLAYER_MANAGER_CURSE_FLAGS 0x8 35 | #define RB_PLAYER_MANAGER_FLOOR_FLAGS 0x5DC0 36 | #define RB_PLAYER_MANAGER_BOSS_ROOM_CODE 0x5DA4 37 | #define RB_PLAYER_MANAGER_ROOM_CODE 0x5D9C 38 | #define RB_PLAYER_MANAGER_ROOM_CODE_FORMULA_OFFSET 0x5AC4 39 | #define RB_PLAYER_MANAGER_FLOOR_BOSS_FIGHT 0x5D98 40 | #define RB_PLAYER_MANAGER_DEAL_PREV_FLOOR 0x10D7E8 41 | #define RB_PLAYER_MANAGER_FLOOR_DONATIONS 0x10D800 42 | #define RB_PLAYER_MANAGER_SEEN_DEVIL 0x10D7E0 43 | #define RB_PLAYER_MANAGER_PAID_DEVIL 0x10D7F8 44 | 45 | #define RB_LABYRINTH_CURSE 0x2 46 | #define RB_BOSS_FIGHT_TOOK_RED_DMG 0xE8C 47 | 48 | #define RB_STAT_SPEED 0xCB4 49 | #define RB_STAT_RANGE 0xBF4 50 | #define RB_STAT_TEARS 0xBE0 51 | #define RB_STAT_SHOTSPEED 0xBE4 52 | #define RB_STAT_SHOTHEIGHT 0xBF8 53 | #define RB_STAT_DAMAGE 0xBF0 54 | #define RB_STAT_LUCK 0xCB8 55 | #define RB_STAT_TEARSFIRED 0xBEC 56 | 57 | class RebirthMemReader : public MemReader 58 | { 59 | public: 60 | RebirthMemReader(); 61 | ~RebirthMemReader(); 62 | 63 | bool IsRunActive(); 64 | 65 | float GetPlayerStatf(PlayerStat player_stat); 66 | int GetPlayerStati(PlayerStat player_stat); 67 | 68 | float GetPlayerRecentStatChangef(PlayerStat player_stat); 69 | int GetPlayerRecentStatChangei(PlayerStat player_stat); 70 | 71 | private: 72 | void GetRebirthModuleInfo(); 73 | 74 | DWORD GetPlayerManagerMemAddr(); 75 | DWORD GetPlayerListMemAddr(); 76 | DWORD GetPlayerMemAddr(); 77 | 78 | float GetDealDoorChance(); 79 | float GetDealWithDevilChance(); 80 | float GetDealWithAngelChance(); 81 | float GetDealWithAngelMultiplier(); 82 | DWORD GetCurrentRoom(); 83 | 84 | bool PlayerHasItem(DWORD item_offset); 85 | bool PlayerHasTrinket(int trinket_id); 86 | 87 | void SaveStat(PlayerStat player_stat, float stat_val); 88 | 89 | private: 90 | DWORD player_manager_inst_p_addr_ = 0; 91 | DWORD player_manager_player_list_offset_ = 0; 92 | 93 | int current_floor_ = 0; 94 | bool boss_fight_took_dmg_ = false; 95 | std::map stat_change_; 96 | }; 97 | 98 | 99 | #endif //MISSINGHUD2_REBIRTHMEMREADER_H 100 | -------------------------------------------------------------------------------- /src/dll/ResourceLoader.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "ResourceLoader.h" 16 | 17 | ResourceLoader *ResourceLoader::instance_ = nullptr; 18 | 19 | void ResourceLoader::Initialize(HMODULE module) 20 | { 21 | if (instance_ == nullptr) 22 | instance_ = new ResourceLoader(module); 23 | } 24 | 25 | 26 | void ResourceLoader::Destroy() { 27 | if (instance_) 28 | delete instance_; 29 | 30 | instance_ = nullptr; 31 | } 32 | 33 | ResourceLoader::ResourceLoader(HMODULE module) 34 | { 35 | module_ = module; 36 | } 37 | 38 | FileResource ResourceLoader::GetBinResource(RESID res_id) 39 | { 40 | if (!instance_) 41 | return FileResource(); 42 | 43 | return instance_->LoadBinResource(res_id); 44 | } 45 | 46 | FileResource ResourceLoader::LoadBinResource(RESID res_id) 47 | { 48 | if (loaded_resources_.count(res_id) > 0) 49 | return loaded_resources_[res_id]; 50 | 51 | // Need to load resource 52 | HRSRC res_loc = FindResource(module_, MAKEINTRESOURCE(res_id), RT_RCDATA); 53 | if (res_loc == NULL) 54 | return FileResource(); 55 | 56 | HGLOBAL h_rec = LoadResource(module_, res_loc); 57 | if (h_rec == NULL) 58 | return FileResource(); 59 | 60 | FileResource res_file = { 61 | (LPVOID)LockResource(h_rec), 62 | SizeofResource(module_, res_loc) 63 | }; 64 | 65 | // Add the resource to our loaded resource's list 66 | loaded_resources_.insert(std::make_pair(res_id, res_file)); 67 | return res_file; 68 | } -------------------------------------------------------------------------------- /src/dll/ResourceLoader.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_RESOURCELOADER_H 16 | #define MISSINGHUD2_RESOURCELOADER_H 17 | 18 | #include 19 | 20 | #include 21 | 22 | typedef int RESID; 23 | 24 | struct FileResource 25 | { 26 | FileResource() { }; 27 | FileResource(LPVOID _bin_data, DWORD _res_size) 28 | { 29 | bin_data = _bin_data; 30 | res_size = _res_size; 31 | } 32 | 33 | LPVOID bin_data = nullptr; 34 | DWORD res_size = 0; 35 | }; 36 | 37 | class ResourceLoader 38 | { 39 | public: 40 | static void Initialize(HMODULE hmodule); 41 | static void Destroy(); 42 | 43 | static FileResource GetBinResource(RESID res_id); 44 | 45 | private: 46 | ResourceLoader(HMODULE module); 47 | 48 | FileResource LoadBinResource(RESID res_id); 49 | 50 | private: 51 | static ResourceLoader *instance_; 52 | 53 | HMODULE module_ = NULL; 54 | std::map loaded_resources_; 55 | }; 56 | 57 | #endif //MISSINGHUD2_RESOURCELOADER_H 58 | -------------------------------------------------------------------------------- /src/dll/ShaderProgram.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "ShaderProgram.h" 16 | 17 | std::map ShaderProgram::instances_; 18 | 19 | ShaderProgram::ShaderProgram() 20 | { 21 | 22 | } 23 | 24 | ShaderProgram *ShaderProgram::GetProgram(int vertex_res, int fragment_res) 25 | { 26 | // Check whether we already have the program compiled 27 | PROGFILES prog_files = vertex_res + fragment_res; 28 | if (instances_.count(prog_files) > 0) 29 | return instances_[prog_files]; 30 | 31 | // Nope, we don't have it cached, gotta compile and link the program 32 | ShaderProgram *shader_program = new ShaderProgram(); 33 | if (!shader_program->CompileProgram(vertex_res, fragment_res)) 34 | return nullptr; 35 | 36 | // Save the PROGFILES for this ShaderProgram 37 | instances_.insert(std::make_pair(prog_files, shader_program)); 38 | 39 | return shader_program; 40 | } 41 | 42 | void ShaderProgram::DestroyAll() 43 | { 44 | for (auto &shader_program : instances_) 45 | { 46 | delete shader_program.second; 47 | } 48 | instances_.clear(); 49 | } 50 | 51 | bool ShaderProgram::CompileProgram(int vertex_res, int fragment_res) 52 | { 53 | // Compile the vertex shader 54 | FileResource vertex_fileres = ResourceLoader::GetBinResource(vertex_res); 55 | GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); 56 | glShaderSource(vertex_shader, 1, (char**)&vertex_fileres.bin_data, NULL); 57 | glCompileShader(vertex_shader); 58 | 59 | // Make sure vertex shader compiled successfully 60 | GLint compile_result; 61 | GLchar info_log[512] = { '\0' }; 62 | glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &compile_result); 63 | if (!compile_result) 64 | { 65 | glGetShaderInfoLog(vertex_shader, 512, NULL, info_log); 66 | OutputDebugStringA(info_log); 67 | return false; 68 | } 69 | 70 | // Compile the fragment shader 71 | FileResource fragment_fileres = ResourceLoader::GetBinResource(fragment_res); 72 | GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); 73 | glShaderSource(fragment_shader, 1, (char**)&fragment_fileres.bin_data, NULL); 74 | glCompileShader(fragment_shader); 75 | 76 | // Make sure fragment shader compiled successfully 77 | compile_result = 0; 78 | glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &compile_result); 79 | if (!compile_result) 80 | { 81 | glGetShaderInfoLog(fragment_shader, 512, NULL, info_log); 82 | OutputDebugStringA(info_log); 83 | return false; 84 | } 85 | 86 | // Link our shaders 87 | compiled_prog_id_ = glCreateProgram(); 88 | glAttachShader(compiled_prog_id_, vertex_shader); 89 | glAttachShader(compiled_prog_id_, fragment_shader); 90 | glLinkProgram(compiled_prog_id_); 91 | 92 | // Make sure link was successful 93 | GLint link_result; 94 | glGetProgramiv(compiled_prog_id_, GL_LINK_STATUS, &link_result); 95 | if (!link_result) 96 | { 97 | glGetProgramInfoLog(compiled_prog_id_, 512, NULL, info_log); 98 | OutputDebugStringA(info_log); 99 | return false; 100 | } 101 | 102 | // Clean-up our shaders 103 | glDeleteShader(vertex_shader); 104 | glDeleteShader(fragment_shader); 105 | return true; 106 | } 107 | -------------------------------------------------------------------------------- /src/dll/ShaderProgram.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_SHADERPROGRAM_H 16 | #define MISSINGHUD2_SHADERPROGRAM_H 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include "ResourceLoader.h" 24 | 25 | typedef int PROGFILES; 26 | 27 | class ShaderProgram 28 | { 29 | public: 30 | static ShaderProgram* GetProgram(int vertex_res, int fragment_res); 31 | static void DestroyAll(); 32 | 33 | void Use() 34 | { 35 | glUseProgram(compiled_prog_id_); 36 | }; 37 | 38 | void CloseProgram() 39 | { 40 | glUseProgram(0); 41 | }; 42 | 43 | GLuint GetProgID() 44 | { 45 | return compiled_prog_id_; 46 | }; 47 | 48 | private: 49 | ShaderProgram(); 50 | 51 | bool CompileProgram(int vertex_res, int fragment_res); 52 | 53 | private: 54 | static std::map instances_; 55 | 56 | GLuint compiled_prog_id_ = 0; 57 | }; 58 | 59 | #endif //MISSINGHUD2_SHADERPROGRAM_H 60 | -------------------------------------------------------------------------------- /src/dll/SpriteSheet.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "SpriteSheet.h" 16 | 17 | std::map SpriteSheet::spritesheets_; 18 | 19 | SpriteSheet *SpriteSheet::LoadSpriteSheet(RESID spritesheet_res_id, RESID spritesheet_tex_map, SPRITESIZE sprite_size, 20 | bool invert_img_y) 21 | { 22 | if (spritesheets_.count(spritesheet_res_id) > 0) 23 | { 24 | return spritesheets_[spritesheet_res_id]; 25 | } 26 | 27 | // Create the spritesheet 28 | SpriteSheet* new_spritesheet = new SpriteSheet(spritesheet_res_id, spritesheet_tex_map, sprite_size, invert_img_y); 29 | 30 | // Add the spritesheet to our loaded list 31 | spritesheets_.insert(std::make_pair(spritesheet_res_id, new_spritesheet)); 32 | return new_spritesheet; 33 | } 34 | 35 | SpriteSheet *SpriteSheet::GetSpriteSheet(RESID spritesheet_res_id) 36 | { 37 | if (spritesheets_.count(spritesheet_res_id) == 0) 38 | { 39 | return nullptr; 40 | } 41 | 42 | return spritesheets_[spritesheet_res_id]; 43 | } 44 | 45 | void SpriteSheet::DestroySpriteSheet(RESID spritesheet_res_id) 46 | { 47 | for (auto &spritesheet : spritesheets_) 48 | { 49 | if (spritesheet.first == spritesheet_res_id) 50 | { 51 | delete spritesheet.second; 52 | spritesheets_.erase(spritesheet.first); 53 | break; 54 | } 55 | } 56 | } 57 | 58 | void SpriteSheet::DestroyAllSpriteSheets() 59 | { 60 | for (auto &spritesheet : spritesheets_) 61 | { 62 | delete spritesheet.second; 63 | } 64 | spritesheets_.clear(); 65 | } 66 | 67 | SpriteSheet::SpriteSheet(RESID spritesheet_res_id, RESID spritesheet_tex_map, SPRITESIZE sprite_size, bool invert_img_y) 68 | { 69 | LoadTexture(spritesheet_res_id, invert_img_y); 70 | MapSpriteTextures(spritesheet_tex_map, sprite_size); 71 | 72 | // Generate the buffers we use to draw the 2D sprites 73 | glGenBuffers(1, &array_buffer_); 74 | glGenBuffers(1, &ele_array_buffer_); 75 | } 76 | 77 | SpriteSheet::~SpriteSheet() 78 | { 79 | glDeleteTextures(1, &spritesheet_texture_); 80 | glDeleteBuffers(1, &array_buffer_); 81 | glDeleteBuffers(1, &ele_array_buffer_); 82 | } 83 | 84 | void SpriteSheet::LoadTexture(RESID spritesheet_res_id, bool invert_img_y) 85 | { 86 | // Load the sprite sheet texture 87 | FileResource spritesheet_res = ResourceLoader::GetBinResource(spritesheet_res_id); 88 | int width = 0, height = 0, channels = 0; 89 | unsigned char* texture_img = SOIL_load_image_from_memory((unsigned char*)spritesheet_res.bin_data, 90 | spritesheet_res.res_size, &width, &height, &channels, 91 | SOIL_LOAD_RGBA); 92 | tex_width_ = width; 93 | tex_height_ = height; 94 | 95 | if (invert_img_y) 96 | { 97 | unsigned char* inverted_image = InvertImage(texture_img, width, height, channels); 98 | SOIL_free_image_data(texture_img); 99 | texture_img = inverted_image; 100 | } 101 | 102 | glGenTextures(1, &spritesheet_texture_); 103 | glBindTexture(GL_TEXTURE_2D, spritesheet_texture_); 104 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width_, tex_height_, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture_img); 105 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 106 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 107 | glBindTexture(GL_TEXTURE_2D, 0); 108 | 109 | if (invert_img_y) 110 | delete[] texture_img; 111 | else 112 | SOIL_free_image_data(texture_img); 113 | } 114 | 115 | void SpriteSheet::MapSpriteTextures(RESID spritesheet_tex_map, SPRITESIZE sprite_size) 116 | { 117 | FileResource tex_map = ResourceLoader::GetBinResource(spritesheet_tex_map); 118 | std::string tex_map_str = std::string((const char*)tex_map.bin_data); 119 | std::vector tex_map_array = ExplodeTexMap(tex_map_str); 120 | 121 | int num_cols = tex_width_ / sprite_size.width; 122 | sprite_tex_width_ = 1.0f / num_cols; 123 | int num_rows = tex_height_ / sprite_size.height; 124 | sprite_tex_height_ = 1.0f / num_rows; 125 | 126 | for (int tex = 0; tex < tex_map_array.size(); ++tex) 127 | { 128 | sprite_index_.insert(std::make_pair(tex_map_array[tex], tex)); 129 | } 130 | } 131 | 132 | void SpriteSheet::DrawSprite(glm::vec2 position, glm::vec2 size, std::string sprite_name, glm::vec3 sprite_color) 133 | { 134 | // Get the GLSL shaders required 135 | ShaderProgram *spritesheet_shader = ShaderProgram::GetProgram(OPENGL_HUD_SPRITE_VERTEX_SHADER, 136 | OPENGL_HUD_SPRITE_FRAG_SHADER); 137 | if (spritesheet_shader == nullptr) 138 | return; 139 | 140 | // Get the sprites index in the spritesheet 141 | int sprite_index = sprite_index_[sprite_name]; 142 | float tex_top_left_x = sprite_tex_width_ * sprite_index; 143 | float tex_top_left_y = sprite_tex_height_; 144 | GLfloat gl_vert_buff[] = { 145 | // Position // Texture 146 | (GLfloat)position.x + size.x, (GLfloat)position.y, tex_top_left_x + sprite_tex_width_, tex_top_left_y, // Top-right 147 | (GLfloat)position.x + size.x, (GLfloat)position.y - size.y, tex_top_left_x + sprite_tex_width_, tex_top_left_y - sprite_tex_height_, // Bottom-right 148 | (GLfloat)position.x, (GLfloat)position.y - size.y, tex_top_left_x, tex_top_left_y - sprite_tex_height_, // Bottom-left 149 | (GLfloat)position.x, (GLfloat)position.y, tex_top_left_x, tex_top_left_y // Top-left 150 | }; 151 | GLuint gl_ele_buff[] = { 152 | 1, 0, 3, 153 | 1, 3, 2 154 | }; 155 | 156 | // Actually draw the sprite 157 | spritesheet_shader->Use(); 158 | 159 | // Handle different aspect ratios 160 | VIEWSIZE vp_size = HUDOverlay::GetInstance()->GetViewportSize(); 161 | glm::mat4 projection = glm::ortho(0.0f, (float)vp_size.width, 0.0f, (float)vp_size.height); 162 | glUniformMatrix4fv(glGetUniformLocation(spritesheet_shader->GetProgID(), "projection"), 1, GL_FALSE, 163 | glm::value_ptr(projection)); 164 | 165 | // Handle color 166 | glUniform3f(glGetUniformLocation(spritesheet_shader->GetProgID(), "texture_color"), sprite_color.r, sprite_color.g, sprite_color.b); 167 | glBindTexture(GL_TEXTURE_2D, spritesheet_texture_); 168 | 169 | // Generate the vertex buffers for the sprite 170 | glBindBuffer(GL_ARRAY_BUFFER, array_buffer_); 171 | glBufferData(GL_ARRAY_BUFFER, sizeof(gl_vert_buff), gl_vert_buff, GL_DYNAMIC_DRAW); 172 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ele_array_buffer_); 173 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(gl_ele_buff), gl_ele_buff, GL_STATIC_DRAW); 174 | 175 | // Position attribute 176 | GLint pos_loc = glGetAttribLocation(spritesheet_shader->GetProgID(), "position"); 177 | glEnableVertexAttribArray(pos_loc); 178 | glVertexAttribPointer(pos_loc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0); 179 | 180 | // Texture coord attribute 181 | GLint tex_loc = glGetAttribLocation(spritesheet_shader->GetProgID(), "texture_coord"); 182 | glVertexAttribPointer(tex_loc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat))); 183 | glEnableVertexAttribArray(tex_loc); 184 | 185 | glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); 186 | 187 | // Clean-up OpenGL bindings 188 | glDisableVertexAttribArray(0); 189 | glDisableVertexAttribArray(1); 190 | glBindBuffer(GL_ARRAY_BUFFER, 0); 191 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); 192 | glBindTexture(GL_TEXTURE_2D, 0); 193 | spritesheet_shader->CloseProgram(); 194 | } 195 | 196 | std::vector SpriteSheet::ExplodeTexMap(const std::string &s) 197 | { 198 | std::vector exploded_tex_map; 199 | std::istringstream ss(s); 200 | 201 | std::string tex_name; 202 | while (std::getline(ss, tex_name, ' ')) 203 | { 204 | exploded_tex_map.push_back(tex_name); 205 | } 206 | 207 | return exploded_tex_map; 208 | } 209 | 210 | unsigned char *SpriteSheet::InvertImage(unsigned char *soil_image_data, int width, int height, int channels) 211 | { 212 | unsigned char *texture_img_copy = new unsigned char[width * height * channels]{ '\0' }; 213 | memcpy(texture_img_copy, soil_image_data, width * height * channels); 214 | 215 | for (int j = 0; (j * 2) < height; ++j) 216 | { 217 | int index_1 = j * width * channels; 218 | int index_2 = (height - 1 - j) * width * channels; 219 | for (int i = width * channels; i > 0; --i) 220 | { 221 | unsigned char temp = texture_img_copy[index_1]; 222 | texture_img_copy[index_1] = texture_img_copy[index_2]; 223 | texture_img_copy[index_2] = temp; 224 | ++index_1; 225 | ++index_2; 226 | } 227 | } 228 | 229 | return texture_img_copy; 230 | } 231 | -------------------------------------------------------------------------------- /src/dll/SpriteSheet.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_SPRITESHEET_H 16 | #define MISSINGHUD2_SPRITESHEET_H 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "GLStructs.h" 29 | #include "ResourceLoader.h" 30 | #include "ShaderProgram.h" 31 | #include "HUDOverlay.h" 32 | #include 33 | 34 | class SpriteSheet 35 | { 36 | public: 37 | static SpriteSheet *LoadSpriteSheet(RESID spritesheet_res_id, RESID spritesheet_tex_map, SPRITESIZE sprite_size, 38 | bool invert_img_y); 39 | static SpriteSheet *GetSpriteSheet(RESID spritesheet_res_id); 40 | static void DestroySpriteSheet(RESID spritesheet_res_id); 41 | static void DestroyAllSpriteSheets(); 42 | 43 | void DrawSprite(glm::vec2 position, glm::vec2 size, std::string sprite_name, 44 | glm::vec3 sprite_color = glm::vec3(1.0f, 1.0f, 1.0f)); 45 | 46 | private: 47 | SpriteSheet(RESID spritesheet_res_id, RESID spritesheet_tex_map, SPRITESIZE sprite_size, bool invert_img_y); 48 | ~SpriteSheet(); 49 | 50 | void LoadTexture(RESID spritesheet_res_id, bool invert_img_y); 51 | void MapSpriteTextures(RESID spritesheet_tex_map, SPRITESIZE sprite_size); 52 | 53 | unsigned char *InvertImage(unsigned char *soil_image_data, int width, int height, int channels); 54 | std::vector ExplodeTexMap(const std::string &s); 55 | 56 | private: 57 | static std::map spritesheets_; 58 | 59 | GLuint spritesheet_texture_ = 0; 60 | GLuint array_buffer_ = 0, ele_array_buffer_ = 0; 61 | int tex_height_ = 0; 62 | int tex_width_ = 0; 63 | 64 | std::map sprite_index_; 65 | float sprite_tex_width_ = 0.0; 66 | float sprite_tex_height_ = 0.0; 67 | }; 68 | 69 | 70 | #endif //MISSINGHUD2_SPRITESHEET_H 71 | -------------------------------------------------------------------------------- /src/dll/TextRenderer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "TextRenderer.h" 16 | 17 | std::map TextRenderer::renderers_; 18 | 19 | TextRenderer *TextRenderer::GetRenderer(RESID font_res_id, RESID font_charmap) 20 | { 21 | if (renderers_.count(font_res_id) > 0) 22 | { 23 | return renderers_[font_res_id]; 24 | } 25 | 26 | // Create the renderer 27 | TextRenderer *text_renderer = new TextRenderer(font_res_id, font_charmap); 28 | 29 | // Add it to our map list 30 | renderers_.insert(std::make_pair(font_res_id, text_renderer)); 31 | return text_renderer; 32 | } 33 | 34 | void TextRenderer::DestroyAll() 35 | { 36 | for (auto &renderer : renderers_) 37 | { 38 | delete renderer.second; 39 | } 40 | 41 | renderers_.clear(); 42 | } 43 | 44 | TextRenderer::TextRenderer(RESID font_res_id, RESID font_charmap) 45 | { 46 | res_id_ = font_res_id; 47 | SpriteSheet::LoadSpriteSheet(font_res_id, font_charmap, SPRITESIZE(16, 16), true); 48 | } 49 | 50 | TextRenderer::~TextRenderer() 51 | { 52 | SpriteSheet::DestroySpriteSheet(res_id_); 53 | } 54 | 55 | glm::vec2 TextRenderer::RenderText(glm::vec2 position, std::string text, Color text_color) 56 | { 57 | SpriteSheet *text_sprites = SpriteSheet::GetSpriteSheet(res_id_); 58 | glm::vec2 text_render_size(0, 16.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier()); 59 | glm::vec3 gl_text_color((text_color.r / 255.0f), (text_color.g / 255.0f), (text_color.b / 255.0f)); 60 | 61 | for (char text_ch : text) 62 | { 63 | char sprite_letter[2] = { text_ch, '\0' }; 64 | text_sprites->DrawSprite(position, 65 | glm::vec2(16.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier(), 16.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier()), 66 | std::string(sprite_letter), gl_text_color); 67 | position.x += 8.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier(); 68 | text_render_size.x += 8.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier(); 69 | } 70 | text_render_size += 8.0f * HUDOverlay::GetInstance()->GetHUDSizeMultiplier(); 71 | 72 | return text_render_size; 73 | } 74 | -------------------------------------------------------------------------------- /src/dll/TextRenderer.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_TEXTRENDERER_H 16 | #define MISSINGHUD2_TEXTRENDERER_H 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "GLStructs.h" 25 | #include "ShaderProgram.h" 26 | #include "SpriteSheet.h" 27 | #include "ResourceLoader.h" 28 | #include 29 | 30 | class TextRenderer 31 | { 32 | public: 33 | static TextRenderer *GetRenderer(RESID font_res_id, RESID font_charmap); 34 | static void DestroyAll(); 35 | 36 | // Returns a vector object specifying the OpenGL height/width of the rendered text 37 | glm::vec2 RenderText(glm::vec2 position, std::string text, Color text_color); 38 | 39 | private: 40 | TextRenderer(RESID font_res_id, RESID font_charmap); 41 | ~TextRenderer(); 42 | 43 | private: 44 | static std::map renderers_; 45 | RESID res_id_ = 0; 46 | }; 47 | 48 | 49 | #endif //MISSINGHUD2_TEXTRENDERER_H 50 | -------------------------------------------------------------------------------- /src/mhud2_version.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Trevor Meehl 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef MISSINGHUD2_VERSION_H 16 | #define MISSINGHUD2_VERSION_H 17 | 18 | #define MHUD2_VERSION "v1.4.4" 19 | 20 | #define MHUD2_MAJOR_VERSION 1 21 | #define MHUD2_MINOR_VERSION 4 22 | #define MHUD2_REVISION_VERSION 4 23 | 24 | #endif //MISSINGHUD2_VERSION_H 25 | -------------------------------------------------------------------------------- /src/proto/mhud2.proto: -------------------------------------------------------------------------------- 1 | package mhud2; 2 | 3 | option optimize_for = LITE_RUNTIME; 4 | 5 | message Log 6 | { 7 | enum LogType 8 | { 9 | LOG_INFO = 0; 10 | LOG_ERROR = 1; 11 | } 12 | 13 | optional LogType log_type = 1; 14 | optional string log_msg = 2; 15 | } 16 | 17 | message Preferences 18 | { 19 | optional bool show_tears_fired = 1; 20 | optional bool show_shot_height = 2; 21 | optional int32 stat_precision = 3; 22 | optional bool split_deal_chance = 4; 23 | } --------------------------------------------------------------------------------