|
(Best viewed in notepad at 800X600 or higher, also, if you see any mistakes or whatnot, please
tell me. I was too lazy to reread through what i wrote ^_^)
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 found a
program called winhex on http://www.download.com and looked in Ragnarok's ram and searched for
the proper values, then had them changed, and searched for the new values, and through this I
found the new locations, pretty simple. Next, I wrote the first external exe viewer that was
written for the English server after patch 41 I think it was? I used visual basic and the
following APIs. (I tried this all out in C too and it worked fine, but I code much much faster in
VB.)
===================================================
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 "longs" or 4 bytes
***NewString is of course a string... thats where the data at location &h55ff0c (or in C
0x55ff0c) will be stored, and 3 bytes will be stored according to the number after it.
***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
---------------------------------------------------
textout me.hdc, 0, 0, trim$(baseexpval) + "" + trim$(baseexpnextval) + "" +
left$(trim$(baseexpval / baseexpnextval * 100), 4) + "%"
---------------------------------------------------
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 brilliant idea was instead of textouting into my windows hdc, i wanted to textout into ROs
hdc, and it worked... somewhat... here is what it looks like.
---------------------------------------------------
thehwnd = FindWindow(vbNullString, "Ragnarok")
textout thehwnd, 0, 0, trim$(baseexpval) + "" + trim$(baseexpnextval) + "" +
left$(trim$(baseexpval / baseexpnextval * 100), 4) + "%"
---------------------------------------------------
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
Basically, 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, 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 pageswap (When the bottom hdc goes to the top hdc)
and the 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, and asking on message
boards. So, what was I to do to make it internal? hm...
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 manipulate and do things to windows,buttons,btextboxes,etc "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. 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. The new code
started with the line that I deleted to put in 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.
#3 the stack IS the ram allocated for that program :)
#4 All numbers are represented in hex
#5 Simple assembly commands
JMP - Jump to a location
CALL - Calls a function
MOV - Moves one point of memory into a register or vice versa.
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
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
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. 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
|