The old version of the framework was first developed in 2016, and at the time seemed like the best project I had written so far in terms of flexibility, reusability and certainly from a design point-of-view. However, over the years it got chopped and changed so much that it became a bombsite of code; it worked, but was not pleasant to work with.
One thing in particular that became apparent over time, was that developing cheats with the old framework was quite time consuming. This was because when creating the layout for a menu, all the element creation had to be done manually line-by-line by the cheat developer. In some cases, this could take up hundreds and hundres of lines of code, adding both bloat and creating a messy project. So someting I wanted to address in the new framework (its called V5 btw) is the time it takes to create a GUI...
Enter the GUI Studio... This new tool allows a developer to create a menu in real-time using an graphical user interface, save it to a GUI Document, then load it into the program in one line of code. Here's a short video demonstrating the studio:
There are a lot of elements missing currently, but you can see the basic creation of a window, two text boxes and a button. The studio allows for the easy parenting of an element to any other element, and correctly handles relative positions. It also features drag snapping for easily aligning objects along a axis. Once the GUI document is exported, it gets converted into an XML document that looks like this:
<?xml version="1.0" encoding="utf-8"?> <document> <meta version="100" /> <element name="Window_0"> <type>V5Interop.Window</type> <enabled>true</enabled> <hidden>false</hidden> <position_x>187</position_x> <position_y>158</position_y> <size_x>400</size_x> <size_y>210</size_y> <parent /> <backgroundcolour>34,35,43,255</backgroundcolour> <label>Please Login</label> </element> <element name="tbUsername"> <type>V5Interop.TextBox</type> <enabled>true</enabled> <hidden>false</hidden> <position_x>46</position_x> <position_y>25</position_y> <size_x>300</size_x> <size_y>20</size_y> <parent>Window_0</parent> <label>Username</label> </element> <element name="tbPassword"> <type>V5Interop.TextBox</type> <enabled>true</enabled> <hidden>false</hidden> <position_x>46</position_x> <position_y>64</position_y> <size_x>300</size_x> <size_y>20</size_y> <parent>Window_0</parent> <label>Password</label> </element> <element name="Button_0"> <type>V5Interop.Button</type> <enabled>true</enabled> <hidden>false</hidden> <position_x>267</position_x> <position_y>144</position_y> <size_x>100</size_x> <size_y>20</size_y> <parent>Window_0</parent> <fontsize>12</fontsize> <label>Login</label> </element> </document>
The beauty of this system is that if a developer wants to change how the menu system works for their cheat, they can adjust it with the studio, save to XML, then upload to the server. No changes are required to the code for the GUI to be changed.
Some of you may be thinking "This looks a lot like OSH GUI". Well, you're not entirely wrong. I was so impressed with the designer from OSH GUI that I wanted to replicate its ease of use and the properties panel. Gladly though, that's the extend of the similarities. While OSH uses a secondary managed renderer for displaying the elements, what we did was to pipe our D3D11 renderer through to the managed canvas, thereby only needing one renderer.
One of the hurdles we had to overcome was the pathway of managed -> unmanaged code. One option would be to pInvoke everything we needed, but this would have been a huge task as the V5 Framework is quite extensive. Instead, we created an CLR interop library that acts as a middle man. For example, let's look at the code that creates a text box. Firstly in our managed front end we issue the CreateUITextBox call to the interop library:
And the interop part:
In the interop library, we perform the necessary conversion from the label to wchar_t*, then we issue the CreateElement call to the native back-end. CreateElement returns a pointer to a V5::TextBox, given a parent element, a initial position, and a label. Using this pointer, we create a managed TextBox object, and give it a reference to the native pointer. The managed TextBox simply acts as a wrapper around the native pointer so we can still use OOP on the text box from managed code. After this, we add the managed text box to the _items List and return it. The native back end of course keeps its own list of created elements, but that's not important.
That's pretty much it, all the coder has to do on their end is one line of code:
Where this can either be an existing gui Element to parent all the objects to, or null.
I hope you enjoyed this one, next time the wait shouldn't be as long. If there's something in particular you'd like me to cover, please reply and let me know.