First App Build - RagnarokInfo
So I'm finally getting around to this and decided to write up something about, what I consider, one of the most fun and inspiring projects I've done outside of college and before I got my current job as an I.T. Specialist. Disclaimer: The app is no longer functional due to reasons I will explain in the content below, so please read! Unfortunately I was also very short-sighted with this project and never really took any screenshots of it in action (boohoo) so all screen captures are from its current non-functional self, lame I know...
Update 6/8/2021 - Through some new developments I was able to get this application working again. I will not discuss how here, but I've updated the screenshots. Cheers!
Background
I feel I should give a bit of backstory here and nerd out a bit as to where my inspiration comes from. 2003 was an interesting time I was a junior in high school and our family had just gotten broadband internet (OMG). Of course like any high school into gaming and computers this was also a great time for online gaming, around this time we started getting more and more MMORPGs in the mainstream market. One of these being Ragnarok Online, an MMO birthed from a Korean comic series written by Lee Myung-jin. I absolutely fell in love with this game in 2003 and spent many many hours binging play sessions. Unfortunately for me I quickly graduated high school the next year in 2004 and stepped into the world as an adult for the first time, worrying about adult things like finance so I dropped this game in favor of, you know, pursuing a college education. At the time a subscription fee of $15/month was a bit steep for a poor college student still living at home and in need of financial aid to pay for tuition. Over the next 7 years I'd find myself taking a hiatus from college, joining and joining the military, and found other ways of getting my fix of RO. This range from returning temporarily to the official paid subscription for some time, dropping out, joining privately run servers, and at one point even hosting my own local server to just mess around in out of boredom. Suffice it to say RO has been a part of both my childhood and adulthood. I finally returned to the game as a regular player when it switched to a free-to-play model in 2011. It was also around this time I started thinking of my life outside of the military and what I should do, the most direct choice was to return to school and finish my studies after my enlistment ended and I did just that in 2012. Fast forward about 5 years I finally finish my undergraduate studies and graduate with a degree in Computer Science, and being the hotshot expert Software Engineer I am (being facetious here) I embarked on my first solo project as a new grad.
The Idea
RO being a fairly old game that seldom provided an improved UX for its players there were always some useful 3rd party tools that were developed by its loyal fanbase. One of the biggest endeavors into this was a little software called RCX. What RCX did for players was amazing it provided such functions as:
- In-game overlays for enemy HP
- EXP calculator for calculating your leveling efficiency
- Pet hunger status UI because the game's in-game UI was awful and intrusive
- Chat logging
- CPU throttling to use less CPU cycles when the game is not in focus
- Mouse freedom for those of us who played in Windowed mode, this allowed us to freely move the mouse in and out of the window without the need to Alt-Tab.
This was an extremely useful and widely used tool by the community; however, it was developed and updated by a single Japanese developer who after a few years stopped updating it. Luckily for us the the developers of the actual game eventually added a few of these features straight into the game itself after heavy rallying from its players. We eventually all received the overlays for HP, CPU throttling, and Mouse freedom features built straight into the game itself! However, my most favored features of the EXP calculator and Pet hunger statuses were still not given the same treatment. This is where my first solo venture into developing an app came about.
Beginning Phases
My initial thoughts on undertaking this project was since this is an online game, it would maybe be possible to intercept and capture all the information I required to create an EXP calculator and pet hunger information from packets captured from the client to server or from the server to the client, in any case I decided to do some snooping with Wireshark to see if any of this data is actually contained in unencrypted packet payloads. Long story short after about half a day of digging and tedious and thorough testing I came up with nothing. Indeed packets were flowing consistently between the server and game client but the payloads of which my novice self was not able to extrapolate any useful data from. Back to the drawing board.
This is where I remembered there's a nifty tool out there named CheatEngine, designed to be a GameGenie or GameShark of sorts for PC games and one that is actively still being developed and updated. Yay! So how this software works is it'll attach itself so a running process and be able to peek inside its memory stack, freakin' awesome. Time to do some digging. Of course first thing I try to do is with the game client running and me being logged into my game account, I attach CheatEngine to the running process and try to do a few searches in the games memory. Of course the basics of any online game you have a character you create usually with a name of your choosing. First attempt success! Many string sized variables are pulled up by CheatEngine containing my characters name, though which one would be useful to me is beyond me, after stewing on it for a while it hit me, if I change characters in the game technically the value in memory should also change right? BINGO! I've found the first memory address I want to capture, and of course I jot this down in a new .txt file. After a day of this back and forth I finally found all the potential memory addresses I could use. My final notes on the matter as follows:
/********************
* RO Memory Hunt Patterns
*********************/
Logged in value = 768, Char select = 0
Pet out false = -1, true = non-zero and non-negative integer
Homu out false = 0, true = 1
My account #{REDACTED} <--- Get this from ROPD or elsewhere...
/********************
* Finding memory addresses of known values (Base value + #Bytes)
* These are the base values required to possibly be updated with every ragexe.exe update
*********************/
Account = (4-byte Int)Account
/********************
* The following values are hard coded into RO info, this is just an FYI
*********************/
Name = Account + 23096 bytes // Changed to + 23096 on 04/05/2019
LoggedIn = Account + 364 bytes
BaseExp = Account + 20 bytes
JobExp = Account + 32 bytes
BaseLvl = Account + 36 bytes
JobLvl = Account + 44 bytes
BaseRequired = Account + 24 bytes
JobRequired = Account + 28 bytes
HomuName = Account + 14132 bytes
HomuLoyalty = Account + 14220 bytes
HomuHunger = Account + 14236 bytes
HomuExp = Account + 14228 bytes
HomuRequired = Account + 14232 bytes
HomuOut = Account + 14272 bytes
PetName = Account - 1512 bytes
PetLoyalty = Account - 1464 bytes
PetHunger = Account - 1468 bytes
PetOut = Account - 1476 bytes
These notes were updated well after the first successful build of my application so please take it that I was not as efficient as shown in the beginning, namely the memory offsets I didn't determine until a few iterations of the application had been built. In the beginning I simply had the exact memory addresses directly from the client, and this would change each time the game client received any large updates...imagine hunting these down each and every time this happened for the number of values I needed. Eventually I realized like any good OOP much of this data is likely stored in class objects i.e. a character, pet, and homunculus class. And being that these objects likely do not get drastically changed the instance variables will likely carry the same datatypes from iteration to iteration of the game client, which is how I eventually just ended up calculating offsets, saving me the time of hunting down each and every memory value for each and every data point I needed for my application any time the game client was updated.
The First Build
Having all the data I required, I quickly moved to mock up some test application, and of course what better way to do this than creating a console application, not requiring any fancy UX, just plain jane WYSIWYG text based values printed into a command window. Not really wanting to use any languages I was already familiar with I decided to give a shot using Visual C# since I did have a Professional license for Visual Studio 2015 at the time. This also proved to be handy as well since it allowed me to use the .NET framework and just import libraries already built into Windows to attach the application to a running process! Yay process hacking! With the following code snippet it made this very straightforward:
internal static class UnsafeNativeMethods {
[DllImport("Kernel32.dll")]
public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("Kernel32.dll")]
public static extern bool ReadProcessMemory(IntPtr hProcess, uint lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead);
[DllImport("Kernel32.dll")]
public static extern bool WriteProcessMemory(IntPtr hProcess, uint lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead);
}
These functions were then used to attach the app to the game client process in either read or write mode depending one what I needed in order to extract or manipulate the values in process memory. Feel free to check out the code itself here to see how I did this. Now that I determined the app is able to extract the data from the game client it was time to get the core functions of the program working.
The EXP Calculation Algorithm (Part I)
Now comes arguably the most complicated part but also the part that I had the most fun working, stressing out on, and spent the better part of a week perfecting. What's the significance of this you ask? Well a few things to note given the available data. Firstly, the game does not provide an easy way to simply pull the exact amount of EXP gained from memory. For this I had to extrapolate the EXP gained using the data made available by the client.
- Only the current value of EXP the character has for both Base Level and Job Level EXP is stored in memory, the last amount of EXP earned is not stored in any apparent variables. Need to do some simple math here to store and calculate the change in EXP as this value is updated in memory.
- In order to track progress at the current level, the amount of required EXP to reach the next level is required. Luckily this information is already made available.
- The characters current level is also needed to accurately track the amount of exp gained between levels, more on this in a moment when I start to talk about bugs encountered.
Within the current level it's very simple to calculate EXP gained, as I will demonstrate with some math, also note by gained it is also possible to 'gain' a negative amount of EXP. Assuming at the time the application is launched and it reads the amount of EXP a character has is at 1000:
prevExp = 0
currentExp = 1000
if currentExp not equal to prevExp
gained += currentExp - prevExp
prevExp = currentExp
start timer
This would be the simplest way to calculate gained exp. However; this quickly breaks when taking a few things into account.
- Current EXP is not additive across different levels i.e. if a character surpasses the EXP required to level up the current EXP resets to 0 and begins accruing towards the next level. When this happens the simple gained EXP difference quickly calculations quickly fails and current EXP will be less than the previous EXP value causing a drastic and incorrect calculation of a loss in EXP.
- The calculator also needs to take into account 2 different sets of EXP, remember above there are 2 different EXP values to account for, Base EXP and Job EXP.
After accounting for this and getting the algorithm mostly working I sought out to take my concept work and finally build a UX around it, development of this algorithm was hardly finished at this point more on this in Part II. Luckily with Visual C# this was almost as simple as taking my current source code and dropping it in a new WinForms and eventually a WPF application.
The User Experience
Like I mentioned in the beginning I wasn't able to screenshot much of my work throughout this progress, which is definitely something I would like to improve on in future endeavors. But I figure what better way to show UX than to just well...show it. I really wish I could show this in action, but I digress. The application started off in many forms, I first created the concept via a console application that simply displayed numerical and string values in a console window. I then moved the underlying code to a WinForms application, very barebones UX no window decorations or colors, very Win95-esque. Once that came to life I finally ported everything over to a WPF application where I could implement more modern looking UI elements into the application views.
Update: 6/8/2019 - Removed this window from the application since the Classic server is no longer in service, also the Sakray test server is not enabled all the time. Replaced with a new start window.
Ragnarok Online being a game with multiple servers, more specifically the International game service has 3 servers: Renewal, Classic, and a test server named Sakray. As such I made the application able to attach to all 3, user selectable upon launch of the app.
After selecting a server, provided that the appropriate game client is running the Character EXP Calculator will be the main application window. On the bottom left it will list the names of currently logged in clients by pulling character names from their respective process. If multiple game clients of the same server are detected the list will automatically populate with all running processes. This allows users to quickly toggle between viewing different characters, very useful for what we call "leeching" in the game world where a main character is used to defeat mobs for EXP while a secondary character is placed in the same map to leech off of the resulting EXP. If a character is logged in the title bar of the application will actually also show the current base and job levels of the character and once it begins calculating EXP gained a timer with the elapsed time will also appear. The "Refresh" button resets the calculator and zeroes out all the values. If no active character is logged in the status will show "Not logged in" as in the screenshot below, otherwise it will be populated with the character's name. And of course the background text that has been beautifully crafted in pastel blue and purple shows the current percentage progress of the character's current level.
Update 6/8/2021 - Added "RO++" button to include functionality for launching additional game clients.
Much like the previous window I also included a view for both pets and homunculi. Fortunately there was no complex business logic behind this view, it simply displays values pulled directly from in-game memory. I also included a nifty dinner bell when set to ring whenever either a pet or homunculi reach a hunger status set by the user. You could also mute the sound by marking the checkbox.
And finally the settings menu, with a slider bar to adjust the opacity of the beautifully colored background text in both the ROInfo view as well as the PetInfo window.
Updated 6/8/2020 - Added game client location to be able to launch a new game client with the "RO++" button in the main application window.
For a simple application like this, I felt this was a sufficient UX, it's simple, straightforward, and just a little bit stylish (IMO.)
The EXP Calculation Algorithm (Part II) / Testing / Debugging
Now all pretty and new I elected to distribute minimal copies of the application to close friends who I had help me bug test. Almost immediately we were met with very glaring issues with the algorithm of them the 2 that stood out as what would make or break this project:
- As mentioned earlier it was at this point we discovered the negative EXP being reported when a character would level up.
- When switching characters EXP gained would also sometimes report a negative value when no EXP was lost or gained.
Now this next part I might be oversimplifying a bit so bear with me, for the first issue it took me something of about 3 full days of testing and debugging to completely fix the bug in a way that was acceptable which I will attempt to explain the logic of using pseudocode, please note this same logic was applied to both base and job EXP calculations:
Static variables:
lastLevel, initialLevel, initialExp, lastExpAmount, levelRequiredExp, previousExpGained, and gainedExp
Values needed from memory:
currentExp, currentLevel, and expRequiredForNextLevel
Extra variables:
actualExp, remainingExpRequired, and leveled
procedure calc(currentExp, currentLevel, expRequiredForNextLevel)
leveled = False
actualExp = currentExp
remainingExpRequired = expRequiredForNextLevel - currentExp
if(currentLevel is (initialLevel + 1))
gainedExp = (expRequiredForNextLevel - initialExp - gainedExp + previousExpGained + currentExp)
lastExpAmount = initialExp = currentExp
initialLevel = currentLevel
levelRequiredExp = remainingExpexpRequiredForNextLevel
leveled = True
if(leveled is True and (currentExp - lastExpAmount) is not 0)
gainedExp += currentExp - lastExpAmount
lastExpAmount = currentExp
else if(leveled is True)
previousExpGained = gainedExp
It's a bit messy and definitely in need of some massive refactoring but at after spending about 30 or so hours on it, the fact that it worked made my week. The second of the issues which was more short-lived was quickly fixed, albeit in not my preferred way, which goes back to the beginning with the WriteProcessMemory function imported from the Kernel32 DLL in the beginning. Upon switching character I learned there was a short blip in memory where the existing data values would get rewritten with some very large negative number. Because of the way I handled how the application would automatically start and stop the calculation timer rather than require user input this bug would occur as it read this very large negative value as a change in EXP and therefore take the input and calculate it as a gain. As a quick fix I saw no harm being done in simply writing the memory space with a 0 value when a character switch occurred. This allowed the calculator to function properly and at this point I had a working application, yay! A very special thanks to my friends who helped me find these issues, test my patches, and bear with the endless amounts of builds and hours I took of your time!
Improvements
Up to this point in my application I stored the memory addresses of the values I needed from the game client process as hard coded values in the source code itself. This obviously needed to be improved if the addresses were to change with frequent client updates. I opted to do this with .NET's built-in XML Serialization:
using System.Xml.Serialization;
With this I was able to simply export the memory addresses to an XML file as such in my version 1 of this file:
Notice the very large set of memory addresses that needed to be pulled every single time the game client was updated, this could not stand. I would go on to improve this by hardcoding memory offsets instead into the source and use less memory addresses.
Vastly improved! I was able to get the memory addresses down to only 5 needed values which sped up my address hunting times greatly, but this was not good enough, so along came version 3 a few months later:
Nice! down to only 2...eventually I was able to get this down to a single memory address needed to fetch all the needed data from process memory which concluded with my final version:
From only the memory address of the account number, I was able to hard code the appropriate offsets straight into the application itself. Greatly reducing the amount of time I needed to update the location in the XML file and push out to anyone who used my app.
I also had many other improvements and features planned to be added to the application and even began a fork of it to play around with the changes, though this quickly became an unrealized goal.
The Projects Death
As much fun as I had working on this project, and the lessons it taught me I was unfortunately left holding the bags because of 1 fateful update that was implemented to Ragnarok Online in early of 2020. Unfortunately like many other large MMORPGs cheating has become prevalent in the community. As such they implement anti cheat software that will run kernel level drivers that encrypt the memory space these games occupy while they are running. Since my application is derived by pulling data from the processes memory stack stored in RAM this effectively killed the functionality of my application and my goals of ever doing a proper refactor and architecture redesign to follow proper MVVC app design.
Maybe some day in the future when I become a more proficient developer I'll look into building a kernel level driver that will reopen that door for myself. Unfortunately I am not quite at that skill level yet. If you haven't already done so please come check out its burial spot here. If you're are an avid process hacker and would like to talk about how I can get this application working again I would love to hear your thoughts you can find my contact info at my main site here!
I hope you enjoyed my ramblings as much as I enjoyed writing about them. I hope to present more of my projects here, albeit going into too much detail might be quite a daunting task for myself but I'll do my best to give high level overviews. Cheers for now, 'til next time!