Results 1 to 25 of 25

Thread: OpenGL Plugin Tutorial (Basics)

  1. #1
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default OpenGL Plugin Tutorial (Basics)

    OpenGL Tutorial (Basics)

    Contents:


    1. Intro.
    2. Compiler & Include Setup.
    3. Basics of Pointers/Typedefs.
    4. Layout of the OpenGL Plugin.
    5. OpenGL Basics.
    6. Function Information/Descriptions.
    7. Putting It Together.
    8. Hooking & Implementing Interceptions.
    9. Injecting vs. Wrapping.
    10. Simba & Lape Side.
    11. Writing An Include. Functions, Pointers, Etc..



    Intro:

    Please note that this tutorial is not for the faint of heart. Be warned that there is pointers involved; Both in Simba and in C++! A decent math background will be required and an extremely high one to develop even further than what is discussed (Vectors, Matrices, Quarternions, etc). Also note that this tutorial is not required to run GLHook or OpenGL but rather it is to develop OpenGL plugins.



    Basics Of Pointers/Typedefs:


    The purpose of a "typedef" is to make life easier. It is so that we do not type the same thing over and over and also allows us to shorten code by a massive amount! It can be used in combination with functions so that you make things more readable when using function pointers.

    One example is:

    C++ Code:
    typedef void (WINAPI *ptr_glAccum) (GLenum op, GLfloat value);

    The above is a declaration of a pointer to a function that returns void (nothing). Its parameters will be OP which is of type GLenum, and value which is of type GLfloat. An enum is short for enumerated value. In other words, it's an integral number. GLfloat is defined as a float (Extended in pascal script).

    The function pointer is identified by its name: "ptr_glAccum" and it is named like that because it is a
    pointer to a function called glAccum. In reality, we can name it whatever we want but for conventional and reading purposes, I've chosen to name it as such.

    All in all, the typedef is saying: Any function of this type(ptr_glAccum) will have two parameters, one of enum type and another that is a float. It will not return anything!

    If you understood that, then you have understood the first step in the development of the OpenGL32.dll.

    Now to use this declaration, we have to declare something of its type! I tend to call this "Pointer naming/declaring". Let's start with an example:


    C++ Code:
    extern ptr_glAccum                    optr_glAccum;

    For now ignore the "Extern" part of the declaration above. In the above, I create a function pointer called optr_glAccum. It has the same parameters and return values as ptr_glAccum. There is NO DIFFERENCE! The reason I do this is because I do not want to write out a whole new typedef again! This is how we use our previous typedef. The "o" infront of the "ptr" stands for original. When hooking things, you want to grab a pointer to the original function and store it for use later! This is essential!

    Now to explain the extern part. Since in C++, we tend to split things into header and body files, it cannot be seen from another file. The extern will tell the compiler that this function is implemented later in some other file (body). Inother words, all our declarations are in headers (.h / .hpp files) and our implementations of those are in bodies (.cpp files).

    That's it for this section.


    Layout Of The OpenGL Plugin/Wrapper:


    First we will create a header file with the extension .hpp. In it, we will have a header guard (Stops files from being included more than once) and our includes/declarations.

    One header file looks like this (.hpp file):
    C++ Code:
    #ifndef GLHOOK_HPP_INCLUDED
    #define GLHOOK_HPP_INCLUDED

    #include <gl/gl.h>    //These are the same as {$I SRL/SRL.Simba} Include our needed stuff.
    #include <gl/glu.h>
    #include <gl/glext.h>
    #include <windows.h>
    #include <iostream>
    #include "SharedMemory.hpp"  //This is in quotes because we are including our own project file that we created! < bleh > means to include something in another folder.. usually compiler files.

    #ifdef OPENGL32_EXPORTS                //For our DLL to be exported/imported respectfully. We defined this in our setup!
        #define DLL_EXPORT __declspec(dllexport)
    #else
        #define DLL_EXPORT __declspec(dllimport)
    #endif

    #ifdef __cplusplus                //If using a C++ compiler then define these so that names aren't mangled.
        #define GL_EXTERN extern "C"
        #define GL_EXPORT extern "C" DLL_EXPORT
    #endif

                                        /**         VARIABLES: Start.        **/
    extern HMODULE OriginalGL;                           //A variable being declared but defined in a different file.
    GL_EXTERN bool __stdcall Initialize(void);           //Declarations of a function using the std calling convention.
    GL_EXTERN bool __stdcall DeInitialize(void);

                                        /**         VARIABLES: End.        **/

                                        /**         TYPEDEFS: Start (Some function pointers).         **/

    typedef void (WINAPI *ptr_glAccum) (GLenum op, GLfloat value);
    typedef void (WINAPI *ptr_glAlphaFunc) (GLenum func, GLclampf ref);
    typedef GLboolean (WINAPI *ptr_glAreTexturesResident) (GLsizei n, const GLuint *textures, GLboolean *residences);
    typedef void (WINAPI *ptr_glArrayElement) (GLint index);
    typedef void (WINAPI *ptr_glBegin) (GLenum mode);
    typedef void (WINAPI *ptr_glBindBufferARB) (GLenum target, GLuint buffer);


    The body for the above header would look like (.cpp file):
    C++ Code:
    #include "GLHook.hpp"    //We have to include our header file in the body or else none of the following would be declared.

                                        /**         VARIABLES: Start.        **/
    HMODULE OriginalGL = 0;            //we just implemented our variable and gave it a value. Remember that the header does not do this. Headers declare. Bodies implement.

                                        /**         VARIABLES: End.        **/

                                        /**         NAMING: Start.       **/

    ptr_glAccum                    optr_glAccum;                                 //These are implementations. optr.. is of type ptr..
    ptr_glAlphaFunc                optr_glAlphaFunc;
    ptr_glAreTexturesResident      optr_glAreTexturesResident;
    ptr_glArrayElement             optr_glArrayElement;
    ptr_glBegin                    optr_glBegin;
    ptr_glBindBufferARB            optr_glBindBufferARB;


    This is the basics of the layout of the OpenGL plugin. All declarations MUST go in a header if they are to be seen by other files. Their respective implementations must go in a body file!
    This is done for every OpenGL function that we need. Thus we do this for the 362 normal OpenGL functions and for the extended OpenGL functions.

    Inother words, Microsoft/Windows only exports 362 OpenGL functions. If you export more, someone can check what extra you did and catch you in the action/possibly ban you. This does not mean that you cannot USE more than 362, it just means that you must only export the ones that are exported by default! All others can be used but DO NOT EXPORT them.

    An exported function would have GL_EXPORT infront of it whereas extra functions would have GL_EXTERN infront of it. This is the reason we did all those #define OpenGL32_Exports, etc.. earlier!



    OpenGL Basics:

    This section will NOT break every single thing down to the very basic tutorial. It will only describe functions and things that are used and how/when to use them. It WILL give a background on the very basics but that is limited and up to you to learn.


    First I'll start by apologizing for just posting OpenGL code without breaking it down piece by piece. I will attempt to do that right now..

    Every OpenGL function/variable, etc starts with "GL" in lowercase.

    Some examples are:

    glAccum, glBindBuffer, glColor4ub.

    They all begin with gl. The next thing that comes is a descriptor/name. For example, BindBuffer will do exactly what it says. Bind a buffer to a memory location. Color will enable all drawings to use the colour that was previously defined.. Etc. After the names, you may see type specifiers.

    Type specifiers would be a suffix usually containing: u, b, f, fv, uv, ub, iv, i, etc and a number specifying the amount of parameters a function takes.


    These specifiers allow users to know what type the function will return or accept for parameters. Example:

    C++ Code:
    GLint, GLchar, GLfloat, GLdouble. //These are all types. No different from the ones without the GL. Infact they are defined as so: typedef GLint int; etc..
    glColor4ub. //Takes unsigned bytes as its parameters.
    glColor4iv. //Takes a void pointer to integers.
    glColor4i. //Takes 4 integers. R, G, B, A.
    glColor3i. //Takes 3 integers. R, G, B.
    glColor3f. //Takes 3 floats. R, G, B.

    Etc.. I for integer, f for float, v for void pointer, u for unsigned, b for byte.. And so on.


    Next is different types of functions. ARB stands for Architecture Review Board. These functions weren't official back then and the OpenGL community refused to accept them as official. Thus they were appended with an ARB to specify that they are extensions!

    The functions without ARB do the same thing as their ARB counter parts. Only difference is that it is now official OpenGL functions and are exported!

    EXT functions are like ARB functions. They are also Extensions but there isn't much I can say here other than that.

    NV, ATI, etc. NV are ONLY for Nvidia specific cards. As you can probably guess by now, ATI is for ATI/AMD/Radeon cards. Any function without these suffixes/prefixes are for all. Try to avoid these for portability reasons. Usually there is never a need for these.

    wGL. wGL functions are usually specific to the Windows operating system but not limited to. They are called "Wiggle" functions. They usually emulate another function that GDI already defines. Example: SwapBuffers from GDI can be used instead of wglSwapBuffers. Same thing. Either way, don't mix GDI and OpenGL!



    Function Descriptions & Usage:


    Note that I will NOT go through EVERY SINGLE OpenGL function. If you need a specific function description, just ask and I will add it. I will go through the necessary ones.



    • glBindBuffer:

      This function will activate a given buffer and make it the active buffer (Binding). It takes two parameters: Target and Buffer. The Target will tell OpenGL what kind of buffer is being bound. Buffer is the ID of the buffer being bound. During interception (as you will see later), the ID is stored and the type of buffer is stored for later. Target can be a vertex, texture, index, etc.. buffer.



    • glBindTexture:

      This function will Bind a texture to memory. The bound texture will become the active texture and all operations done after this call will be applied to that texture. To unbind a texture, its ID is set to NULL. This function is quite similar to glBindBuffer. During interception, we check if a 2DTex or Rect is being bound. If so then we do a lookup on its texture ID to find out its corresponding Checksum/Item ID.



    • glBufferDataARB:

      This function takes 3 parameters. The first is the type of buffer. Size is the size of the buffer in bytes. Data is a const void pointer to an array holding all the info that will be passed to the graphics card. Usage is whether the buffer's data is dynamic, static, etc.. Basically defines how the buffer can be used.


      This function is used to fill the currently bound buffer with data. Whether it be vertex data, texture data, index data, etc.. During interception, this function's parameters are used to generate/checksum MODEL ID's.



    • glBufferSubDataARB:

      This function is the same as above, except that it allows you to change/fill only a specific range of the currently bound buffer. This is also intercepted in-case an object is either animating or changing its data. With a range of 0 to "size" this function is exactly the same as the one above it.



    • glCallList:

      This function is equivalent to an array. The difference is that this can hold a sequence of commands and current data/bound textures/buffers, etc. When a List is called, whatever it holds is pushed to the graphics card!

      This function has 1 parameter. Its parameter is the ID of the list that holds all the data (Lists are made using GenList and delete using DeleteList). During Interception, this function is used to Identify which list is currently being used. Font characters are usually generated in a list. One list per character. Using the ID, I've used this to figure out which Letter is currently being rendered to the screen and I store that in a vector/array.



    • glColor4ub:

      This function is used for colouring all sorts of things. OpenGL is what we call StateMachine API calling. This means that the colour that will be used is the one that was last passed to this or any other colour functions.

      Example:
      C++ Code:
      glColor3i(255, 255, 0);
      //Render some text here.. It will be the same as the colour above.
      //Render some more text.. It will have the same colour as the above.
      glColor3i(0, 255, 0); //This will change the colour of whatever is rendered AFTER this call.. and so on..
      During interception, this function is used to store the last colour used. It is also used to detect font colours and font shadow colours.



    • glDisable/glEnable:

      This function will enable/disable different state machines. Example:
      C++ Code:
      glDisable(GL_TRIANGLES); //This will turn off the rendering of triangles.. etc. Usually used to turn off blending/depth testing.

      During interception, I've used this to tell when a new set of fonts/text are being rendered inorder to tell when there is a new line or a space between characters.



    • glDrawElements:

      This function has quite a few paramters. It is used to draw out the currently bound buffer to the screen! The important paramters are its count which gives a count of the amount of indices being rendered and a pointer to a buffer holding an array of all indices.

      During interception.. Well at first, I used this to get every vertex point on a model. I've removed that for portability purposes (for now at least). Currently it is used to iterate a stored list of buffers and figure out which ones are currently being rendered. If it is being rendered then make it render in wireframe mode or else fill it!



    • glEnd:

      This function is actually quite important. Here's why. When a texture or anything is being rendered, most of the time it has to be rendered between a call to glBegin and glEnd. glBegin will define what is being rendered. glEnd will say ok.. that is finished being rendered, flush the buffer and move onto the next thing.

      Example:

      C++ Code:
      glBegin(GL_QUADS);
      //All drawing of textures done here.. glVertex2i, glVertex2f, etc..
      glEnd();

      During interception, this function is used to reset variables, detect when things are done rendering so that we can store the final product. It is used to determine when Items/textures are finished rendering so that I can add all the info collected about that item to a vector of structs(data holders).



    • glEndList:

      This function is used to let OpenGL know that you are finished generating a list. During interception, this function is used to figure out when a list if finished being generated and to store its data in an array/vector.



    • glGenLists:

      This function is used to generate a list. During interception, it is used to determine the size of the list. This is important to determine if fonts are being buffered. How? Well this function takes 1 parameter. The amount of Lists that will be created in the next couple calls. Fonts usually have 256 characters right? Well when the amount = 256, then we start recording the ID's of each list! This lets us keep track of all lists of fonts.



    • glMultiTexCoord2fARB:

      This function isn't too important for RS. It just allows you to specify coordinates for the current texture or Texture0/1. During interception, it is used to figure out if the compass is being rendered to the screen. The compass is a texture too but it is actually bound to multiple textures first before it is rendered and that is the usage of this function. To specify where to merge textures.



    • glNewList:

      This function will generate a list. The amount of calls to this function depends on how many lists you said you'd need in glGenLists. Thus if glGenLists was passed 256, we know that the next 256 NewList calls are 256 characters. 1 for each char. During interception, I use this function to tell when a new list is being generated and to log the calls inbetween.



    • glPopMatrix:

      This function is possibly one of the most useful/most important ones. This function will pop the current matrix OFF the stack and the matrix on top will be the default identity matrix. Push matrix is the counterpart to this function which will push the current matrix onto the stack! During interception, I use this function to determine when things are finished being pushed from the graphics card to the screen and to then project all the point data to the screen. Basically project 3D stuff to 2D before it touches the screen.



    • glTexCoord2f:

      This function is used to tell opengl which portion of a texture should be used when rendering. During interception, this is used to store which portions of a font is being rendered. Whether it be a half a letter, a whole letter, a pixel more, etc.. Think of this as the crop tool in photoshop. You choose what parts you want.



    • glTexImage2D:

      This function is also very important as its parameters are exactly what we want. This function will take a buffer holding bytes that describe a texture and give it an ID, size, and format. This basically tells opengl how to read the texture. During interception, I check if the texture being stored is a rectangle or 2DTex. If it is, Run the checksum on its buffer. Store its checksum and other info in a multimap which will be our lookup table! This is lookup table will only add/remove ID's if a texture was deleted or does not already exist! This is the KEY to our SPEED increase in texture reading!



    • glTranslatef:

      This function is actually quite complicated as it depends on if the current matrix is row or column major. Well that's all user implemented. It will take the current matrix and multiply it by its parameters (X, Y, Z).
      Anything rendered before this call, will translated on the screen by this function.

      Example:
      C++ Code:
      glCallList(2141); //A List containing a letter. Lets assume letter A.
      glColor4ub(255, 255, 0, 255);  //Give the font character some colour.
      glTranslatef(265, 20, 0);        //Draw the font at this location. Aka translate it.

      During interception, it's used as in the above example. To figure out the position of font characters! Aka each letter.



    • glVertex2f:

      The vertex of what part of a model/texture should be rendered. During interception these paramters are only stored. Not really used.



    • glVertex2i:

      This function has two paramters. X, and Y. This function is used to tell opengl where to draw. The current raster position. During interception, the parameters are saved to figure out where fonts are within their respective font textures and where/position of Textures are being rendered on the screen.



    • glVertexPointer:

      This function gives opengl a pointer to an array of vertices. At first this was hooked inorder to determine each vertex of any model. This was not cross card compatible and removed. Currently, during interception it is just used to add all the collected model data to an array/vector.



    • wglGetProcAddress:

      This function is quite important. Infact this function is equivalent to the amount of unexported functions that we decide to use. For functions that aren't exported, they have to go through this function first! This function will then return a pointer to the address where the unexported function is located. You then use that function pointer and pass it parameters to use the function you wish to use.

      During interception, this function is used to make our pointers valid and to hook non-default exported functions.



    • wglSwapBuffers:

      This function is very very very important in OpenGL. There isn't a single program out there that does not use this or SwapBuffers. Well that is if the program uses multiple buffers (double/tripple buffering) then it must use this function! During interception, this is used to call our custom functions right before all the data is pushed to the screen. Inorder words, OpenGL will render everything to a buffer. When this function is called, that entire buffer is scraped and push straight to the screen all at once! This makes rendering extremely fast rather than doing one by one!

      When hooked, this is used to communicate with the shared memory between OpenGL32 and Simba. It is also used to clear all our collected data so that we don't use too much memory and so that we don't overflow, etc.. It is used to draw to the screen as well and to display info such as ID's, Angles, etc!

    Last edited by Brandon; 11-11-2012 at 03:14 AM.
    I am Ggzz..
    Hackintosher

  2. #2
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default

    Putting It Together:


    Detours:

    What is a detour? Well a detour is a function that "detours" or in other words, intercepts. Think of it like a middle-man. Instead of just having the data pass straight through, it has to pass through you first then you will take a look at it before deciding to let it through. Since it passes through you first, you have full control with what happens to that data. This is known as detouring. There are three ways to detour/hook a function/call.

    The first way is to use Assembly and cheat engine or a debugger such as ollydbg/ida-pro. These tools are used to figure out what addresses (if static) can be injected to/read/written to inorder to intercept/modify/block information, etc..

    The second way is called code-caving. This also uses assembly but it's a bit different. Code-caves are where you modify a part of the client so that it "JMP" (Jumps) to your code. Then when your code is finished executing, it will Jump back to its previous position right after the forced Jump.

    The third way is called a wrapper (the method we are using). A detour wrapper is a function with the exact same syntax and parameters as the original. What happens here is that you use a .def file to redirect all calls to the original function to go through your wrapper function. Your wrapper then analyzes the data and passes it through to the original. This is a true middle-man technique and the main one I will discuss in detail.

    Technically there are four ways. But I don't care much for Microsoft's detour library so ignore that.

    The main way we will be discussing looks like this:

    C++ Code:
    GL_EXTERN void __stdcall GLHook_glBindTexture(GLenum target, GLuint texture)
    {
        (*optr_glBindTexture) (target, texture);
    }

    In the above, I've declared a function with the exact same syntax and parameters as the original! Now here's the break down of how it works.

    GL_EXTERN is used to just define the function internally but NOT export it. Thus the function is not listed in the export table and not accessible from outside. GL_EXPORT will add it to the list and using an export viewer, you can see that it is added. GL_EXTERN and GL_EXPORT are custom defines that we created when starting the project. Now why did I not export this function? Well the reason is because you NEVER want to export your detours/hooks! EVER! When you use the .def file, it will export all the original OpenGL functions for you. If you export your detours, you will have 2x as much exports (Your detour + the original for each function)! That is fully detectable! So do NOT do that please.

    Next is the Function name. Well I added GLHook_ as a prefix to the original function name because no two functions can have the same name and parameters or else the compiler will get confused! You can name it whatever you want but I preferred the name GLHook because that's exactly what that function does. It hooks/detours.

    After this is the Parameters. The parameters must be the exact same as the function were are trying to hook/detour. This is a definite must or else things will crash/go bad!

    Finally is the Function's Body. Every function must have a body. What's so special about ours though? Well ours calls the original function via a function pointer. This is the whole trick to the detour. See without calling the original, the data is lost in space and nothing happens. How did we get the pointer? Well you remember a little while back when I spoke of typedef's and optr being Original_ptr? That's where we got it from!

    Every aspect of the above 4 instructions on creating wrapper detours is crucial to the success of our DLL!
    Now what use is that if we just let the data through? Well here's an example of what we can do (Read the code comments below for more info):


    C++ Code:
    typedef struct      //Dammit. Here we go again with this typedef stuff.. ;) We declare a datatype called PossibleItem and its internals is a struct.
    {
        GLint TID;      //Texture ID
        GLint BaseID;   //Base ID
        GLint MeanID;   //Average Colour ID.
        GLint CoordCount;  //Coordinate Count.
        GLint SX[4], SY[4];  //Coordinates on Screen.
    } PossibleItem;         //Our custom made data type.

    PossibleItem PI;    //PI is of data-type PossibleItem.


    GL_EXTERN void __stdcall GLHook_glBindTexture(GLenum target, GLuint texture)   //WrapperDetour definition
    {
        TextureFound = false;           //A global boolean set to false by default.
        if (target == GL_TEXTURE_RECTANGLE || target == GL_TEXTURE_2D)     //If it is a desired texture. Maybe one that we want?
        {
            PI.TID = texture;                       //Save the texture ID.

            std::multimap<uint32_t, std::pair<uint32_t, uint32_t>>::iterator it = ListOfPanelIDs.find(texture);  //The multimap is our lookup table. We search for the textureID.
            if (it != ListOfPanelIDs.end())    //If the TextureID is found within our lookup table.
            {
                PI.BaseID = it->second.first;   //Get its BaseID.
                PI.MeanID = it->second.second;  //Get its Average Colour ID.
            }
        TextureFound = (texture == 0 ? false : true);  //If the texture ID = 0 then texture found = false.. Otherwise Texturefound = true.
        }

        (*optr_glBindTexture) (target, texture);         //Pass the parameters we got from the game to the original opengl32 via a function pointer.
    }

    In the above, a structure was created to hold our intercepted data for just one item. If we wanted more, we'd do:
    C++ Code:
    std::vector<PossibleItem> ListOfItems;



    Now in the above, you might try to just use that function just like that. Thing is, just like that, that function itself will throw an error since the pointer to the original function is actually NULL! Why? That's because I haven't showed you how to store the original function's pointer.
    Let's take a look at that now:

    Header File GLHook.hpp:
    C++ Code:
    extern HMODULE OriginalGL;                        //Global Variable. A handle to a module/dll.
    GL_EXTERN bool __stdcall Initialize(void);      //Declaration of a function that takes no parameters and returns a bool.
    GL_EXTERN bool __stdcall DeInitialize(void);  //Same as the above.

    typedef void (WINAPI *ptr_glAccum) (GLenum op, GLfloat value);   //The definition of a function pointer to glAccum.

    extern ptr_glAccum                    optr_glAccum;                        //optr_ is of type ptr_ and is defined/implemented in a different file.


    Body File GLHook.cpp:
    C++ Code:
    #include "GLHook.hpp"              //Must include our header in our body!
    HMODULE OriginalGL = 0;          //Set the default handle to NULL.
    ptr_glAccum                    optr_glAccum;  //Implement our declaration.

    bool __stdcall Initialize(void)   //Implementation of our declaration of the Initialize function.
    {
        if (!OriginalGL)                 //If the handle is NULL then..
        {
            char Root[MAX_PATH];   //An array/string that will hold the path to the original opengl.dll.
            GetSystemDirectoryA(Root, MAX_PATH);     //Ask the system for the root folder. This is usually System32!
            #ifdef _MSC_VER                            //If using Microsoft Visual Studio instead of codeblocks.
            strcat_s(Root, "\\opengl32.dll");
            #else                                         //If using codeblocks.
            strcat(Root, "\\opengl32.dll");        //Append OpenGL32.dll to the system32 path. Thus: C:/Windows/System32/OpenGl32.dll is actually the location on windows! We do this dynamically!
            #endif

            OriginalGL = LoadLibraryA(Root);        //Now we load the original opengl via the path we just got above. And store the handle to it.
            if (!OriginalGL) return false;       //If loading failed, throw an error and stop our DLL from going any further.

            if ((optr_glAccum = (ptr_glAccum) GetProcAddress(OriginalGL, "glAccum")) == NULL)  //We get the address of the original glAccum from the original OpenGL32.dll file. We check if it's null. We assign the handle to our Original_Pointer.
            {//Now our Original Pointer holds the address of the original function we are detouring.
                return false;   //If it was indeed NULL, throw an error/return false!
            }
        return true;   //We return true/successful if ALL function pointer grabbing was successful. Thus do this for all 362 functions and whala. We just stored all 362 original pointers so we can use them for detouring.
    }

    In the above, we basically loaded the original opengl32.dll which is located in system32 on windows. We ask it to give us the address of a specific function. We store that address in our Optr_... If the address was null, we exit and stop everything. If it was successful, then that's great!! We can now use it with our detour function!

    Now to write the destructor. The initialize function deals with pointers and loaded libraries. We of course need to clean up after ourselves. This is C++. Cleaning up after yourself is a must as no one will do it for you! Here's how our deinitialize implementation looks:

    C++ Code:
    bool __stdcall DeInitialize(void)   //Implementation.
    {
        if (FreeLibrary(OriginalGL))     //Remember how we stored the handle to the original openGL.dll? Well we now free that!
        {
            OriginalGL = NULL;  //Just to be safe. If the DLL is freed, set this to NULL. It's just safety and not 100% required.
            return true;  //Deinitialization and cleanup was successful. Let the system know that everything went well.
        }
        return false;     //Return false if nothing was successful.
    }

    In the above, we freed the original OpenGL32.dll that we loaded during initialization. That's all we do here. That's basically it. Now to add it to our main and this will allow the initialization to run whenever we go into OGL mode and to cleanup when we exit the game:

    C++ Code:
    #include <windows.h>
    #include "GLHook.hpp"   //Include the file we just completed with all 362 exports + the initialization/cleanup.

    GL_EXTERN BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) //Our main!
    {
        switch (fdwReason)
        {
            case DLL_PROCESS_ATTACH:    //Upon being loaded do the following.
            {
                DisableThreadLibraryCalls(hinstDLL);   //I'll leave this to you to search up.
                return Initialize();    //We return our Initialization function which should return true if we are successful.
            }
            break;

            case DLL_PROCESS_DETACH:   //When the game is closed/dll is unloaded, we will clean up.
            {
                return DeInitialize();   //We return whatever our deinitialization returned. Should be true if cleanup is successful.
            }
            break;
        }
        return TRUE;
    }

    That's is all that was needed to Successfully Detour via a Wrapper DLL. We can now use our Detour functions however we want! We can do whatever we wanted with the data that the game attempts to pass to the original OpenGL32.


    Hooking & Implementing Interceptions:

    Serialization:

    What is serialization? Serialization is a method of turning data into bytes. Why do we do this?
    Well inorder to transfer data over a network, usually that data is a series of bytes and when it gets to the other side, it's reconstructed into its original form for use.
    What does that have to do with us? Well our DLL uses serialization in-order to give Simba the data it needs. I'm almost 100% sure that anyone reading this would remember that Smart does the same thing. It serializes its data, transfers it to Shared Memory or over a Socket connection. When it gets to the other side, it's then reconstructed and given to Simba indirectly.

    In our DLL, since Smart does not implement this yet, we need a way to communicate with Simba or any other program/dll that decides to use the data we collected. Now why exactly do we have to use this technique? Can't we just communicate like normal? Well the answer to that is no.
    When a DLL is loaded by a program, all allocations and things it does is within that program's address space. For example, a pointer in Address Space A may not be the same as in Address Space B. Thus it's a bad idea to send pointers over a network/shared memory as it cannot be used.

    So in-order to share data, we are going to serialize it, write it to our shared memory buffer and then have Simba read from that. This topic touches on Inter-Process Communication (IPC).


    Memory Communication:

    Examples:
    C++ Code:
    #ifndef SHAREDMEMORY_HPP_INCLUDED
    #define SHAREDMEMORY_HPP_INCLUDED

    #define MapSize 1000000   //We declare the size of our memory-map in bytes. This is just 1Mb. It has to be large enough to store all our data.

    #include <windows.h>
    #include <iostream>

    #ifdef OPENGL32_EXPORTS       //Just some defines and what not.
        #define DLL_EXPORT __declspec(dllexport)
    #else
        #define DLL_EXPORT __declspec(dllimport)
    #endif

    #ifdef __cplusplus
        #define GL_EXTERN extern "C"
        #define GL_EXPORT extern "C" DLL_EXPORT
    #endif


    extern void* pData;      //A pointer that will store the location of our SharedBuffer.
    extern HANDLE hMapFile;  //A handle to the open shared memory file.
    extern std::string SharedMemoryName;   //The name of our memory/file.

    //The rest are NOT exported. These are function declarations and will be implemented soon. Their names describe what they will do.

    GL_EXTERN bool __stdcall CreateSharedMemory();
    GL_EXTERN bool __stdcall OpenSharedMemory();
    GL_EXTERN bool __stdcall UnMapSharedMemory();
    GL_EXTERN bool __stdcall SharedMemoryBusy();
    GL_EXTERN void* __stdcall RequestSharedMemory();
    GL_EXTERN bool __stdcall SharedMemoryReturned();
    GL_EXTERN bool __stdcall SharedDataFetched();

    #endif // SHAREDMEMORY_HPP_INCLUDED


    The body file will look like:
    C++ Code:
    #include "SharedMemory.hpp"  //Include our header.

    void* pData = NULL;   //Set the original pointer to NULL.
    HANDLE hMapFile = NULL;   //Set the file handle to NULL.
    std::string SharedMemoryName = "Local\\GLHookMap";  //Ahh Local means that our SharedMemory can only be used on the computer we are on. No servers or anything. GLHookMap is our memory's name. It can be named anything but remember simba will need to know its name in-order to access it.

    GL_EXTERN bool __stdcall CreateSharedMemory()
    {
        if ((hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MapSize, SharedMemoryName.c_str())) == NULL)
        {
            return false;   //We attempt to create the shared memory buffer and if we failed to store its handle, return false!
        }

        if ((pData = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, MapSize)) == NULL)
        {
            CloseHandle(hMapFile);   //We attempt to map the shared memory for usage. We store the pointer to its location. If we are not successful, close the map and return false.
            return false;
        }
        return true;
    }

    GL_EXTERN bool __stdcall OpenSharedMemory()  //Self explanatory.
    {
        if ((hMapFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, false, SharedMemoryName.c_str())) == NULL)
        {
            std::cout<<"\nCould Not Open Shared Memory Map.\n";
            return false;
        }

        if ((pData = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, MapSize)) == NULL)
        {
            CloseHandle(hMapFile);
            std::cout<<"\nCould Not Map View Of File.\n";
            return false;
        }
        std::cout<<"\nInter-Process Communication Successful.\n";
        return true;
    }

    GL_EXTERN bool __stdcall UnMapSharedMemory()  //Self Explanatory.
    {
        if (hMapFile != NULL)
        {
            UnmapViewOfFile(pData);
            CloseHandle(hMapFile);
            return true;
        }
        return false;
    }

    GL_EXTERN bool __stdcall SharedMemoryBusy()
    {
        double* Data = static_cast<double*>(pData);   //We cast our Pointer's data to a double since all our data is stored in 8 byte segments. (doubles are 8 in size).
        return static_cast<int>(Data[1]) > 0 ? true : false;  //Cast it to an integer. We say that Data[1] will hold the status of our process.
    }

    GL_EXTERN void* __stdcall RequestSharedMemory()
    {
        for (int Success = 0; Success < 50; ++Success)  //We will try 50 times at most.. I've updated and changed this to Infinite wait.
        {
            if (SharedMemoryBusy())
                Sleep(10);
            else
                break;
        }
        return pData;   //If all is well and the memory is free, return a pointer to it.
    }

    GL_EXTERN bool __stdcall SharedMemoryReturned()
    {
        double* Data = static_cast<double*>(pData);
        return static_cast<int>(Data[1]) == 2 ? true : false;  //If the data was fetched successfully from the memory location.  1 = busy, 2 = good.
    }

    GL_EXTERN bool __stdcall SharedDataFetched()
    {
        for (int Success = 0; Success < 50; ++Success)
        {
            if (SharedMemoryReturned())
                return true;

            if (!SharedMemoryBusy())
                return false;

            Sleep(10);
        }
        return false;
    }


    Now that you have the basics of Memory Communication, we will move onto serializing Data part 2..


    Serialization Part 2:

    Please note that in C++, templates have no declarations. They must be implemented on the spot! Thus our implementation for these templates must go in the header. A template is a specialization of a function that can be passed any paramter. In Pascal script, this is known as a TVariant. C++ templates are far more powerful though as anything can be passed. It is not limited in any way.

    Our header file will look like this:
    C++ Code:
    template<typename T>  //T will be a variable representing "anything"
    void MemSerialize(unsigned char* &Destination, const T &Source)  //Our function accepts a pointer to a destination buffer and a pointer to the source info.
    {
        CopyMemory(Destination, &Source, sizeof(T));  //We copy the source's info to the destination buffer. Destination buffer will typically be our shared memory pointer.
        Destination += sizeof(T);   //We iterate the pointer to the next free location in memory.
    }

    template<typename T>
    void MemSerialize(unsigned char* &Destination, const std::vector<T> &VectorContainer)  //Same thing as the previous except we do it for a vector source.
    {
        size_t Size = VectorContainer.size();   //We're going to iterate the entire vector and serialize all the data in it into bytes. Write the data to our shared memory.
        for (size_t I = 0; I < Size; ++I)
            MemSerialize(Destination, VectorContainer[I]);
    }

    template<typename T>  //Deserialization is the opposite of serialization. We read from the destination back into the source in the reverse order! It's like a stack. Last in is the first out!
    void MemDeSerialize(T& Destination, unsigned char* &Source)
    {
        CopyMemory(&Destination, Source, sizeof(T));
        Source += sizeof(T);
    }

    template<typename T>
    void MemDeSerialize(T*& Destination, unsigned char* &Source, size_t Size)
    {
        CopyMemory(Destination, Source, Size);
        Source += sizeof(T);
    }

    template<typename T>
    void MemDeSerialize(std::vector<T> &Destination, unsigned char* &Source, size_t Size)
    {
        Destination.resize(Size);
        for (size_t I = 0; I < Size; ++I)
            MemDeSerialize(&Destination[I], Source);
    }

    Now that we have the essential serialization functions, our Body file will look like this:

    Let use assume we have a structure like the following:
    C++ Code:
    typedef struct
    {
        uint32_t ID;
        GLint SX, SY;
        GLuint Stride;
        GLint TriangleCount;
        GLboolean NullVertex;
        GLboolean ShowVertices;
        const GLvoid* VertexPointer;
        std::vector<Vector3D> Vertices;
    } Model;

    That structure stores all our data for Models. How do we write that entire thing into memory so Simba can read it? Well.. In our body, we'd implement a specialized serialization function as follows:

    C++ Code:
    void MemSerialize(unsigned char* &Destination, const Model &Source)
    {
        MemSerialize(Destination, Source.ID);
        MemSerialize(Destination, Source.SX);
        MemSerialize(Destination, Source.SY);
        MemSerialize(Destination, Source.Stride);
        MemSerialize(Destination, Source.TriangleCount);
        //MemSerialize(Destination, M.Vertices);
    }

    In the above, we serialize every member of our struct. Notice the ORDER that the data is stored in! This is crucial as we have to read in the reverse order when deserializing! So we turn the ID into bytes, the X, Y, Stride, etc.. into bytes and write it to our shared memory. How do we read that data back?

    C++ Code:
    void MemDeSerialize(Model* Destination, unsigned char* &Source)
    {
        MemDeSerialize(Destination->ID, Source);
        MemDeSerialize(Destination->SX, Source);
        MemDeSerialize(Destination->SY, Source);
        MemDeSerialize(Destination->Stride, Source);
        MemDeSerialize(Destination->TriangleCount, Source);
    }

    Here we read in the reverse order. It may not look like it since our function looks quite similar to the Serialization version. Here we read from the Serialized data and reconstruct our Model from that.
    Last edited by Brandon; 09-09-2012 at 03:36 PM.
    I am Ggzz..
    Hackintosher

  3. #3
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default

    Injecting Vs. Wrapping:

    What's the difference? Well there isn't much but I chose to use a wrapper and you'll see why very soon.

    Wrapping:

    As we saw earlier, our detour functions were in the form of wrappers. A wrapper to use was known at the time of reading, as a function with the same parameters, same return value but a different prefixed name. That was the only difference described between the wrapper and the original function.

    Now we will talk about Wrapper DLL's. I know wtf right? Well it's the exact same concept.

    A wrapper DLL is a copy of the original DLL. Throughout this entire tutorial, you've been writing a wrapper DLL without even noticing it! Your wrapper DLL will "wrap" specific functions within it and be loaded. Your wrapper DLL is a middle-man DLL and whenever the game wants to load OpenGL, it will load your DLL which will intern load the original OpenGL.dll

    How does this happen? Well remember that .Def file I spoke of? That's the key to this. Our wrapper dll has wrapper functions for every single OpenGL32 function from the original. It's an exact copy except they're wrappers inside it. Next it redirects all calls from the original to ours. Well how is that done?

    Here's an example of a .def file that does the work for us:
    C++ Code:
    LIBRARY OpenGL32   //We give our library the exact same name as the original!
    EXPORTS    //Our list of exports follow this call.

    glAccum                    =    GLHook_glAccum;
    glAlphaFunc                =    GLHook_glAlphaFunc;
    glAreTexturesResident      =    GLHook_glAreTexturesResident;
    glArrayElement             =    GLHook_glArrayElement;
    glBegin                    =    GLHook_glBegin;
    glBindTexture              =    GLHook_glBindTexture;
    glBitmap                   =    GLHook_glBitmap;
    glBlendFunc                =    GLHook_glBlendFunc;
    glCallList                 =    GLHook_glCallList;
    glCallLists                =    GLHook_glCallLists;

    In the above, All calls to glAccum will be sent to GLHook_glAccum first. GLHook_glAccum will then use the information, log it, do whatever with it and then pass it to the original glAccum.

    Lost yet? GLHook_glAccum is a middle-man between the game and the original opengl.dll. The equal sign is said to redirect calls! We do this for all 362 original functions so that all their calls go to our DLL first; which in turn passes it off to the original.

    Next we place our DLL in the same folder as the game. Why? For portability reasons, there isn't any game that does:
    "LoadLibrary("C:/windows/system32/opengl32.dll")"
    ..

    No game programmer is stupid enough to do that as some systems do not have system32. Instead, they do:
    "LoadLibrary("opengl32.dll")".
    The system will automatically give it the closest opengl32.dll file. This is good for us! Why? Because it will look in its own folder for OpenGl32.dll first! If it isn't found then it will search the system for it! Thus the game will end up loading ours first =]

    Whala we just successfully got the game to load our Wrapper/Hook DLL because it thinks that it's the original =].


    Injection:

    We haven't used this method but it is actually quite safe! Reason being is that if the game does decide to load specifically the system32 one, we can easily inject before that happens and force it to load ours! Yes that's right, we don't need to place it in the same folder!

    First let's get to the basics of injection. How do we inject? Where do we inject? Well when injecting, you always want a nice place to start and a place that is always going to be there no matter what game you inject into.
    The first is called Kernel32.dll.

    Every program on Windows MUST load Kernel32.dll. Microsoft has guaranteed that and they have guaranteed that the address of it will always be the same no matter what!

    Thus this is the best place to start injecting. The following code with comments explains how it's done:

    C++ Code:
    #include <Windows.h>
    #include <TlHelp32.h>   //Include this file for grabbing process info.
    #include <iostream>

    using namespace std;

    HANDLE hThread;   //Handle to a thread.
    void* pLibRemote;   //Pointer to a memory location.
    DWORD hLibModule;  //Handle to a module.
    HMODULE hKernel32; //Handle to Kernel32.dll
    HANDLE hProcess;   //Process handle.

    PROCESSENTRY32 GetProcessInfo(char* ProcessName)   //This function gets a snapshot of all running processes. It then searches those for the specified process and returns its info.
    {
        HANDLE hSnap = NULL;
        PROCESSENTRY32 Proc32 = {0};

        if((hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)) == INVALID_HANDLE_VALUE)
            return Proc32;

        Proc32.dwSize = sizeof(PROCESSENTRY32);
        while(Process32Next(hSnap, &Proc32))
        {
            if(!strcmp(ProcessName, Proc32.szExeFile))
            {
                CloseHandle(hSnap);
                return Proc32;
            }
        }
        CloseHandle(hSnap);
        return Proc32;
    }

    void Inject()  //This is how the injection is done.
    {
        char szLibPath[MAX_PATH];
        memset(szLibPath, 0, MAX_PATH);
        hKernel32 = ::GetModuleHandle("Kernel32");  //Get a handle to Kernel32.dll.

        PROCESSENTRY32 RSDLL = GetProcessInfo("JagexLauncher.exe");  //Find the process called JagexLauncher.exe.
        hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, RSDLL.th32ProcessID);
        strcat(szLibPath, "C:/Users/Brandon/Desktop/GLHook.dll");  //Open the process for writing and reading and get the path to our Wrapper/Hooking DLL.

        pLibRemote = ::VirtualAllocEx(hProcess, NULL, sizeof(szLibPath), MEM_COMMIT, PAGE_READWRITE);  //Allocate space for our DLL to be injected.
        ::WriteProcessMemory(hProcess, pLibRemote, (void*)szLibPath, sizeof(szLibPath), NULL);  //Write the location of our DLL into the specified process.

        hThread = ::CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE) ::GetProcAddress(hKernel32, "LoadLibraryA"), pLibRemote, 0, NULL);    //When Kernel32.dll is loaded, create a thread that loads our DLL and wait until it is successful!
        ::WaitForSingleObject(hThread, INFINITE);
        ::GetExitCodeThread(hThread, &hLibModule);
        ::CloseHandle(hThread);
        ::VirtualFreeEx(hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE);
    }

    void Terminate()  //This is just cleanup stuff.
    {
        hThread = ::CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE) ::GetProcAddress(hKernel32, "FreeLibrary"), (void*)hLibModule, 0, NULL);
        ::WaitForSingleObject(hThread, INFINITE);
        ::CloseHandle(hThread);
    }


    int main()  //Finally in our main we just inject.. We cleanup whenever the user decides to terminate the game. If the game terminates on its own then the dll cleans itself up.
    {
        Inject();
        cin.get();
        Terminate();

        return 0;
    }

    That's how it's done folks. Simply find the location of Kernel32.dll, write the path to your DLL into the process. The process will now load our DLL right after loading Kernel32.dll =].

    Both the wrapper and the injection methods are safe! The reason why I chose the wrapper method is because it can be used both ways. You can inject your wrapper DLL and it will work exactly the same as if the game loaded it. With injection only, you'd have to re-write almost all functions to get it to load (naturally by the game) as a wrapper.


    Simba & Lape Side:

    So we have our OpenGL Wrapper DLL and the game loads it successfully. It grabs the data and stores it in a memory location where Simba can read it and use it! Wooohoo. Now how do we get Simba to use it?

    Well first, Simba must load a DLL that can read from the memory location and interpret the data. The DLL will then give Simba all the data it needs in the form or pointers or however you decide.

    GLHook.dll will thus need to have the same serialization and deserialization files. I'm not going to write it again, just copy paste from the previous section of this tutorial. It will also need to have the same structures so that it can reconstruct the data into those. Finally, it will have the same memory communication files. This is so that it knows where to grab the data from.

    Thus the layout of GLHook.dll will look like the following..

    SimbaPlugin.hpp:
    C++ Code:
    #ifndef SIMBAPLUGIN_HPP_INCLUDED
    #define SIMBAPLUGIN_HPP_INCLUDED

    #include <windows.h>
    #include <iostream>
    #include "Models.hpp"     //Include the header that has all the model structs.
    #include "Serialize.hpp"   //Include the Serialization files.
    #include "SharedMemory.hpp"   //Include the Shared Memory files.
    #include "MapStructures.hpp"

    #define GLStatusSent        1     //You should remember these from before.
    #define GLStatusReturned    2

    #define GLHook_ViewPort     1
    #define GLHook_Position     2
    #define GLHook_Resizeable   3
    #define GLHook_PanelItems   4
    #define GLHook_GetText      5
    #define GLHook_GetModels    6
    #define GLHook_SetOverLay   7
    #define GLHook_SetStride    8
    #define GLHook_DrawAll      9
    #define GLHook_CompassAngle 10
    #define GLHook_Filter       11

    #define NumberOfExports     17   //How many functions are we exporting so that Simba can use?


    extern HINSTANCE hInstance;
    extern char* Exports[];

    GL_EXPORT int GetPluginABIVersion();  //Simba needs this.
    GL_EXPORT int GetFunctionCount();    //Same as above.
    GL_EXPORT int GetFunctionInfo(int Index, void*& Address, char*& Definition); //Same as above.

    //Finally, these are the C++ versions of the function declarations Simba will see when the DLL is loaded!

    GL_EXPORT bool GLHGetViewPort(int &X, int &Y, int &Width, int &Height);
    GL_EXPORT bool GLHGetPosition(int X, int Y, float &WorldX, float &WorldY, float &WorldZ);
    GL_EXPORT void GLHSetClientResizeable(bool Resizeable);
    GL_EXPORT void* GLHGetPanelItems(int &VectorSize);
    GL_EXPORT void* GLHGetItemsByID(int BaseID, int &VectorSize);
    GL_EXPORT void* GLHGetItemsByArea(int X1, int Y1, int X2, int Y2, int& VectorSize);
    GL_EXPORT void* GLHGetFonts(int& VectorSize);
    GL_EXPORT void* GLGetFontsArea(int X1, int Y1, int X2, int Y2, int& VectorSize);
    GL_EXPORT void* GLHGetModels(int& VectorSize);
    GL_EXPORT void* GLHGetModelsByID(uint32_t ID, int &VectorSize);
    GL_EXPORT void* GLHGetModelsByTCount(int TriangleCount, int &VectorSize);
    GL_EXPORT void* GLHGetModelsByArea(int X1, int Y1, int X2, int Y2, int &VectorSize);
    GL_EXPORT void GLHSetOverlay(bool Overlay);
    GL_EXPORT void GLHSetStride(int Stride);
    GL_EXPORT void GLHDrawAll(bool DrawAll);
    GL_EXPORT void GLHCompassAngle(float &DegreeAngle, float& RadianAngle);
    GL_EXPORT void GLHEnableFilter(bool EnableFilter, int FilterType, int ItemType, long ID, int R, int G, int B, int A, int X1, int Y1, int X2, int Y2);


    //These are some structs for Simba. C++ doesn't always play nicely so I've made a couple changes to the Simba side of things.

    typedef struct
    {
        int X, Y, Z;
    } Simba3DVector;

    typedef struct
    {
        GLuint ID;
        GLubyte R, G, B, A;
        GLfloat X, Y;
        GLchar Symbol;
        GLboolean Shadow;
        GLfloat Translate[3];
        GLint VX[4], VY[4];
        GLint TX[4], TY[4];
    } SimbaFont;

    typedef struct
    {
        GLuint ID;
        GLint SX, SY;
        GLuint Stride;
        GLint TriangleCount;
    } SimbaModel;

    extern std::vector<SimbaFont> ListOfSimbaFonts;
    extern std::vector<SimbaModel> ListOfSimbaModels;
    GL_EXTERN void __stdcall FontsToSimbaFonts(std::vector<FontInfo> &F, std::vector<SimbaFont> &SF);
    GL_EXTERN void __stdcall ModelsToSimbaModels(std::vector<Model> &M, std::vector<SimbaModel> &SM);

    #endif // SIMBAPLUGIN_HPP_INCLUDED

    Ahh so that's our header! It includes everything we need to get Simba up and running and using our data. Notice that there is a function called Fonts To Simba Fonts and Models To Simba Models? This is because some structs actually don't have the same alignment as the Simba versions of them. Thus these are simple POD (Plain Old Data) structures. These will have the same alignment as the Simba/Pascal "Record" versions of them.

    Now to implement the body:
    C++ Code:
    #include "SimbaPlugin.hpp"

    HINSTANCE hInstance = NULL;
    std::vector<SimbaFont> ListOfSimbaFonts;
    std::vector<SimbaModel> ListOfSimbaModels;

    char* Exports[] = {   //As you can see, the following is the Simba/Pascal definitions of the C++ functions. This basically converts the C++ functions to Pascal functions for use with Simba. They also act as wrappers. They have the same declarations and parameters as their C++ counterparts!

        (char*)"GLHGetViewPort", (char*)"Function GLHGetViewPort(var X, Y, Width, Height: Integer): Boolean;",
        (char*)"GLHGetPosition", (char*)"Function GLHGetPosition(X, Y: Integer; var WorldX, WorldY, WorldZ: Single): Boolean",
        (char*)"GLHSetClientResizeable", (char*)"Procedure GLHSetClientResizeable(Resizeable: Boolean);",
        (char*)"GLHGetPanelItems", (char*)"Function GLHGetPanelItems(var VectorSize: Integer): Pointer;",
        (char*)"GLHGetItemsByID", (char*)"Function GLHGetItemsByID(BaseID: Integer; var VectorSize: Integer): Pointer;",
        (char*)"GLHGetItemsByArea", (char*)"Function GLHGetItemsByArea(X1, Y1, X2, Y2: Integer; var VectorSize: Integer): Pointer;",
        (char*)"GLHGetFonts", (char*)"Function GLHGetFonts(var VectorSize: Integer): Pointer;",
        (char*)"GLGetFontsArea", (char*)"Function GLHGetFontsArea(X1, Y1, X2, Y2: Integer; var VectorSize: Integer): Pointer;",
        (char*)"GLHGetModels", (char*)"Function GLHGetModels(var VectorSize: Integer): Pointer;",
        (char*)"GLHGetModelsByID", (char*)"Function GLHGetModelsByID(ID: Cardinal; var VectorSize: Integer): Pointer;",
        (char*)"GLHGetModelsByTCount", (char*)"Function GLHGetModelsByTCount(TriangleCount: Integer; var VectorSize: Integer): Pointer;",
        (char*)"GLHGetModelsByArea", (char*)"Function GLHGetModelsByArea(X1, Y1, X2, Y2: Integer; var VectorSize: Integer): Pointer;",
        (char*)"GLHSetOverlay", (char*)"Procedure GLHSetOverlay(Overlay: Boolean);",
        (char*)"GLHSetStride", (char*)"Procedure GLHSetStride(Stride: Integer);",
        (char*)"GLHDrawAll", (char*)"Procedure GLHDrawAll(DrawAll: Boolean);",
        (char*)"GLHCompassAngle", (char*)"Procedure GLHCompassAngle(var DegreeAngle, RadianAngle: Single);",
        (char*)"GLHEnableFilter", (char*)"Procedure GLHEnableFilter(EnableFilter: Boolean; FilterType, ItemType: Integer; ID: LongWord; R, G, B, A, X1, Y1, X2, Y2: Integer);"

    };

    GL_EXPORT int GetPluginABIVersion()
    {
        return 2;
    }

    GL_EXPORT int GetFunctionCount()
    {
        return NumberOfExports;
    }

    GL_EXPORT int GetFunctionInfo(int Index, void*& Address, char*& Definition)
    {
        if (Index < NumberOfExports)
        {
            Address = (void*)GetProcAddress(hInstance, Exports[Index * 2]);
            #ifdef _MSC_VER
            #pragma warning(disable: 4996)
            strcpy(Definition, Exports[Index * 2 + 1]);
            //strcpy_s(Definition, Exports[Index * 2 + 1]);
            #else
            strcpy(Definition, Exports[Index * 2 + 1]);
            #endif
            return Index;
        }
        return -1;
    }

    GL_EXTERN void __stdcall FontsToSimbaFonts(std::vector<FontInfo> &F, std::vector<SimbaFont> &SF)
    {
        size_t I = 0;
        ListOfSimbaFonts.resize(F.size());
        for (std::vector<FontInfo>::iterator it = ListOfFontData.begin(); it != ListOfFontData.end(); ++it, ++I)
        {
            SF[I].ID = it->ID;
            SF[I].R = it->Colour.R;
            SF[I].G = it->Colour.G;
            SF[I].B = it->Colour.B;
            SF[I].A = it->Colour.A;
            SF[I].X = it->X;
            SF[I].Y = it->Y;
            SF[I].Symbol = it->Symbol;
            SF[I].Shadow = it->Shadow;
            SF[I].Translate[0] = it->Translate[0];
            SF[I].Translate[1] = it->Translate[1];
            SF[I].Translate[2] = it->Translate[2];

            for (int J = 0; J < 4; ++J)
            {
                SF[I].VX[J] = it->VX[J];
                SF[I].VY[J] = it->VY[J];
                SF[I].TX[J] = it->TX[J];
                SF[I].TY[J] = it->TY[J];
            }
        }
    }

    GL_EXTERN void __stdcall ModelsToSimbaModels(std::vector<Model> &M, std::vector<SimbaModel> &SM)
    {
        size_t I = 0;
        ListOfSimbaModels.resize(M.size());
        for (std::vector<Model>::iterator it = M.begin(); it != M.end(); ++it, ++I)
        {
            SM[I].ID = it->ID;
            SM[I].SX = it->SX;
            SM[I].SY = it->SY;
            SM[I].Stride = it->Stride;
            SM[I].TriangleCount = it->TriangleCount;
        }
    }

    GL_EXPORT void* GLHGetPanelItems(int &VectorSize)  //returns a pointer to a list of all Items/Textures from the shared memory.
    {
        double* Data = static_cast<double*>(RequestSharedMemory());  //First we ask for the pointer to the shared memory if it isn't already busy.
        Data[0] = GLHook_PanelItems;  //We write to the shared memory. We write that we want Items.
        Data[1] = GLStatusSent;   //We also write the status. We write that we are sending a request for data! Notice we are basically writing to an array.

        ListOfItems.clear();   //We clear our list and prepare for data.
        if (SharedDataFetched())  //If the wrapper dll successfully sent the data to use via the shared memory..
        {
            if (static_cast<int>(Data[2]) != 0)  //Data[2] lets use know if the data returned successfully.
            {
                unsigned char* SerializedData  = reinterpret_cast<unsigned char*>(&Data[3]);  //Data[3] is a pointer to the location in the shared memory that contains the info the wrapper gave us.
                MemDeSerialize(ListOfItems, SerializedData, size_t(Data[2]));  //We DE-serialize the bytes and reconstruct it into Models. Those models are stored in a vector.
                VectorSize = int(Data[2]);  //Data[2] is the pointer to the shared memory that contained the size of the data the wrapper gave us.
                return ListOfItems.data();  //Finally we give simba a pointer to the reconstructed data!
            }
            return NULL;
        }

        return NULL;
    }

    GL_EXPORT void* GLHGetItemsByID(int BaseID, int& VectorSize)
    {
        GLHGetPanelItems(VectorSize);  //We can now grab all Items from shared memory.
        std::vector<PanelItem> ItemsFound;  //We create a vector to hold all items that have a specific ID.
        for (std::vector<PanelItem>::iterator it = ListOfItems.begin(); it != ListOfItems.end(); ++it)
        {
            if (it->BaseID == BaseID)  //If the data has the same ID as the one Simba asked for, add it to the vector/list.
            {
                ItemsFound.push_back(*it);
            }
        }

        ListOfItems = ItemsFound;
        VectorSize = ListOfItems.size();  //Return the size of the list to simba.
        return ListOfItems.data();  //Give simba a pointer to the list.
    }

    Basically, we write to shared memory asking it for data. The wrapper dll will then look at its collection of data and write back whatever we requested. We then read the location that it wrote back to and give it to Simba.

    Thus the steps are as follows:

    1. Write a letter to the wrapper begging it to give us Models on array index 1.
    2. Tell it that we are requesting data on array index 2, not sending.
    3. Wrapper will check array index 1 to see what the letter is about.
    4. Wrapper will check array index 2 to see if it's a request.
    5. If it was a request, it will write to index 3 the serialized data that was requested.
    6. It will write to index 2 that it is Sending data, not requesting.
    7. It will write to data[4] the size of the data it sent.
    8. SimbaPlugin will read data[2] to see if it was actually sent.
    9. SimbaPlugin will read data[4] to see the size of the data sent.
    10. Simba Plugin will deserialize the data in data[3] and reconstruct/use it.

    The 10 Simple steps can be seen in the following diagram (Please note that it's up to you what channel/index you want the data on:





    Writing An Include. Functions, Pointers, Etc..:


    Ahh so we finally got to the Simba/Pascal side of things. Don't worry, we aren't out of the C++ world yet as that's quite important.

    Let's discuss Pointers first as that seems to be what most people have trouble on.

    Pascal script doesn't allow Pointers. Thus we cannot use the data that SimbaPlugin gives us! OMG!! Well the solution is Pascal.. Not Pascal Script. Lape is a Pascal Interpreter and attempts to emulate Pascal itself!
    Thus in Lape, we can use pointers.

    Lets dive in shall we?
    The first thing I'll teach about pointers is the difference between the three kinds. Yes Pascal has 4 kinds of pointers/reference operators whereas C++ has 3.

    The first kind is the address of operator. In Pascal, there are two address operators. Well technically one but for our purposes I'd like you to know that you already know one of them.

    It is the var operator. The var operator means pass by reference. This operator can be used to return multiple values from a function in pascal. In C++, this is almost equivalent to the & (address-of) operator. Thus when translating a function from C++ to Pascal, if you see int &X, you must replace that with var on the pascal side.


    The second operator is the @ operator. Also referred to as the address-of operator. This allows a user to grab the address of a function/procedures. In C++, this is equivalent to the typedef stuff we did earlier. It essentially allows us to create function pointers and variable pointers! Also equivalent to &X when used on a variable in C++.


    The third is the ^ operator. This operator can be used as a suffix or a prefix. This operator is equivalent to the pointer/de-reference * operator in C++.
    When used as a prefix, it is said that the caller is a pointer to a type of callee. When used as a suffix, it is said to de-reference the data that the pointer points to.

    I know that was quite confusing so I'll include some code:

    Simba Code:
    type pByte = ^Byte;   //pByte is a pointer to a Byte.
    type pInt  = ^Integer; //pInteger is a pointer to an int.
    type pBool = ^Boolean;  //Same concept..
    type pFloat = ^Single;   //Same concept.. Note a Single is equivalent to a float in C++.

    var
       Foo: ^Integer;   //Foo is a pointer to an integer.
       Meh: pInt;        //Exactly the same as above. Also a pointer to an integer.

    Those are pointers to a type as you can see. How do we de-reference? Well we use the ^ operator as a suffix instead of a prefix!

    Simba Code:
    var
        Foo: ^Integer;
        Boo: Integer;

    begin
        Boo := 191;
        Foo := @Boo;  //Make Foo pointer to Boo.
        WriteLn( Foo^ );  //Should print 191. Notice that ^ is after Foo.
    end.

    When used as a suffix, it essentially says: "Get the data pointed to". Foo points at some data. Foo^ is equivalent to the data that Foo points to. Thus Foo^ == 191 or Foo^ == Boo.

    Finally the Fourth type of Pointer is actually "Pointer".

    A "Pointer" in pascal can point to ANYTHING! It is equivalent to a void pointer in C++ (void* )..
    A Pointer has no type in pascal. Thus the following can be used:

    Simba Code:
    var
       Foo: Pointer;
       Bleh: ^Integer;
       Gah: Integer;

    begin
       Gah := 177;
       Foo := @Gah;  //Foo points to Gah.
       Bleh := Foo;    //Bleh Points to Foo/Gah.
       Writeln(  Bleh^  );   //Should print 177.
    end.

    Another example is as follows:

    Simba Code:
    type TModel = record
      ID: Cardinal;
      SX, SY: Integer;
      Stride: Cardinal;
      TriangleCount: Integer;
    end;

    var
       Foo: Pointer;
       Meh: ^TModel;

    begin
       Foo := GLH_GetModelByID(145252);  //This function returns a "Pointer".
       Meh := Foo;
       writeln(Meh^);  //Would print everything about the model.

       //The above is the same as:
       Meh := GLH_GetModelByID(145252);
       writeln(Meh^);
    end.

    The difference in the two examples above is that Foo can be iterated whereas Meh cannot! Thus Foo is probably more useful. Example is that if the function returned a pointer to an array of models, Foo will have to be used!

    Example:
    Simba Code:
    type TModel = record
      ID: Cardinal;
      SX, SY: Integer;
      Stride: Cardinal;
      TriangleCount: Integer;
    end;

    type TModelArray = array of TModel;

    Function GLHook_GetAllModels: TModelArray;
    var
      Ptr: Pointer;
      Size, I: Integer;
      ModelPtr: PModel;
    begin
      Ptr := GLHGetModels(Size);  //As you can see, grab a pointer to an array of models.
      SetLength(Result, Size);
      For I := 0 To Size - 1 Do
      begin
        ModelPtr := Ptr;
        Result[I] := ModelPtr^;  //Dereference it and add it to the array.
        Ptr := Ptr + sizeof(TModel);  //Move onto the next model in the pointed array.
      end;
    end;

    As you can see above, Pointer was very useful as that was the only way we could move to the next model in the array pointed to.

    I really hope you guys enjoyed my extremely LONG tutorial! Please leave some feedback. If you request any knowledge, please leave a comment. If I did not see your comment or you want to speak directly with me, please leave me a PM and I'll get back to you whenever I can.

    For More Info on GLHook see the source: https://github.com/Brandon-T/GLHook

    Also note that you should NOT have to write all 362 functions at all! I have a generator that does that in the same link. Just click on OGL-Editing to see how it's generated. The source is: Click Here To View The Link.

    I'm sorry if my tutorial isn't very pretty. I didn't have much time. Also didn't have much space to put everything so if anything is hard to understand/read, just leave some feedback and I'll try to fix it up.
    Last edited by Brandon; 09-09-2012 at 05:47 PM.
    I am Ggzz..
    Hackintosher

  4. #4
    Join Date
    Feb 2006
    Location
    Tracy/Davis, California
    Posts
    12,631
    Mentioned
    135 Post(s)
    Quoted
    418 Post(s)

    Default

    1st < Spam < Noob <
    thanks for tut

  5. #5
    Join Date
    May 2008
    Location
    Mental Hospital
    Posts
    414
    Mentioned
    4 Post(s)
    Quoted
    42 Post(s)

    Default

    Aww. I read everything not knowing it was incomplete

  6. #6
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default

    Quote Originally Posted by Noob King View Post
    Aww. I read everything not knowing it was incomplete
    There well enjoy that long ass read ={ There really isn't a way I can pretty it up. Everything is just the raw info as is. I can clean it up a bit and make it more pleasing/readable but meh.. I'll do that later or something. Around 90% of the info you need to know is there now. That should be enough to get anyone started. I think I may have over commented the code lol. Also note that some definitions may be different for you but I wrote it how I think/see it. You'll understand it better I believe.

    The tutorial itself is aimed at future OpenGL Hook developers. The Simba part of it is for any include developers. I teaches the basics of pointers and lape. Of course I'm not going to cover every single thing as a scripter should already be able to adapt to Lape/Pascal from Pascal Script.

    This is about my fourth cpp/ps tutorial. I hope someone takes interest unlike my others lol.
    Last edited by Brandon; 09-09-2012 at 05:50 PM.
    I am Ggzz..
    Hackintosher

  7. #7
    Join Date
    Aug 2008
    Location
    London, UK
    Posts
    456
    Mentioned
    0 Post(s)
    Quoted
    1 Post(s)

    Default

    This is amazing Brandon. I wish that I had had this rather than spending many days scouring the internet!

  8. #8
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default

    Quote Originally Posted by ReadySteadyGo View Post
    This is amazing Brandon. I wish that I had had this rather than spending many days scouring the internet!

    LOL I know what you mean =] I wish I did too lol but luckily we now have some documentation to work with. Hopefully people read it though LOL it is quite long and seems discouraging lol.
    I am Ggzz..
    Hackintosher

  9. #9
    Join Date
    Aug 2007
    Location
    Colorado
    Posts
    7,421
    Mentioned
    268 Post(s)
    Quoted
    1442 Post(s)

    Default

    Geez :S. That's a well-written guide Brandon, very nice job. You put in a long time to make this. So thanks for this, I'll read over some of it and see if I can get a better understanding of OpenGL.

    Current projects:
    [ AeroGuardians (GotR minigame), Motherlode Miner, Blast furnace ]

    "I won't fall in your gravity. Open your eyes,
    you're the Earth and I'm the sky..."


  10. #10
    Join Date
    Feb 2012
    Location
    Wonderland
    Posts
    1,988
    Mentioned
    41 Post(s)
    Quoted
    272 Post(s)

    Default

    Wow.

    Read through this for the first time; I'm blown away. Granted I'm new to studying code in general, but the use of your c++ code is also quite interesting too, as I've been attempting at picking that up from cplusplus docs.

    This line, "These two use and abuse me ={ haha! Makes the learning process that much more fun!

    Thanks for creating such a comprehensive guide for the OpenGL beta work you've created. This will definitely have me occupied for a while, trying to learn and utilize the tutorial! I'll probably end up asking quite a few questions later down the road too!

    Time to save this thread and read it again and again later!
    Cheers,
    Lj!

  11. #11
    Join Date
    Aug 2007
    Location
    England
    Posts
    1,038
    Mentioned
    0 Post(s)
    Quoted
    6 Post(s)

    Default

    Very very nice, Brandon. I'm going to be reading all this later for sure. I've read so much of it and so far I understand everything your saying, yay What I've learned about C++ has definitely helped with that
    Much appreciation to you for doing this tut.
    Rep+
    Today is the first day of the rest of your life

  12. #12
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default

    Quote Originally Posted by Jakkle View Post
    Very very nice, Brandon. I'm going to be reading all this later for sure. I've read so much of it and so far I understand everything your saying, yay What I've learned about C++ has definitely helped with that
    Much appreciation to you for doing this tut.
    Rep+
    Glad you guys are taking the time to read it. I've gotta change a couple things this week though. This is for smart compatibility. That will be the new section added. I've got it working in smart check the gl hook thread for more info on that.

    Also going to add a Linux section since it has to be cross platform.
    I am Ggzz..
    Hackintosher

  13. #13
    Join Date
    Dec 2011
    Location
    Hyrule
    Posts
    8,662
    Mentioned
    179 Post(s)
    Quoted
    1870 Post(s)

    Default

    Quote Originally Posted by Brandon View Post
    Glad you guys are taking the time to read it. I've gotta change a couple things this week though. This is for smart compatibility. That will be the new section added. I've got it working in smart check the gl hook thread for more info on that.

    Also going to add a Linux section since it has to be cross platform.
    You are the bomb! Will slowly be reading through this...one section down what up!

  14. #14
    Join Date
    Aug 2007
    Location
    England
    Posts
    1,038
    Mentioned
    0 Post(s)
    Quoted
    6 Post(s)

    Default

    A few quick questions Brandon:
    I noticed that there is a version of code:blocks that comes with MinWG and was wondering if that's okay to use rather than installing MinWG separately or is it best to use the one without and then install MinWG separately ?
    If separate, is that because, like you said, it's "a custom backend: Mingw." that needs to be made ?

    Edit: I think I answered my own question ? lol
    Last edited by Jakkle; 09-11-2012 at 05:39 PM.
    Today is the first day of the rest of your life

  15. #15
    Join Date
    Feb 2006
    Posts
    3,044
    Mentioned
    4 Post(s)
    Quoted
    21 Post(s)

    Default

    Great Job Brandon!

    ~Home

  16. #16
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default

    Quote Originally Posted by Jakkle View Post
    A few quick questions Brandon:
    I noticed that there is a version of code:blocks that comes with MinWG and was wondering if that's okay to use rather than installing MinWG separately or is it best to use the one without and then install MinWG separately ?
    If separate, is that because, like you said, it's "a custom backend: Mingw." that needs to be made ?

    Edit: I think I answered my own question ? lol
    Lol no install the mingw because the old one uses an outdated compiler and the latest supports C++11. So far I've been holding off and limiting this project to x98 in order to support msvc.
    I am Ggzz..
    Hackintosher

  17. #17
    Join Date
    Aug 2007
    Location
    England
    Posts
    1,038
    Mentioned
    0 Post(s)
    Quoted
    6 Post(s)

    Default

    Quote Originally Posted by Brandon View Post
    Lol no install the mingw because the old one uses an outdated compiler and the latest supports C++11. So far I've been holding off and limiting this project to x98 in order to support msvc.
    Okay I see, thanks for clearing that up. Must have been the reason I couldn't compile the source when I tried to a while back lol.
    Well I've installed both of them already anyway, on a computer I'm going to be using solely for scripting/programming.
    I'm going to go through all this tut then I will have a look at the gl hook thread you mentioned.
    Today is the first day of the rest of your life

  18. #18
    Join Date
    Aug 2007
    Location
    Colorado
    Posts
    7,421
    Mentioned
    268 Post(s)
    Quoted
    1442 Post(s)

    Default

    Quote Originally Posted by Brandon View Post
    Glad you guys are taking the time to read it. I've gotta change a couple things this week though. This is for smart compatibility. That will be the new section added. I've got it working in smart check the gl hook thread for more info on that.

    Also going to add a Linux section since it has to be cross platform.
    Did it take much editing to SMART itself to make this possible? Also, I assume Ben hasn't seen any of this yet? By the way, 5-star-ified.

    Current projects:
    [ AeroGuardians (GotR minigame), Motherlode Miner, Blast furnace ]

    "I won't fall in your gravity. Open your eyes,
    you're the Earth and I'm the sky..."


  19. #19
    Join Date
    Feb 2011
    Location
    The Future.
    Posts
    5,600
    Mentioned
    396 Post(s)
    Quoted
    1598 Post(s)

    Default

    Quote Originally Posted by Flight View Post
    Did it take much editing to SMART itself to make this possible? Also, I assume Ben hasn't seen any of this yet? By the way, 5-star-ified.
    Nope not much editing. At first I made the mistake of making Simba load the OGL then I had to undo that and write it so that Smart loads it

    Basically works like this:

    Get Simba's Plugin path.
    Pass that as an argument to Main along with all Smart's other arguments.
    Change Total Number of Arguments passed and StrLength.
    Load Path via the Argument only if the file is found. Checked using .isFile();

    Ben's hint was "Look at how the JNI plugin is loaded". So it's loaded the same way as the JNI plugin.
    I am Ggzz..
    Hackintosher

  20. #20
    Join Date
    Mar 2012
    Posts
    78
    Mentioned
    0 Post(s)
    Quoted
    3 Post(s)

    Default

    I know almost no programming, yet I *felt* like I understood that all (I could understand what was happening, and why to an extent). Thanks for that

  21. #21
    Join Date
    May 2007
    Location
    UK
    Posts
    4,007
    Mentioned
    1 Post(s)
    Quoted
    12 Post(s)

    Default

    Got this downloaded to my phone and shall spend the next few days reading it

    Thanks
    -Boom

  22. #22
    Join Date
    Jan 2012
    Posts
    915
    Mentioned
    13 Post(s)
    Quoted
    87 Post(s)

    Default

    Brandon, you inspired me to learn C++. I'll look at this once I get a little bit better with.. everything. I'm only experimenting with variables, and math. Lol. Just started today, and I'm going to post my new program for some CnC.

    Thanks for everything you've done here, Brandon.


    Edit: Devil's post.

  23. #23
    Join Date
    Jun 2016
    Posts
    10
    Mentioned
    0 Post(s)
    Quoted
    3 Post(s)

    Default

    Very nice tutorial, can't wait to screw around with this!

  24. #24
    Join Date
    Mar 2013
    Posts
    35
    Mentioned
    0 Post(s)
    Quoted
    8 Post(s)

    Default

    This looks great, much more advanced than reflection in my opinion.

  25. #25
    Join Date
    Aug 2017
    Posts
    5
    Mentioned
    0 Post(s)
    Quoted
    0 Post(s)

    Default

    Pretty basic, good tutorial man.

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •