├── LICENSE ├── README.md └── Chams.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Rev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android OpenGL ES Chams 2 | In order to use this, the application needs to be using OpenGL ES for graphics rendering, otherwise it will not work. I will also be working on a project similar to this one, but it will provide support for applications that use Vulkan to render. I've included descriptive comments within the main header to provide easy insight on the code. In the example, I used [ARMPatch](https://github.com/RusJJ/ARMPatch/) as my hooking framework. 3 | 4 | Forward Assault has been used as the target. 5 | 6 | ## Features 7 | * Dump shaders to your devices /Downloads/ directory (which can be easily changed), or view shader names directly through a log. Both features use glGetUniformLocation. 8 | * Get the location of a uniform from the current program ID and uniform name, instead of having to rely on the count to modify a specific uniform. 9 | * Render primitives (triangles, lines, etc) using glDrawElements. 10 | 11 | ## Implementation 12 | 1. You will need to include the GLESv2 library into your project (which we get directly from the Android NDK). Depending on what you're using to build the application, the process will be a little bit different. 13 | 14 | CMake (CMakeLists.txt): 15 | ``` 16 | find_library( # Sets the name of the path variable. 17 | 18 | GLESV2_LIB 19 | 20 | # Specifies the name of the NDK library that 21 | 22 | # you want CMake to locate. 23 | 24 | GLESv2) 25 | 26 | target_link_libraries( # Specifies the target library. 27 | 28 | ${GLESV2_LIB}) 29 | ``` 30 | 31 | NDK Build (Android.mk): 32 | ``` 33 | LOCAL_LDLIBS := -lGLESv2 34 | ``` 35 | 36 | 2. You will also need to include the gl2 header (provided by the NDK) somewhere in your project. I've included it in my native-lib.cpp as seen [here](https://github.com/Rev/Android-Native-Mod-Template/blob/master/app/src/main/cpp/native-lib.cpp). 37 | ``` 38 | #include 39 | ``` 40 | 41 | 3. Call isChams somewhere after you have included the provided chams header - such as in your main thread. 42 | ``` 43 | isChams(); 44 | ``` 45 | 46 | ## Examples 47 | I've included many examples of the limitless 'types' of chams that you can do. These examples can be found in the [main header](https://github.com/Rev/Android-OpenGL-ES-Chams/blob/main/Chams.h). 48 | 49 | * Invisible Chams ![image](https://user-images.githubusercontent.com/64957743/173176435-eb201f00-ed9e-4d27-b87f-a0606006f64d.png) 50 | * Black Flat Chams ![image](https://user-images.githubusercontent.com/64957743/173176366-04663aa3-7236-4efc-ad20-55b6138b2b3e.png) 51 | * Wireframe Chams ![image](https://user-images.githubusercontent.com/64957743/173176396-2375e86b-9eab-4945-aae9-728a23714a07.png) 52 | * Visibility Check Chams ![image](https://user-images.githubusercontent.com/64957743/173176411-b4c6df81-1010-4815-bc72-c66d87efcd8f.png) 53 | * Base-Colour Chams ![image](https://user-images.githubusercontent.com/64957743/173176486-b2d18573-fa08-437d-bbff-6097b0d1b850.png) 54 | -------------------------------------------------------------------------------- /Chams.h: -------------------------------------------------------------------------------- 1 | // 2 | // Chams.h 3 | // 4 | // Created by Rev on 4/06/2022. 5 | // 6 | 7 | #ifndef CHAMS_H 8 | #define CHAMS_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | const char *isUseShader = OBFUSCATE("_BumpMap"); 15 | const char *isUniformLocation = OBFUSCATE("glGetUniformLocation"); 16 | const char *isDrawElements = OBFUSCATE("glDrawElements"); 17 | const char *isDumpLocation = OBFUSCATE("/sdcard/Download/Shaders.txt"); 18 | const char *isSymbolError = dlerror(); 19 | 20 | auto isGLESHandle = dlopen(OBFUSCATE("libGLESv2.so"), RTLD_LAZY); 21 | auto isglGetUniformLocationAddress = dlsym(isGLESHandle, isUniformLocation); 22 | auto isglDrawElementsAddress = dlsym(isGLESHandle, isDrawElements); 23 | 24 | bool isDumpToSD = true; 25 | bool isLog = true; 26 | 27 | /* 28 | * dlopen allows us to get the handle of a specified shared object - which is libGLESv2.so in my case. 29 | * dlsym returns the address of a symbol, from the given handle. 30 | * dlerror returns a null terminated string of characters that delineates the last error when calling dlopen. For example, if it can't find 'glDrawElements' from the given handle 'libGLESv2.so', it will return an error. 31 | * Be sure to implement in the AndroidManifest.XML if you would like to dump the shaders. 32 | */ 33 | 34 | string isDump(const char *name) 35 | { 36 | vector isShaders = {name}; 37 | 38 | static string isTotalShaders; 39 | 40 | for (const auto &isAddTo: isShaders) 41 | isTotalShaders += (isAddTo + "\n"); // Adding whatever is added to the string vector to isTotalShaders, with a new line added per shader. 42 | 43 | return isTotalShaders.c_str(); 44 | } 45 | 46 | GLint (*old_glGetUniformLocation)(GLuint program, const GLchar *name); 47 | GLint glGetUniformLocation(GLuint program, const GLchar *name) // returns location of a shader/uniform. 48 | { 49 | if(isLog) 50 | LOGINFO("Shader: %s Program: %i", name, program); 51 | 52 | if(isDumpToSD) 53 | ofstream(isDumpLocation) << isDump(name); 54 | 55 | return old_glGetUniformLocation(program, name); 56 | } 57 | 58 | /* 59 | * What is a uniform, and what does glGetUniformLocation do? 60 | * A uniform is a global shader - meaning that it can be accessed from all scopes. glGetUniformLocation returns the location of a uniform based on the given program, and uniform name. 61 | * Read more: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetUniformLocation.xhtml 62 | */ 63 | 64 | bool isShader() // https://stackoverflow.com/a/62663705/17529905 65 | { 66 | GLint program; 67 | glGetIntegerv(GL_CURRENT_PROGRAM, &program); // returns the value of program -> https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGet.xhtml 68 | 69 | return old_glGetUniformLocation(program, isUseShader) != -1; 70 | } 71 | 72 | /* 73 | * This function returns -1 if "name" (isUseShader) does not correspond to an active uniform variable in program (program), if name starts with the reserved prefix "gl_", 74 | * or if name is associated with an atomic counter or a named uniform block." https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetUniformLocation.xhtml 75 | */ 76 | 77 | /* 78 | * Why != -1? 79 | * It isn't a requirement to have it. This is just here in case the shader isn't found within the given program, it won't return -1. 80 | * So, why is it important? Well, since it is a bool, anything that isn't 0 will evaluate to true. 81 | * In cases that it can't get the shader, and it returns -1, it will enable every shader at once in that specified program. 82 | * Think of it like this, if you're searching for a shader that doesn't exist in the program, and it returns true, it will "enable" every shader because it's not returning true for any specific shader - as it doesn't exist, and returning -1 evaluates to true. 83 | * If the shader does exist, it will return the actual id for it (> 0), and make the isShader check specific to it. 84 | * glGetUniformLocation's purpose is to return the location of a shader/uniform - where returning -1 indicates an issue. 85 | * I hope that makes sense! 86 | */ 87 | 88 | void (*old_glDrawElements)(GLenum mode, GLsizei count, GLenum type, const void *indices); 89 | void glDrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices) 90 | { 91 | old_glDrawElements(mode, count, type, indices); 92 | 93 | if(isShader() > 0) 94 | { 95 | /* LOGDEBUG("Count: %i", count); By using this, you can get the count from the shader. Logging the count from '_BumpMap' gives you 10716, 1116, 8868, 5394. So, if you were to do 96 | * if(count == 10716 || count == 1116 || count == 8868 || count == 5394), you wouldn't need to use if(isShader() > 0). */ 97 | 98 | 99 | /* Base-Colour Chams: https://i.imgur.com/Mab5QyF.png 100 | The code below will always use the same colour on the shader, no matter the depth. 101 | 102 | glDisable(GL_DEPTH_TEST); 103 | glEnable(GL_BLEND); 104 | 105 | glBlendFunc(GL_ONE, GL_CONSTANT_COLOR); 106 | glBlendColor(1.0f, 0.647f, 0.0f, 1.0f); // orange -> 255,165,0 -> r, g, b /255 -> 1.0, 0.647, 0.0 107 | 108 | old_glDrawElements(GL_TRIANGLES, count, type, indices); 109 | 110 | glEnable(GL_DEPTH_TEST); // re-enable depth testing, so that we don't see every shader through walls. */ 111 | 112 | 113 | /* Visibility-Check Chams: https://i.imgur.com/GRUl5eT.png 114 | Seeing players through objects works purely because of GL_DEPTH_TEST. When we disable it, it will show our shader through all depths (whether it be walls, or other objects, etc). 115 | When we give a colour specific to this disabled GL_DEPTH_TEST (let it be green), it will give a green colour to the shader when the shaders depth isn't equal to our shaders depth (not visible). 116 | When we re-enable it, and use glBlendColor again (red), it will give a specific colour to the shaders that ARE in your current depth - which would be something that is visible by your player. 117 | 118 | glDisable(GL_DEPTH_TEST); 119 | glEnable(GL_BLEND); 120 | 121 | glBlendFunc(GL_ONE, GL_CONSTANT_COLOR); 122 | glBlendColor(0.0f, 1.0f, 0.0f, 1.0f); 123 | 124 | old_glDrawElements(GL_TRIANGLES, count, type, indices); 125 | 126 | glEnable(GL_DEPTH_TEST); 127 | 128 | glBlendColor(1.0f, 0.0f, 0.0f, 1.0f); 129 | 130 | old_glDrawElements(GL_TRIANGLES, count, type, indices); */ 131 | 132 | 133 | /* Wireframe Chams: https://i.imgur.com/L2FKuKz.png 134 | To make wireframe chams, all we need to do is specify a line width to use, and then use 'GL_LINES' in our 'mode' parameter. We will also add the depth tests, as usual. 135 | glDepthFunc with the "GL_LESS" parameter is used to keep rendering the wireframes, even when the object (or part of the object) isn't visible to us as per the depth. 136 | 137 | glDisable(GL_DEPTH_TEST); 138 | glLineWidth(1.0f); 139 | 140 | old_glDrawElements(GL_LINES, count, type, indices); 141 | 142 | glEnable(GL_DEPTH_TEST); 143 | 144 | glDepthFunc(GL_LESS); 145 | 146 | old_glDrawElements(GL_TRIANGLES, count, type, indices); */ 147 | 148 | 149 | /* Black Flat Chams: https://i.imgur.com/z2fWmOz.png 150 | glDisable(GL_DEPTH_TEST); 151 | glEnable(GL_BLEND); 152 | 153 | glBlendFunc(GL_ZERO, GL_ZERO); 154 | 155 | old_glDrawElements(GL_TRIANGLES, count, type, indices); 156 | 157 | glEnable(GL_DEPTH_TEST); */ 158 | 159 | 160 | /* Invisible Chams: https://i.imgur.com/CSnca4n.png 161 | glDisable(GL_DEPTH_TEST); 162 | glEnable(GL_BLEND); 163 | 164 | glBlendFunc(GL_ZERO, GL_ONE); 165 | 166 | old_glDrawElements(GL_TRIANGLES, count, type, indices); 167 | 168 | glEnable(GL_DEPTH_TEST); 169 | 170 | glDepthFunc(GL_LESS); */ 171 | 172 | 173 | // Rainbow chams can be easily made with my Rainbow Colour Cycler header; https://github.com/Rev/ImGui-Rainbow-Colour-Cycler 174 | 175 | // Interested in creating your own 'style' of chams? Read the documentation! 176 | // https://docs.microsoft.com/en-us/windows/win32/opengl/gl-functions 177 | // https://www.khronos.org/ 178 | // https://learnopengl.com/ 179 | 180 | } 181 | 182 | } 183 | 184 | /* 185 | * What does glDrawElements do? 186 | * In short, glDrawElements is used to render a set of geometric primitives (triangles, lines, etc - which is shown in the 'mode' parameter). 187 | * Read more: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDrawElements.xhtml 188 | */ 189 | 190 | void isChams() 191 | { 192 | if(isSymbolError) 193 | { 194 | LOGERROR(OBFUSCATE("Symbol Error: %s"), isSymbolError); 195 | } 196 | 197 | else 198 | { 199 | ARMPatch::hook((void *) isglGetUniformLocationAddress, (void *) &glGetUniformLocation, (void **) &old_glGetUniformLocation); 200 | ARMPatch::hook((void *) isglDrawElementsAddress, (void *) &glDrawElements, (void **) &old_glDrawElements); 201 | } 202 | } 203 | 204 | #endif 205 | --------------------------------------------------------------------------------