(Best viewed in notepad at 800X600 or higher. Reviewed amd edit 10/05/02 for submission on ixlair. Ingredients: A programming language (c++ or VB) An ASM disassembler (I use OLLYDBG) Patience Dr. Pepper check www.programmerstools.org for good stuff. Ok, I kept promising people that I'd do this for a while, and I finally got around to it, so here goes. A few quick definitions. Hwnd - The "Handle" of a window that you use to control aspects of it. Hdc - the "Handle to a Device Context" or a value you can use to put graphics into a window. You can find the hdc using the API getdc(hwnd). API - Application Programming Interface... basically functions windows has you can call. You can find them all in the "API viewer" in visual basic. The first thing I did on my long quest was learning how to find the memory locations. I used a program called winhex found on http://www.download.com and looked in Ragnarok's ram and searched for my current experience (found by looking at a character in the character select screen.) I then killed a monster to change it, and searched for the new value in the previously found memory location set, and through this I found the memory locations of experience, pretty simple. Next, I wrote the first external exe viewer that was written for the English server after patch 41 I think it was? The original experience viewers where originally programmed for the korean RO. I used visual basic and the following APIs. (This all works fine in C too, but Visual Basic is nice to make programs that are not processor intensive, because coding generally goes faster.) ================================================================================================= FindWindow - Finds a windows hwnd with only the name GetWindowThreadProcessId - Finds the threadprocess knowing the hwnd OpenProcess - opens the process using the threadprocess CloseHandle - closes the process ReadProcessMemory - reads a memoryvalue inside the opened process ================================================================================================= Here is an example of how to find a value. ------------------------------------------------------------------------------------------------- thehwnd = FindWindow(vbNullString, "Ragnarok") GetWindowThreadProcessId thehwnd, ProcessId ProcessHandle = OpenProcess(&H1F0FFF, False, ProcessId) ReadProcessMemory ProcessHandle, &h55ff0c, NewString, 3, 0& ------------------------------------------------------------------------------------------------- ***newhwnd,processid,and processhandle are all long integers/longs, or 4 bytes ***NewString is of course a string... thats where the data at location &h55ff0c/0x55ff0c will be stored, and N number of bytes will be stored according to the number after it (3 in the example). ***When declaring the string, declare it as: dim stringname as string * number (you need the * number part to make it a certain length, and not variable.) once you have the new value in a string, you can copy it into a long or integer (4 bytes/2bytes) with the following API. ================================================================================================= CopyMemory - copies memory from one variable to another. ================================================================================================= ------------------------------------------------------------------------------------------------- CopyMemory IntegerVariable, NewString, 3 ------------------------------------------------------------------------------------------------- From there, it was a simple matter of blitting it into the external window. ================================================================================================= Textout - Outputs a text into any hdc ================================================================================================= ------------------------------------------------------------------------------------------------- Textout me.hdc, x , y, "NewStringName", 13 <-- the last number is the length of the string. ------------------------------------------------------------------------------------------------- so basically, i used ------------------------------------------------------------------------------------------------- expstring = trim$(baseexpval) + ":" + trim$(baseexpnextval) + ":" + left$(trim$(baseexpval / baseexpnextval * 100), 4) + "%" textout me.hdc, 0, 0, expstring, len(expstring) ------------------------------------------------------------------------------------------------- Now, this is the basic thing that most of the exp programs do, but I tried to go farther and I succeeded in some places and failed in others. My idea was instead of textouting into my window's device context, i wanted to textout into RO's dc, and it worked... somewhat... here is what it looks like. ------------------------------------------------------------------------------------------------- thehwnd = FindWindow(vbNullString, "Ragnarok") thedc = getdc(thehwnd) expstring = trim$(baseexpval) + ":" + trim$(baseexpnextval) + ":" + left$(trim$(baseexpval / baseexpnextval * 100), 4) + "%" textout thedc, 0, 0, expstring, len(expstring) ------------------------------------------------------------------------------------------------- Now, there were two problems with this code. #1 it always put it at location 0,0 #2 it showed in the window, but always blinked The way I solved the first one was just finding the address in memory for the x and y locations. To do this, I put the basic info window at a random location, took a screen shot, found the x and y position in paint, searched for the value in winhex, did the same again, and found where the x and y were stored, and changed my textout accordingly. Now, the second problem I never did find out how to fix. To explain why it didn't work I have to explain a little about DirectX first. DirectX usually has at least to HDCs, one that the user actually sees, the second is a back one where all the changes occur, and on refreshes is copied onto the top one that users see (now I know more about directx - they are actually pageflipped, not copied... I suggest people research DX a bit, it's good stuff) so the user never sees any changes occurring. Basically, my program blit onto the top one and when a refresh happened it disappeared, and then put it back up, causing a blinking effect. There were 3 ways I could think of fixing this #1 Making my program blit between Ragnarok’s pageflips (When the bottom hdc goes to the top hdc) and then refresh #2 controlling the refresh myself through my program and only initiating after my thing gets blit #3 finding the hdc of the back hdc So... #2 was impractical, and I never did find out about #1 and #3, I just gave up after scouring hundreds of sites, asking college professors and people who should know, asking on message boards, etc. So, what was I to do to make it internal? hm... Addition: Now I know more about directx, I think I know how it would be possible, but I really don't have a reason to test so I will explain my theory. The way pageswapping works is that everything is drawn on the hdc not on the screen at the time (I will reffer to it as the back hdc), then the programs on screen hdc value is simply set to the back hdc, and the back hdc is set to the old front one's. So, in essence, only 4 bytes have to be swammped instead of a whole picture. This is about the time Arsenic posted his ArseKit link on the pak0 forums... right after Ro-world died might I add. I liked his patching the exe approach, and this is where the assembly came in. Before I get into the assembly I will explain the rest of the functions of the external viewer. Windowed Full Screen ------------------------------------------------------------------------------------------------- Dim resizerag As WINDOWPLACEMENT thehwnd = FindWindow(vbNullString, "Ragnarok") //gets the hwnd GetWindowPlacement thehwnd, resizerag //gets the current window placement SetWindowLong thehwnd, -16, GetWindowLong(thehwnd, -16) And Not 12582912 //Used for making the window not on top SetWindowPos thehwnd, 0, 0, 0, 0, 0, 39 //same as above resizerag.rcNormalPosition.Top = 0 resizerag.rcNormalPosition.Bottom = GetSystemMetrics(1) //screen y res resizerag.rcNormalPosition.Left = 0 resizerag.rcNormalPosition.Right = GetSystemMetrics(0) //screen x res SetWindowPlacement thehwnd, resizerag //Resize the window ------------------------------------------------------------------------------------------------- Always On Top SetWindowPos Me.hwnd, IsOnTop, 0, 0, 0, 0, 83 -1 for on top -2 for not on top The rest of the APIs I used... ================================================================================================= GetOpenFileName - Standard windows open box thingy GetAsyncKeyState - Used to find keystrokes - Used for alt+n and shift keys 18,78,16 respectively SendMessage - Used to send messages to windows,buttons,textboxes,etc to manipulate them "used to move external exp viewer window with mouse without having to click and hold down on the title bar. ReleaseCapture - Helps release my last sendmessage command... to an extent SetCursorPos - Set the position of the mouse mouse_event - Used to make the computer think a mouse button is pressed (Last 5 messages used for menu stuff and moving the window basically) ================================================================================================= Now on to the assembly. First of all, this was the first experience I have ever had in reverse engineering a program and editing it, and If I can say so myself I did a very good job. Now, I used to use WinDASM but for some reason, I don't like it anymore ^_^ I searched around and the best one I found for right now is OLLYDBG which you can find on www.programmerstools.org (check out the videos in the fun stuff there... the project, please the cookie thing, and heaven 7, and if you will the comics and stuff ^_^). Someone on the pak0 forums as a matter of fact gave me the link to that site. Thanks Chendrak! I have heard softice is very good, but I have not found a working version yet that doesn't crash on my OS. And from how I understand how it works, it isn't as efficient as OLLYDBG for our current task. Anyways, onto the explanation. Well, since I already knew the memory locations of the exp, I simply searched for that address in the assembly code and it found 3 instances of it, When I looked off to the right of all the assembly code, I also noticed the words "Exp Lv. %d". At this point I thought "W00T!" (sorry hehe) because that means I can find any text in the game I need. Anyways, The text is stored in the ram after the main executable in ASCII format that you can read. So, after much studying of the code and looking over what the stuff did, I made a jump to the end of the program and added the following code. http://www.castledragmire.com/ragnarok/externals/asmss.jpg Note: When editing assembly code, you cannot just add lines, you can only edit lines that are already there. What I did was I took one line of code that takes the same number of bytes as the jump statement, put the jump statement there, then I added all my new code at the very bottom of the program where there is a lot of empty space/null characters/0s (plus that line I replaced with the jump statement). Anyways... Some simple assembly and cpu basics. #1 There are a certain number of "registers" on every cpu that you can use, some are 8 bit, some are 16, etc. In this code I Use: Eax, Ecx, Edx, Esp (stack pointer) #2 the stack pointer is kind of like the last ram value address used in memory on the stack #3 the stack is a allocated section of ram where values are added and taken off in backwards order, reffered to as first in last out. #4 All numbers are represented in hex #5 Simple assembly commands JMP - Jump to a location CALL - Calls a function RETN - Returns from a function (reads the stack to determine where to go back to) MOV - Moves a value in ram or register into a value in ram or register. CMP - Compares 2 values JE - Jump if equal (JNE for jump if not equal) SUB - Subtract 2nd from 1st value and store in 1st PUSH - Put something on the stack PULL - Take top value off the stack LEA - Like move ADD - like subtract, but for addition CALL 00474F90 Line from the code that I deleted. MOV EAX,DWORD PTR DS:[59FFA4] Move Actual Exp into Eax MOV ECX,DWORD PTR DS:[558F48] Moves Last Exp Into Ecx (I picked an unused memory space) CMP EAX,ECX Checks if current exp and last checked exp are same JE SHORT 0052BCDE If they are equal, jumps down 3 lines SUB EAX,ECX Subtracts last exp from current exp and stores in eax MOV DWORD PTR DS:[558F4E],EAX Moves the new value into an unused memory space so you now know how much you got for your last kill. MOV EAX,DWORD PTR DS:[59FFA4] Jump to Location: Also moves current exp back into eax MOV DWORD PTR DS:[558F48],EAX Moves current exp into old exp place MOV EAX,DWORD PTR DS:[558F4E] Moves Last kill exp into eax PUSH EAX Puts this value on the stack to be called for later MOV EAX,DWORD PTR DS:[59FFA8] Puts needed exp into eax MOV ECX,DWORD PTR DS:[59FFA4] Puts current exp into ecx PUSH EAX Puts eax on stack PUSH ECX Puts ecx on stack LEA EDX,DWORD PTR SS:[ESP+20] Puts into edx the current stack position - 32 spaces (0x20) PUSH 558FEB This calls from the ram where I stored how I want the new information displayed. "%d/%d/%d" - I stored this information in an unused space. PUSH EDX Pushes on the stack Edx CALL 005107F9 Calls a function to do some stuff, made by gravity ADD ESP,14 Adds on 0x14 to the stack (20 to you non hex people) LEA ECX,DWORD PTR SS:[ESP+14] PUSH 0 PUSH E PUSH 1 PUSH 0 PUSH ECX PUSH 41 This value is the new y location PUSH 56 This value is the new x location MOV ECX,ESI CALL 00474F90 This is the final function that does EVERYTHING for you to put up the text, basically it looks at the stack and the values you have given it, in reverse order and uses your text string "%d/%d/%d" to change the %d's to your values you put on the stack, then uses the textout API to put it on the screen, and takes everything off the stack. Repeated the same process for the Job one, differences include different memory pointers and instead of PUSH 41 - PUSH 5C so it located it at a new Y location, below the job exp bar. The final command was a jump to go to the line after my first jump command I added in. JMP 0044AD8B That's basically it. The final part of my program was to make it dynamic for any patch. Basically I loaded the whole ragexe.exe into ram, looked for the location in memory for "Lv. %2d / %s / Lv. %2d" etc... this is the part used for when the basic info window is minimized. Anyways when I found that location in ram, I looked for that address call in the ASM part of the program, when I found it, I went a certain number up or down in bytes and found all the values I needed. As long as they don't change that part of the program it should always work. Oh, and the only problem my program MAY have with further patches is if it finds spots in RAM to store the extra data I needed, like "%d/%d/%d" and last exp and last kill exp, with 0s that is actually already used. I made a small algorithm I believe will work to find good ones. OLLYDBG tips: When editing a line, make sure "Fill NOPs" is checked. Right click the code to get bunches of options. Go to "search for"/"All referenced Text Strings" to see all the used text strings in the game. A quick project... see if you can find how health is stored in the game, find the text string "Hp. %d / %d" or something like that, and look at the code around it, look at the memory locations used, see how it all works out. Hint: The health value is encrypted afterwords using xor and stuff so you cant just search for the ram location, you have to go by the text. If you can find it, the more power to you, and you understand something I tried to teach ^_^ Wai! Well, good luck! Well, that's it, I hope someone finds this information useful. Sasami Hyrulean Productions