Guide to Adding Code to Client or Server via a DLL.
(Part 1.)
Contents
Printable View
Guide to Adding Code to Client or Server via a DLL.
(Part 1.)
Contents
IntroductionOkay... I've been working away on this in spare time, which has become quite difficult with physical technical issues of late, so it's later than I'd hoped. I am also going to cut it down from what I originally planned, because I think the work involved in getting to the goal I originally hoped is too much in a single tutorial. Therefore, though this tutorial doesn't take you to any really great new feature, please take it as a grounding to implementing some of your own, and be patient for more tutorials which will follow.
Make no mistake, this is a long, wordy guide which cannot be padded with pretty pictures or turned into a Video guide. You will need to read it at your own pace, and follow it carefully. If you are already a conversant programmer, and / or really understand Windows PE file format and the Win32 KE Executive Kernel you can "skim" some bits, but I doubt that many of you do, unless you are an "Old Git" like me. That's not a put-down, it's just not something which gets taught these days the way we had to learn it a couple of decades ago... and for this, you are going to need it.
When we change the client, to add items, levels, ages, mixes etc or to translate the built in text language, we often add a section to the PE file. The most common example is the KPTTrans section found in many clients and servers released here. We add code, and data to that section to avoid overwriting important bit's of the client, and allow us more flexibility in out edits. But what if we want to add features of newer clients later on? Well, we can export the KPTTrans (or any other added) section from our old PE, and import it into a new base, but we have to make all the fix-ups pointing to that section in the correct places in the new executable. The alternative, is to find all the new code and data in the new client and replicate it in our old one in the section we have added to it.
Here's another way of doing things. Instead of adding a section to the main PE, why not link in a separate file at the point the PE is loaded into memory? That is exactly what happens when Windows loads DLLs into an executable.
What's the advantage? Well, for one thing, if you change client you don't have to export the section from your old client and import it into your new client, you just add the DLL import to the header of your new client... all the fix ups which call the new DLL are the same as those to call code and data in your added section. This means you can share a DLL, and some instruction on what people should look for to link it in to their client and you share, with this or other communities, the feature implemented in your DLL without giving away your entire client... implementing it in their client is then up to them.
There are other advantages too. If you implement an optional feature which is quite resource intensive, you can allow your users to select which implementation they use. For example, many servers offer "NoLag" options by disabling all the sound files... you could pass sound functions through a DLL, and load either a "full", "minimal" or "nosound" DLL, and only rename 1 file in your options GUI to implement the change... there are other means of selecting which DLL is used too, but I shall speak more on that later on. That example is simplistic, and could be implemented easier in your options GUI, but you have a greater means of customising here. To expand the idea, the reason sound files cause lag is that they are uncompressed and take a long time to load from disk, especially if the user has a slow drive. (UDMA 133 compared to a solid state SATA II drive makes a BIG difference to this "lag") How about compressing the files? MP3s OGGs? That would be great if you have a slow drive and a fast CPU, but if you have a fast drive and a slow CPU it would be disastrous... now you can let users decide how to optimise the game... or even time their system performance and do the tuning for them in the installer, or launcher.
Getting StartedTo begin with, we need to know a little bit about DLLs, what they are, and how they are used by the executive Kernel and the programs which load them. I presume you are familiar with the techniques of adding sections (to some extent) and are used to looking at the game.exe in Olly, or some other debugger. As OllyDbg is most commonly used, I will refer to it's interface, but the same information could be found in most others. If that assumption is true, then you know a lot of this already... what I am adding here is understanding of the knowledge you already have. ;)
So, what is a DLL? Essentially it's a program file which doesn't know how to run. It has exactly the same format as an EXE file, but it has no predefined start point. That doesn't sound very useful does it? Well, it is actually. When you load an EXE file, it has only one entry point exported to the OS, and the kernel adds it to the execution roster starting with the code at that point, and runs it. A DLL will export lots of execution start points, but no default one. The idea is that it contains a library of routines that can be called upon by other programs. The extension DLL stands for Dynamic Load-Linkage library. Every OS has them, in most Unixes they are lib.so files, on DOS .OVLs where the norm, but the idea is that if the underlying target changes, you can change the DLL without re-writing the program which calls it.
So if you load game.exe into your debugger (Olly) and look at the memory map, you will see that there are lots of memory sections defined by files other than game.exe. They are DLLs (for the most part).
When you build an Executable file in a compiler, you may get one executable from many many C files and their respective H(eader) files. These are all linked in together into the one file at compile time. But your program actually consists of far more code than just that which you wrote. What Olly shows in it's memory map is the program, as it will run on your machine. When you run the executable, it is then linked again, at run-time, to a whole host of routines in your system... and the combination of your code and system linked code makes up the entire program, which is listed in Ollys memory map.
You can list the DLLs imported by game.exe in a number of tools. Below is the representation in CFF explorer (above) and PE Explorer (below).
I believe I have already removed the HanDes.dll linking, because at no point in the game.exe is any routine or data in HanDes.dll ever used... but the header still requests it's presence. I take this as another sign that the "official" PT developers no longer understand the code they are working on.
Most of these are pretty standard libraries of code, and I hope most of you have some understanding of what functions they contain. In case you don't, I'll give a brief run-down, and ask you to check the MSDN for more details:-
- DSound.dll = DirectX Sound Library, present for DirectX 3 compatability.
- KERNEL32.dll = The Win32 Kernel, this means we are working as a Win32 program, and not using the POSIX or OS/2 layers.
- USER32.dll = game.exe is a UserLand program, these functions access parts of the Kernel in a way that does allows us to use the Kernel without running Kernel level code... which would be an "illegal instruction exception". You won't find it in Kernel mode drivers for Windows, but pretty much everything else needs it.
- GDI32.dll = GDI (Graphic Display Interface) is the Windows drawing routines. This 32bit incarnation was introduced with Win32s for Windows 3.1 and mimics the functionality of Windows NT 3s 32bit GDI, and only standardised by 95 SP1... it's operation (bugs and features) settles down by Win2K. (or, 98SE mostly, but it hasn't really changed since 2K)
- ADVAPI32.dll = Standard since Win95, Advanced API features not present in older Windows systems. Stuff like User profiles, Security Policies and Group Policy Objects which where in NT 3, but there was no real standard way of using them accross workstations and servers alike.
- SHELL32.dll = Very useful for launching programs and dlls from within your program, also contains code to look up version strings and icons and such like. All of the Resource section accessing code is in this as well as handy routines for enumerating directory structures and determining the launch program for a .txt file (for example).
- ole32.dll = This is the Object Linkage Embedding library which pulls in data from other programs. It's used a lot in databases, but also for Drag and Drop functionality and clipboard access. It provides communications between programs. This is how you can paste an Excel spreadsheet into WordPad, for example.
- DDRAW.dll = This is DirectX 2D drawing routines, again a standard part of DirectX 3. This is what we focused on upgrading our WinG applications to use when we ported from Windows 3.1 to Win95 / NT4.
- WINMM.dll = Multimedia library for Windows 3.x compatibility... essentially the other half of SDL.
- SHLWAPI.dll = Shell Windows API... not entirely sure why we would need it, but there it is.
- WININET.dll = Internet communication library from Windows 95 up. This is used for looking up Clan information on the web server. Essentially this is libCURL for Windows.
- IMM32.dll = This is how Windows handles different users on one system with different language input preferences.
- WSOCK32.dll = BSD Socket library from Windows 3.x. Most of PT's internet communication goes via this.
As you can see... there is nothing linked which is newer than Win98... though newer builds of PT use GDI32 functions only found in 2K or XP, and don't work right in NT4 or 95... how well they work on ME (for example) I'm not sure. Contrary to many many posts on RZ, PT is NOT a DirectX 7 game, and does not use either Direct3D or OpenGL at all; ever. Many of the libraries used are actually legacy libraries from Windows 3 era, and better alternatives have been available since Win95, suggesting that when development started, it was either on Windows 3.x (NT 3.x) or using an older compiler which didn't have bindings to those newer libraries.
The marketing hype about it being a full 3D game environment is actually misleading. It is as 3D as DOOM for DOS, [strike]no more and no less[/strike], though it can use Windows 32bit kernel and Display Drivers to achieve more than 320x200x8 display resolutions, and can correctly store "bridges" (or multiple floor heights at a single X,Y co-ordinate) which was a limit of the DOOM engine to reduce map size. This may well be the reason for the rope bridges and arch in GOF... just to prove the point. XD
--- EDIT ---
Re-reading this, there clearly is more hardware use than that, and I'm surely doing a dis-service in the last statement. However, I'm really not sure how the image is "layered" from multiple 3D renderings. Translucent textures overlapping clearly go wrong, and forcing anti-aliasing never works right... which it should if D3D rendered everything in one go... so there is some form of software interpretation and placement in-between the rendering of each frame, but it's not as great as I first thought.
---/EDIT ---
Two major questions arise from examining the list:-
- Where is XTrap.dll, the npxxx.dlls etc?
- Why does OllyDbg show many more DLLs in the list than those listed there?
The answer to question 1, is that they are late-loaded. Which is to say that the code loads them later during the operation of the program. Most DLLs are usually loaded by the kernel executive when it is asked to run an Exe file, but it is possible for the executable to request additional DLLs be loaded into it's memory space after it has already started running. Examples of this use would be plugins for WinAMP... when WinAMP is compiled into an Exe the developers don't know what plugins the user is going to have, or want to use, so they cannot include them all in the header of the Exe file. PT uses this method to "hide" XTrap, nProtect and GameGuard from the PE header information... but we could use it to give users a choice. For example, Diablo II uses it to select DirectDraw, Direct3D, OpenGL or GLIDE rendering of the game. (To give you another idea of what you could achieve with this method)
The answer to the Second question, is that it is entirely possible for a DLL to be dependent on another DLL, and therefore include that in its' import header. This happens more on my x64 system than other x86 systems, because many libraries are linked into WOW64 to allow for the 32-bit compatibility layer. What libraries are loaded on your system, aside from those on the list, will depend greatly on your version of Windows (or Wine or what-ever) and the exact version, service-pack and other software you have installed which may have upgraded those core libraries, or their dependences.
Gets confusing doesn't it? Not really, we don't need to worry about all those dependencies because the Kernel will sort them out for us, and we have to rely on the DLL writers (probably Microsoft for all of these) to have checked the dependencies of these libraries before shipping them out to us poor users. ;)
Okay... so seeing the DLLs imported tells us some things about the capability of the program, and also it's dependencies. But what if we want to add one? Well, I'm going to proceed with an example, which you can follow to get an idea of the principals involved.
Tools RequiredQuick links (for those who CBA to RTFM):-
- DLL-BEv101.7z (little or no change between 100 and 101, original link died and I believe I've enabled porc.exe as alternative to gorc.exe in the build script)
- GoASM Mirror
- GoRC Mirror
- GoLink Mirror
- IIDKing < Actually CFF Explorer will do
- OllyDbg
You are going to need some basic tools in order to achieve this goal. If our mission is to add some useful code to the PT game.exe (you can transport this idea to the server, or any other program you like) which we are going to write and build as a DLL, then we actually have several "sub-missions".
- Write a routine or two, and build them into a DLL file, not an Exe.
- Alter the PE header, to cause the Kernel to load a new DLL with the program.
- Modify routines in the original PE to use routines in our DLL instead of (or as-well as) their original function.
For mission 1, we need a build environment capable of compiling a Win32 x86 DLL. That's quite specific, so I have to say that you can't write a Java byte code program, build Python, Ruby or Perl executables or use .Net assemblies. You also cannot build an x64, i64, PowerPC or ARM DLL. Windows supports all of these, but they can only be called by programs written for those processors, and that is the reason why MS are pushing .Net. An x86 program can only call an x86 DLL, a PowerPC program can only call a PowerPC DLL, and an x64 (AMD64 or EMT64) program can only call an x64 DLL... a .Net program can call a .Net DLL, or a DLL which is native for the system on which it is running.Asside as to what all these things are, in case you come accross them:-So it's important that the language you program in creates x86 DLLs ONLY. Popular languages like C# and VB.Net don't do this, but similar languages like C++, Objective C, VB6 (can) and FreeBASIC are all fine, provided you use an x86 Win32 build target. Languages like Java and Python are great, but the effort to make them effective for this job is probably more than it's worth.Spoiler:
For my example, I am going to write in native x86 assembler. The reason is simple, one of the first things you are likely to want to do, is copy chunks of code out of your debugger and into notepad, build it as a DLL linked to the game.exe and then you can re-open it in notepad, edit and rebuild your DLL without worrying about boundaries in the exe. You need never touch the exe again to modify that code. You can manually re-write the routine in C or C++ if you want, but you will probably want to work the DLL as x86 assembler first, what-ever else you may do later on.
I've searched for an Assembler that everyone can use freely, that will do the job well, and work with syntax as we are used to seeing it in Olly as much as possible. This might sound like a trivial thing, but it is actually much harder than it sounds. If you look at the options in Olly, you will see that it can format syntax in several different ways, to "look" more like one assembler or another.
The most common Assembler syntax used in the Win32 environment is that of Microsofts MASM, and that is understandable. Microsoft stopped distributing MASM as an Assembler, and not part of a compiler suite back in the days of DOS, so MASM only produces 16bit DOS programs and OVL libraries. Of course, MASM has been upgraded to 32bit as it's used as part of Visual Studios' tool chain to build modern Win32 and Win64 programs, but you can't get that on it's own, and in newer builds it is used as a library, not an independent program. So we need to look elsewhere.Asside (for those interested in my considerations):-To get you going without having to research and find all the appropriate includes and so on, I have made a DLL build environment available for you to download:-Spoiler:
DLL-BEv101.7z from Mega.co.nz
DLL-BEv101.7z from Dropbox
[strike]DLL-BEv100.7z from Host1x.net Direct Link
DLL-BEv100.7z from MediaFire Indirect
DLL-BEv100.7z from Deposit Indirect
DLL-BEv100.7z from Uploading.com Indirect
DLL-BEv100.7z from Uploading.com Indirect
MegaUpload[/strike] Indirect
Size = 2.09MB
MD5 = 56484D8BE8B76C888E8D7017EDC3582A
SHA1 = ECD157990253D0E72F9526B77336233B4F93DA1F[/strike]
It's now on [strike]my Zumo cloud[strike] so shouldn't break. v101 signatures:-Size = 2.09MB
MD5 = 6703C7C5AF2FF88FFC29041FF4EE88CF
SHA1 = CF399DCB2BAB215FCC6A51B0F474F081DA27D6D4
Use 7-Zip or PeaZip to extract it to a location of your choice. (WinRAR sux :p)
Also go and get, and install GoASM, GoRC and GoLink. To install them, just create a folder in "C:\Program Files" (or where ever %ProgramFiles% points on your machine) if you are using an x86 Windows install, or in "C:\Program Files (x86)" (or where ever %ProgramFiles(x86)% points on your machine) if you are using an x64 Windows install, called GoASM, and extract each Zip file into it. My Build Environment will look for them there. (if this is too difficult for people to understand, I may make an installer.
For mission 2, we will use IIDKing from the RETeam (Reverse Engineering Team) and specifically SantMat. You should find it near the bottom of the page, but you can also Google as this little gem comes up on most of the Reverse Software Engineering blogs, forums and tools repositories. Theoretically, other tools like y0das' LordPE could be used, and the results of that are really very good, but it's much harder to explain how to use it.
Finally we need to tackle mission 3... the tools for this are simple. You can, of course use a Hex editor... but that's a pain in the backside. It's much easier to use OllyDbg. Go... GET IT, LEARN IT, USE IT. It rocks. XD
I don't mind if you use OllyDbg 1.10 or the final OllyDbg 2.0 Beta. What is the difference? Olly v2 is way faster, Olly 1.1 has a working "plugin" interface. It's great for cracking tough debug detecting encryption or anti-crack executables, but we don't need it for this.
Oh... yea... you'll also want to have a working private PT server and client installation, but what you choose to use there I leave entirely up to you.
Mission 1a - Creating a DLL for PTOkay, to link a DLL into a program, we need to know what functions (specifically, addresses) it exports. You can add new functions to export from your DLL at a later date, but you will have to re-link them in to the executable, so it's best if you know what functions you intend to export first.
The location of the routines in the code doesn't matter any more, because those addresses will be linked into the program at the point it is loaded into memory. This makes better use of available memory than adding a section to the Exe, and means that code, and data in your DLL can have correct access privileges which won't upset DEP or anything, and will annoy people trying to perform injection on your code. All of which is a good thing.
For this example, as some of you reading my discussion threads may have gathered, I'm going to replace the Win32 library call GDI32:TextOutA()... that is to say, the ANSI version of the TextOut() Win32 API, normally located in GDI32.dll. The reasons I'm being so mean, and picking on this little function are:-
- It has a very simple API functionality, with clearly defined functional parameters. (It's not a complex Object Oriented COM+ library routine like the DirectX calls)
- It is called by the program an awful lot. (Too many times during each frame considering the complexity of Anti-Aliased True Type Font rendering in Windows)
- It is key to translating a client. (all displayed text which is not in bitmaps is produced through this)
- It is used poorly by many clients. (overlapping text, mismatched joins and backgrounds with don't fit the dimensions of the text they are supposed to back etc.)
- There are better alternatives which produce clearer and more accurate output. (FreeType, Anti-Grain Geometry, Adobe and Apple font renderers and even several Bitmap Blitting game font routines)
Some of these reasons will be illustrated in this guide, some will be left for you, or until later guides.
Other popular contenders where kernel32:VirtualAlloc(), kernel32:ReadFile(), GDI32:BitBlt() and USER32:FindFirstFileA() and FindNextFileA(), but of course, you don't have to pick Win32 APIs and DLL calls to patch, you can export any function you like. The advantage of using a DLL API call for this tutorial is that I can use a named reference which will show up in your code under that name, where code offsets may well vary, so I'm leaving that for a later tutorial, or your own imaginings... supposing that you don't struggle with this, which I think some of you might well do.
Okay, so we've checked out the Win32 API on MSDN, and we know that the API calls for:-Which means all the calls to that API are going to PUSH a count of the characters in the string to render, an output string pointer, a Y and an X start position on the DC (Device Context) on which text will be rendered, and the handle to an existing DC.Code:BOOL TextOut(
__in HDC hdc,
__in int nXStart,
__in int nYStart,
__in LPCTSTR lpString,
__in int cbString);
Aside (for those who are not familiar Device Contexts)If we look at an Olly dump of one of those calls, we will see how these parameters are passed to the API (DLL) via the stack.
The Win32 environment needs to unify programs access to devices such as Keyboards, Pointing Devices, Displays, Printers etc. These are known as HIDDs, or Human Interface Device Drivers, and they are accessed via User Land programming APIs. Graphics displays have been through a number of incarnations and tried several architectures over the years, and when Windows and other WIMP (Windows, Icons, Menus and Pointer) systems where coming to the fore, there was a great deal of experimentation.
These days, graphics are usually produced in 24 or 32-bit, with a fixed palette formed, either as RGB or BGR (Red, Green and Blue, or Blue Green and Red) with an optional Alpha byte with a 1:1 pixel aspect ratio. However, 16-bit, 15-bit and 8-bit palletised displays used to be popular in a variety of pixel aspect ratios... and stranger techniques like the semi-popular HAM (Hold, And Modify) displays allowed for up to 24k colours simultaneously and un-palletized at only 8-bits per pixel. But accessing and correctly generating such a compressed "RAW" image was so complex that it was only really useful for static, un-animated images.
Other colour models are also popular on non-dynamic displays, such as printers. So CYMK colour space is the most popular, but YCrCb or YUV spaces are also used, and these are very popular among Video encoding and 3D Texture Maps.
Somehow Windows has to consolidate all these different standards and make sure that programs "just work". It does this via Device Contexts, and DIBs. A DIB is a Device Independent Bitmap, and the process of rendering it to a Device Context is the process of translating that DIB into a format which fits the target device in question.
Handles to Device Contexts (hDCs) should only be accessed via Windows APIs to ensure that the correct translation occurs with any information you pass to that DC... even if that DC is using technology which hasn't been invented at the time you write your program... something that was mostly impossible in the days when DOS was our primary OS for PCs. So make no mistake that these little beauties are both important and really really useful.You should note that each parameter, as it is shown in the C examples in the MSDN, is passed in the reverse order in Assembler, because it is passed on the stack. A stack is a LIFO (Last In First Out) memory block, so the last thing PUSHed on, is the first thing POPed off. ;) The C ordering is based on the order the parameters are read by the routine, not the order they are passed to it.Code:mov edx,[arg3]
push eax ; /Count => [Arg5]
mov eax,[arg2] ; |
push ecx ; |String => [Arg4]
mov ecx,[arg1] ; |
push edx ; |Y => [Arg3]
push eax ; |X => [Arg2]
push ecx ; |hDC => [Arg1]
call [<&GDI32.TextOutA>] ; \GDI32.TextOutA
Okay, so we know that we are going to create a DLL with one exported function, and we know what the parameters of that function are going to be, so now we can start prototyping our DLL code.
If you've already unpacked my DLL Build Environment you will see that it already contains source code for a Test.dll. This DLL does absolutely nothing, but it's files can be used as a template for starting a new project, and you should really have a look at it, and acquaint your self with the code and the tools used. (read the GoASM, GoLink and GoRC manuals)
To start a new DLL project, copy Test.asm, Test.rc and Res\Test.rc to a new files. For this example, I'm going to call the DLL TextRend.dll, and you'll see that I've left Res\TextRend.rc in there by mistake. XD To link to that, you should open your new TextRend.rc and replace the only line in it '#include "Res/Test.rc"' with '#include "Res/TextRend.rc"'
What are these files? Okay, the .asm is your main source code file, the first .rc file is just a link to the Res\blah.rc file which contains the code to be compiled into your resource section. That enables you to include a version string, and even an icon in your DLL if you wish. The two batch files Build.bat and Make.bat allow you to create the executable DLL files from the source code and view any output from the build process in notepad. Build.bat requires the name of the source files and DLL it's being asked to build, but Make.bat will just pass the names of every source it can find to Build.bat one at a time. So by double clicking Make.bat, any DLL you have the full working source for will be pumped out, and a log of the process pops up in notepad.
You can try that now, the output you should see in notepad will be something like this:-If it is, as this log shows no errors, you should now have a Test.dll and a TextRend.dll in your build folder.Code:>Assembly started at 10:42:06.02 on 04/01/2010
>10:42:06.03 - Building Test
GoAsm.Exe Version 0.56.8 - Copyright Jeremy Gordon 2001/9 - JG@JGnet.co.uk
Output file: Test.obj
GoRC.Exe Version 0.90.5 - Copyright Jeremy Gordon 1998/2009 - JG@JGnet.co.uk
Output file: Test.res
GoLink.Exe Version 0.26.14 - Copyright Jeremy Gordon 2002/9 - JG@JGnet.co.uk
Output file: Test.dll
Format: win32 size: 3,072 bytes
>10:42:06.16 - Building TextRend
GoAsm.Exe Version 0.56.8 - Copyright Jeremy Gordon 2001/9 - JG@JGnet.co.uk
Output file: TextRend.obj
GoRC.Exe Version 0.90.5 - Copyright Jeremy Gordon 1998/2009 - JG@JGnet.co.uk
Output file: TextRend.res
GoLink.Exe Version 0.26.14 - Copyright Jeremy Gordon 2002/9 - JG@JGnet.co.uk
Output file: TextRend.dll
Format: win32 size: 3,072 bytes
If you look at them in CFF Explorer or PE Explorer, you will see that they export two functions. DllEntry(), and TestFunction(). The only difference (since we have just copied the files over and renamed the copies, will be the version information... so if you look at the Version / Details tab of the properties of each, File description is either "Test DLL" or "Modular Priston Tale - Text Rendering Library" and the Copyright is either "©ThisYear MyCompany" or "© 2009 Modular Priston Tale". ^_^ (Modular Priston Tale, or ModPT is the working name for where I'm going with all of this)
Now lets look at the source code in the .asm file. We need to understand that.The original source by Iczelion which is written for MASM32 and can be found here. I've adapted it to work with GoASM, which is mostly about working Frames and Exports, and the fact that you don't need any of that nonsense about Flat models and .386 stdcall... all of which is obvious because you are creating a Win32 program... and would be really annoying if you wanted to port your assembler to something other than Win32. In GoASM, those sort of things are more correctly Assembler, and Linker switches, and not included in your source code.Code:;--------------------------------------------------------------------------------------
; DLLSkeleton.asm
;-----------------+
; by: Iczelion |
;--------------------------------------------------------------------------------------
;.386
;.model flat,stdcall
;option casemap:none
#include "include\windows.h"
DATA SECTION
CODE SECTION
START:
EXPORT DllEntry FRAME hInst, reason, reserved1
mov eax,TRUE
ret
ENDF
;---------------------------------------------------------------------------------------------------
; This is a dummy function
; It does nothing. I put it here to show where you can insert functions into
; a DLL.
;----------------------------------------------------------------------------------------------------
EXPORT TestFunction:
ret
This is just 10 lines of code, and only 3 of them are actually machine code instructions.The rest are directives for the Assembler, and linker.
Let's take the machine code first, since you are probably familiar with that from Olly.That's all the code that's going to be output by this test example, and the only thing you won't see in Olly is the TRUE bit. Seeing this code in Olly is a little difficult, but we can disassemble it in CFF explorer. If we do, what we see is:-Code:mov eax,TRUE
ret
ret
The sections in Red, we wrote specifically, the rest the assembler deduced from our build model and the directions we gave it.Code:push ebp
mov ebp, esp
mov eax, 0x1
pop ebp
ret 0xc
ret
So in practical terms TRUE=0x1. It's a constant included in the file include\windows.h. In binary logic terms, any non-zero value is TRUE. Some values are more TRUE than others however, and most systems consider -1 to be the most TRUE value, since it equates to F in twos-complement hexadecimal... specifically, as many Fs as you have room for in whatever value you are using. So FF in a byte (or char in C) FFFF in a word, FFFFFFFF in a long word etc. That's pretty darn TRUE if you ask me. XD TRUE being 1 is very Windows centric... and that's why that is why it's defined in the include\windows.h file. This file also defines a number of other constants, like WM_KEYDOWN and so on for Windows Messages. So anything you want to do with Windows APIs, is probably going to require you to include that header, or define the constants your self... otherwise you are just going to have to look up the value that they equate to. So that's covered #include"include\windows.h" too.
Aside for those not used to using headers or includes.We know what a mov instruction and a ret instruction do, so we now understand 4 of those 10 lines. What we don't understand, is where those extra instructions that mess around with the EBP and ESP do, and where they came from, since we didn't write them. Well, they are created by GoASMs' smart stack frames. They only happen in the DllEntry function of our DLL, and they happen because we declared DllEntry with the FRAME keyword.
Having those standard things defined in a set, standard header makes your source code much more readable, and makes it easier for your to write, and understand your code... you don't have to look up the value of WM_CREATE, WM_PAINT, WM_KEYDOWN etc. VK_NUMPAD0, VK_LSHIFT, VK_ESCAPE etc. or any of the other very very useful defines (or equates, depending on your preferred terminology) which it includes.
We can declare a FRAME at any point, but using it for a labelled routine is most useful. So DllEntry FRAME declares a FRAME with the label DllEntry. The comma "," delimited terms which follow it are the parameters passed to the function, and because it's an entry point for a DLL, their content is fixed by the Win32 API. The entry point for a DLL (if one exists) is executed at the point a DLL is loaded, and at the point it is terminated, though it can be called at any other point just like any other exported routine. This means that you could "patch" the executable with this routine... if you wanted to.
A FRAME is closed with ENDF, and to understand what it actually does, you need to know how the system stack works in machine code. Assuming you have a reasonable understanding of that, I can tell you that by using FRAME and ENDF anything you do with the stack within that frame, is local to your routine only and will be discarded at the ENDF... so you don't have to worry about corrupting the stack, forgetting to POP something you PUSHed or remembering to PUSH back something you POPed off.
Let's look at declaring routines then, and we should be able to crack the lot of it. The usual way of CALLing or JuMPing to specific addresses in Assembler, is to declare a "label". You can define labels in Olly, either in code or in data by pressing colon ":" and typing a name. This isn't stored in your executable, but in Ollys' .udd file, and means anything referring to that address is either replaced, prefixed or postfixed with that label in the CPU and Dump windows in Olly, which makes your program easier to read than looking at address numbers and offsets. In an Assembler source file, we really don't want to limit our code to specific addresses, that should be left to the linker to decide.
Absolutely anything you like can be a label, so long as it isn't a reserved word (basically any of the x86 mnemonics, or assembler directives) and the Assembler will know that it's a label because it ends in a colon ":".
GoASM requires only one label called START:. The reason for that, is that every executable needs to have a main entry point... even if it's a DLL. Interestingly, the DllEntry export is optional, and because we are building a DLL and not an executable, the START label won't actually do anything, but it still needs to be there for completeness.
The keyword EXPORT tells the Assembler that it should keep a place holder in its' binary that points to this point, and remember the label it is given, so that the Linker can add the fixed up location to the executable as a named exported function in the exe, or dll.
So now there are only two lines out of ten that we don't understand. CODE SECTION and DATA SECTION. These are also cues for the Linker. We already know that a PE file contains several sections for the program, it's data, import and export tables and the resource section. The resource section cannot be compiled in Assembler, because it is not code, but data... okay, so you could declare a shed load of Defined Bytes and stuff, but it makes more sense to use the Windows standard Resource source code. The Exports are handled by the Linker, given cues in our source as we have already seen, and Imports are also given cues in our source if we need to import functions from other DLLs. So all that's left is our Code and Data sections... and these are defined by these statements.
It's possible to have a Test.inc or Test.h with all the data section and #include that file in Test.asm the way we did with windows.h instead of having them all in one file, but what is the point with such a small file to begin with?
Okay... so now you understand all the code in an empty DLL. Wow, a program that does nothing takes so much learning. XD
Now we need to make that empty DLL usable as a replacement for TextOutA(). But before we start work in earnest, let's clean up the comments which make up the bulk of this source, and then I want to show you a different way of managing the exports. Let's change the source to this:-So, basically we've got rid of the EXPORT statements, and added some ALIGN statements. An ALIGN 4 tells the linker that the address should be aligned to a 32-bit (4 byte) address, which in x86 machine code is addressed faster than a misaligned call, and in some cases may assemble JuMPs and CALLs to smaller byte code.Code:;--------------------------------------------------------------------------------------
; TextRend.asm
;--------------------------------------------------------------------------------------
#include "include\windows.h"
DATA SECTION
ALIGN 4
RetAddr DD 0x0
EAXStore DD 0x0
CODE SECTION
ALIGN 4
START:
DllEntryPoint FRAME hInst, reason, reserved1
mov eax,TRUE
ret
EndF
;--------------------------------------------------------------------------------------
; TextRend
; This is the main function patched by this DLL, it captures all text rendered to the
; screen, or any other Device Context via the OS TextOut function.
; This is the only OS Function that PT uses to render text... though there are 3 others
; it, or now you, could use instead. Additionally, you could use this to implement a
; custom text renderer, like FreeType.
;--------------------------------------------------------------------------------------
ALIGN 4
TextRend: ; Stricktly speaking FRAME hDC, xStart, yStart, lpString, cbString
pop [RetAddr] ; Most important... we must take the return address from the call off the stack!
mov [EAXStore],eax ; Store EAX
pop eax ; Pull the hDC off the stack
pop eax ; Pull the X off the stack
pop eax ; Pull the Y off the stack
pop eax ; Pull the String pointer off the stack
pop eax ; Pull the Character Count off the stack
mov eax,[EAXStore] ; Restore EAX
push [RetAddr] ; Restore the Return Address
ret
The main TextRend() function is now taking all the parameters off the stack, and... throwing them away, basically. We have to remember our return address though, and we also have to remember the former state of the Accumulator (EAX register) since that's where I'm going to pull (or POP) all of those stack values into. At the end of the function I restore both these.
This means I have two DWords defined in my Data Section. The syntax (in GoASM) to declare DWord is DD, A byte is DB, a Word is DW and a Quad DQ. This is different from MASM, but similar to NASM, so you need to bare this in mind.
The reason I've done away with the EXPORT statements is that I want you to try out an alternative method of informing the Linker about the exports. In MASM, if you follow Iczlions tutorials or others around the Web, you will see that you have to create a .def file which declares exports. Although GoASM and GoLink use completely different syntax, that method of working is still possible, provided you don't declare exports in the Assembler, and the define file. So I want you to save that, and create a new text file called TextRend.def and fill it with the following code:-Now you can run Make.bat again, and see that the produced TextRend.dll is still built just the same as before.Code:/Export DllEntryPoint
/Export TextRend
You can examine the Build.bat and Make.bat files at your leisure, but basically, if a .def file is present for your build target, it will pass it on to the linker, and if it isn't, it will pass the parameters assuming that your exports are declared in-line in your .asm file.
You may find that method cleaner, or you may not... but if we start sharing DLL code, as I hope we will, you will need to know that either method works the same, and which you choose is up to you.
Mission 2 - Linking Our DLL into the Game ClientOkay, so now we get to the juicy bit... but also the easiest bit. This is where it's finally worth my while giving you some pretty pictures. :D
So, fire up IIDKing, click off the controls in the order illustrated below:-
First selecting your game.exe file, then your TextRend.dll and when you have done that, you can select either just the TextRend() API or both TextRend() and DllEntry(). I don't intend to use DllEntry() for this example, but it may be useful to you for your experiments later on.
That done, click off "Add Them!!" at the bottom (point 3 of fig.1) and you should have an executable which is linked to TextRend.dll. \o/
If "the proof is in the pudding" I'm going to get myself a big fat slice of Stake and Kidney pudding, with peas and gravy. Open your executable in CFF Explorer or PE Explorer and check the imports.
Make sure your game.exe is in your client folder, and that TextRend.dll is either next to it, or somewhere in the path and make sure the game still runs okay.
I'm sure you won't have any problems. No change yet though right? ^_^
If you want to do it the CFF Explorer way, you'll find it's a little more effort (not much) and the results are a lot nicer. You don't get an extra import section.
Open the executable in CFF, click the "Import Adder" down the left hand tree view and then click "Add" right in the top centre.
Pick TextRend.dll from the file browser and the exports from it will show up down the bottom.
Click off each one and "Import by name". We exported by name, and ordinals haven't really been "standard" since Win3.1. If you work by ordinal (the numeric order in which the functions are exported) you have to be very careful each time you build a new update to your DLL that the functions order, or numeracy doesn't change... how you do that depends on your compiler / assembler and even more on your linker.
Anyway... when they are all on the list to be imported, just "Rebuild Import Table" and save the executable.
Mission 3 - Modifying the Game Client to Use Our DLLWith the Juicy bit over and done with, and way too easy thanks to IIDKing, we now get to the fun part of modifying the game code to actually use the code that's now loaded with it in our new DLL.
Load your modified game.exe into Olly and, because we are replacing a named API from an existing DLL, right click and "Search for" -> "All intermodular calls"
Sort the resulting list by "Destination" and scroll down to the "T"s until you get to GDI32:TextOutA.
Each of those calls needs to be replaced with TextRend:TextRend, so double click the first one, and the CPU window will shift it's code listing down to the line "call [<&GDI32.TextOutA>]"
But how are we going to change that? If you double click the line, it translates to a pure address:-
So how do you know what the "Address" of your new DLL routine is? And isn't the point of using DLLs that the actual address they are loaded in memory isn't known until the program loads anyway?
Yes; and yes. However, the program will know the address of the import in the import address table, because that is part of the main executables structure. It is filled out by the KE (the Executive Kernel) as the executable is loaded into memory, alongside it's dependent dynamic load linkage (DLL) libraries. This is why the CALL address is in square brackets. The CALL will actually be made to the address pointed to by the contents of (or stored in) the memory located at 5BA048. So in C terms, you are dereferencing a pointer. In machine code parlance we usually refer to this as indirect addressing.
Aside, about high level and low level terminology.This method of addressing APIs in DLLs via address tables is key to how modern Operating Systems provide such powerful and standardised software, by building up large libraries of standard routines that everyone can program against. Understanding how it works will get you a long way to understanding how your computer works, and what is happening when it goes wrong. So let's look at the CALL as it is in more detail.
I hope that most people will understand, regardless of the terminology you use, but you should probably be aware that C / C++ people will think in terms of pointers and dereferencing, and Assembler people think in terms of direct, relative or indirect addressing. In C you can reference, point and dereference anywhere you like, Assembler has some limitations on when you can MOV, JMP or CALL relative (in other words, 8 bytes forward, or backward from where we are now, for example) and when you must MOV, JMP or CALL direct (to a specific address) and when you can MOV, JMP or CALL indirect (to a location stored in memory at another specified address). As a rule of thumb, MOVs are largely allowed to address direct or indirect, but not usually relative, JMPs are usually relative or direct, not indirect and CALLs are usually direct or indirect, not relative. How you go on Shifts (SHL, SHR), INCs, DECs, MODs, DIVs, MULs etc is more hit and miss, but usually they work like a MOV.
If you right click it, and "Follow in dump" -> "Memory location" you should see something like this:-
So you can see that the address that will actually be called is 75A5F9B4... on my system... today. Because, theoretically that address could change, as my system load changes, my system libraries get upgraded etc. etc. In practice, for any particular system, it changes very little, but I do note that the addresses of DLLs loaded by game.exe in my XP x86 system are quite different to those loaded under WOW64 (x86 compatibility layer for x64 Windows) in my Vista x64 system.
Okay, so next we need to find the Import Address Table entry that points to our new DLL routine TextRend(). So first of all, we should check out the memory map in Olly. (click the pale blue 'M' on the toolbar) at this point you should see that there is a new section added to your game.exe by IIDKing called .import. Well that's promising, but until I used IIDKing I wasn't aware that you could have more than one import table in a PE file.
It's formatted a little different to the .rdata section created by the linker that comes with Visual C++ (where the exe was originally created) but if you open it up (by double clicking it) you can scroll up and down (with the scroll bar) and left and right by pressing Control (CTRL) and the Up and Down arrow (cursor) keys. The pointer to the TextRend() routine will highlight in the Address column when you find it... unless you have set Ollys preferences not to show labels. If it is only showing labels and not addresses, you can tell it to show both, or you can calculate the address as +10h from the line above, or -10h from the line below.
--- Update ---
I didn't realize how powerful it was when I first wrote this guide, but for the most part you can reformat the display in Ollys dump from Hex -> 16 bytes + ASCII, to Integer -> Address, and the display changes to listing each 32 bit address on a separate line with labels next to them. ^_^--- /Update ---
So in my case I need to CALL DWord [440E148], the DWord is required, even though Olly doesn't include it in the Assemble window... I'm not sure why, but if you leave it off, the line fails to assemble and you get a warning message. When you have it right, you know you have it right because Olly updates the CPU listing to say "call [&<TextRend.TextRend>]".
As usual with Olly, you can right click the altered line and "Edit" -> "Copy to executable" but you may want to hold off on saving the executable off to file (perhaps with a different name) until you have updated each of the TextOutA() calls to point to TextRend() instead.
Having done that, make sure your newly Ollied executable is in your client install folder, and your newly built TextRend.dll is copied in there too, and run the game.
You should find that you are now typing blind, and the only text that is displayed anywhere is that which is built in to the graphics files. If you can log in without seeing what you are typing, you may well find that you gain a fair bit of extra frame rate too, since we've significantly reduced the processing that needs to go on to render each frame. ^_^
If you are seeing what I've just described, then you have followed everything right so far. Our DLL is imported, and all the TextOut() calls are going through it to be thrown away with no further processing. You can check this in Olly, by opening the Code section of your TextRend.dll and placing a IBP (Instruction Break Point) on the first line of the TextRend() function. That way you can step through it, and see the instructions doing as I said they would in the comments. ^_^
Next we need to make that function do something useful.
Mission 1b - Creating a Useful DLL for PTOkay, so we've got the game running our code, but it doesn't do anything now. In fact, it's doing less than it was before.
First of all lets get it doing what it used to, so change the listing in TextRend.asm to this:-Okay, so you can save that, and re-run Make.bat to get your new DLL. But we've got some new syntax in there, and some of it is distinctly not like an Olly listing, so let me explain that while you prove that your typing (or Copy and Paste) skills are still up to scratch. XDCode:;--------------------------------------------------------------------------------------
; TextRend.asm
;--------------------------------------------------------------------------------------
#include "include\windows.h"
DATA SECTION
ALIGN 4
RetAddr DD 0x0
hDC DD 0x0
xStart DD 0x0
yStart DD 0x0
lpString DD 0x0
cbString DD 0x0
CODE SECTION
ALIGN 4
START:
DllEntryPoint FRAME hInst, reason, reserved1
mov eax,TRUE
ret
EndF
;--------------------------------------------------------------------------------------
; TextRend
; This is the main function patched by this DLL, it captures all text rendered to the
; screen, or any other Device Context via the OS TextOut function.
; This is the only OS Function that PT uses to render text... though there are 3 others
; it, or now you, could use instead. Additionally, you could use this to implement a
; custom text renderer, like FreeType.
;--------------------------------------------------------------------------------------
ALIGN 4
TextRend: ; Stricktly speaking FRAME hDC, xStart, yStart, lpString, cbString
pop [RetAddr] ; Most important... we must take the return address from the call off the stack!
pop [hDC] ; Get the handle to the DC to draw to
pop [xStart] ; Get the X Coord
pop [yStart] ; Get the Y Coord
pop [lpString] ; Get the string pointer
pop [cbString] ; Get the character count
INVOKE GDI32:TextOutA, \
[hDC], \ ; This Invocation is passing our parameters on to TextOut() as they would
[xStart], \ ; have been done in the first place.
[yStart], \
[lpString], \
[cbString]
push [RetAddr]
ret
This time I've defined enough DWords to store all the parameters as well as the return address. I don't move the stacked values into the accumulator (EAX, if you remember) any more, so I don't need to remember what it was before I overwrite it any more.
Then we have an INVOKE instruction with some very strange and complex syntax. It's not on mentioned in Intels' x86 instruction set documentation so what is it all about?
Time was, I used to have to assemble by hand, looking at the tables of OpCodes and working out what the Hex was for the instructions I wanted to write, then POKE them into memory in the ROM based BASIC interpretor, but these days, not only do we have free access to machine code assemblers, but almost all of them are Macro Assemblers. This means that sequences of commonly used instructions can be turned into a Macro, which is like a subroutine, or a function except it is expanded at the time of Assembly, rather than branched to at the programs run-time.
INVOKE is a widely adopted Macro which you can pretty much guarantee any Assembler will have some in-built understanding of. You can create, define or re-define Macros to your hearts content, but INVOKE typically calls function in a library in the OS you are building for. Two advantages of INVOKEing rather than stacking parameters and CALLing are that you can pass parameters in the C order shown in the Windows API documentation, and not in reverse order as they show in a disassembly like Olly. So there's a legibility advantage there. Also INVOKE macros take care of differences in calling conventions between platforms. So this means you can build for Linux without changing the method of calling libraries and libpng.dll (for example) functions can be called just the same as libpng.so, you just change the build target from Win32 PE to Linux x86 ELF. But probably more important to you (as most of you here are Windoze people) is that the calling convention for x64 versions of Windows are different at assembler level than they are for x86 Windows platforms... you don't have to worry about that if you call via INVOKE. Okay... we can't rebuild PT for x64 until we have everything in game.exe in source form, and WOW64 will take care of those differences for a virtualise x86 program running on Win64 platforms... but it's a future consideration surely?
The GoASM INVOKE syntax is pretty simple, the first parameter is the DLL name, and the function name separated by a colon ":" then a comma "," and every parameter which should go to the function is passed after that, one at a time, with a comma separating each one.
To improve the legibility of my source file, I've broken the INVOKE instructions on to multiple lines using the line-continuation character "\". This means I can comment along side it, and you don't have to scroll left and right to read the whole line. In C the compiler ignores new lines and looks for a semi-colon to end an instruction, and in most BASIC languages that support line-continuation characters, the standard is an "_" so you can think of the "\" as one of those.
Most assemblers would need you to #include a .inc file near the top of your source code which should contain all the details of the functions contained in any DLL (or lib.so or .library or whatever your target OS uses) you intend to INVOKE within your source listing. But GoASM is really really smart, and provided the DLL in question is somewhere in your search path it, will figure out the details of the include from the DLLs export section it's self.
It's actually common that you should include a .h file in C or C++ if you are going to utilise an OS library, so this is a really handy short-cut... but you could also use the headers files (.h) as documentation to the OS APIs, so without them, you are going to have to rely more heavily on the MSDN.
Okay, so we've restored the original functionality, and we haven't had to touch the game.exe to do it; but what about getting something useful out?
Right, time to INVOKE some more DLL routines from our own DLL. ^_^ The Windows environment allows you to send Debug information out from your application without having to log it to a file, debugger or profiler... and if you look at the list of imported functions, you can see that the game.exe (most versions I've looked at anyway) do make use of that for checking problems with DirectX. Why those calls are still in the release versions I'm not entirely sure, since most users don't have any way of reading those output messages, so you're not going to get sensible bug reports from them with it. It's proved handy for me to know what the DirectX calls are supposed to be doing though. :)
First, I need to make sure you can read system Debug information. The typical way to use it is to connect a Serial Printer, or Dumb Terminal (or PC running a Terminal Emulator) to the COM1: port on the back of your test PC. But that's a lot of effort for very little. The easier way to use it is to grab a copy of Mark Russanovichs' DebugView, and that will capture them to a list in a window. \o/
This will be my final code listing for this tutorial, as anything else you can probably figure out with the grounding I've given you now:-Okay... there are only a couple of new things to say about this, and the rest should be self-evident. Firstly ADDR value is the opposite of [value]. It's a reverse de-reference, so I'm passing the address of the value, not the contents of the value. Secondly you'll notice that I defined 256 bytes and stated that I don't care what their initial value is with the line szDebug DB 256 DUP ?. You'll also want to look up the API references for wsprintfA() and OutputDebugStringA.Code:;--------------------------------------------------------------------------------------
; TextRend.asm
;--------------------------------------------------------------------------------------
#include "include\windows.h"
DATA SECTION
ALIGN 4
RetAddr DD 0x0
hDC DD 0x0
xStart DD 0x0
yStart DD 0x0
lpString DD 0x0
cbString DD 0x0
szDebug DB 256 DUP ? ;Allow room for 255 characters of Debug text.
CODE SECTION
ALIGN 4
START:
DllEntryPoint FRAME hInst, reason, reserved1
mov eax,TRUE
ret
EndF
;--------------------------------------------------------------------------------------
; TextRend
; This is the main function patched by this DLL, it captures all text rendered to the
; screen, or any other Device Context via the OS TextOut function.
; This is the only OS Function that PT uses to render text... though there are 3 others
; it, or now you, could use instead. Additionally, you could use this to implement a
; custom text renderer, like FreeType.
;--------------------------------------------------------------------------------------
ALIGN 4
TextRend: ; Stricktly speaking FRAME hDC, xStart, yStart, lpString, cbString
pop [RetAddr] ; Most important... we must take the return address from the call off the stack!
pop [hDC] ; Get the handle to the DC to draw to
pop [xStart] ; Get the X Coord
pop [yStart] ; Get the Y Coord
pop [lpString] ; Get the string pointer
pop [cbString] ; Get the character count
INVOKE USER32:wsprintfA, \ ; Invoking the wsprintfA function to format a string containing our
ADDR szDebug, \ ; details to send to OutputDebugString()
ADDR 'RVA: %08lX @%d, %d = %s', \
[RetAddr], \
[xStart], \
[yStart], \
[lpString]
add esp,24 ; wsprintfA never cleans it's stack. 6 parameters at 4 bytes each, 6 * 4 = 24
INVOKE KERNEL32:OutputDebugStringA, \
ADDR szDebug ; And output it.
INVOKE GDI32:TextOutA, \
[hDC], \ ; This Invocation is passing our parameters on to TextOut() as they would
[xStart], \ ; have been done in the first place.
[yStart], \
[lpString], \
[cbString]
push [RetAddr]
ret
If you run DebugView, and then start the game client with this new build of the DLL, you can see all the details of every text string that is rendered to the screen. It's calling address, the location on the screen (or strictly speaking the Device Context) at which it is rendered and what the actual text string was.
Other Methods and IdeasI did ask that people here suggest what they may do with DLLs if they knew how to link them in to their game.exe and I got a couple of questions but very little in the way of answers. That suggests to me that people here are lacking in imagination quite frankly. XD
So what we've done could be done a lot easier with an API tracer, but what we could now do is use that information to trace back to all the text display code, then export that to our DLL and fix the font positioning and so on. We could (as I mentioned in the codes comments) use a different font renderer to display the text. Something of higher quality like AGG, or something faster, like a BitBlit of a cached bitmap font.
We could also use this information to cache the rendered text in an overlay DC or DIB so that when the game asks it to render the same line of text a second time, it's already in a bitmap form and can just be blitted back to the main display in one hit, or we could just optimise the text displaying of the main program so that it doesn't ask a line of text to be rendered more than once, and keeps a handle to the rendering for positioning on the display any time it needs it after that.
Now think of the other things you could change using this method. You could replace Hotuk.ini with registry entries, or a proper .ini file. You could replace the texture loading routines with ones that load PNGs, or DDSs. You could replace the Wave sound loading with OGGs or MP3s, you could replace the response to clicking "Exit" from the system menu with another menu, allowing the user to change detail levels, remap keys etc during the game.
If you can change how Hotuk.ini is stored, then you can change how the server reads all of it's configuration files. Wouldn't it be considerably faster if it go all that data from the SQL server? And if all the SQL database access goes through a DLL you have written and have the source code for, how easy is would it then be to re-target it to MySQL, SQLite, Postgre or DBase?
What if your client didn't get clan data from a web address but via a Telnet connection, or an IRC channel on an IRC server, or even via FTP... wouldn't that improve your security? Now imagine what you would have if that information used your own protocol and went via UDP packets? Once you are confident that the information is going through, and being processed by your code, and you have a source file, you can do anything you like with it, and change it at a whim.
Speaking of security considerations... speaking to friends who run PT on the wild wild web, they are concerned that if large sections of sensitive code where moved into DLLs then couldn't hackers just swap out the DLL to do something you don't want them to? The simple answer to that is that as this project stands, yes, they can. So you would have to counter that, and I have some ideas as to how.
Many of you use the CLSafe_Code to ensure that the client connecting to your server is byte-for-byte the same as the one you give to your users, and hasn't been altered in any way... okay, so you can implement a similar checksum algorithm in your game.exe which checks that the code, and data in the DLLs being loaded by it match the ones you give your players. So if someone modifies or swaps out a DLL, they will have to modify the checksum in your game.exe for it to work, and then, of course, the CLSafe_Code won't match, again. So you've gained a lot of flexibility, made your development environment more creative and really haven't lost any security.
What about this idea (which you know I hate) of encrypting your executable so that nobody can read your code. (Nobody who wouldn't understand the code they could see if it weren't encrypted at least. :S ;) ) Well, of course you can encrypt your DLLs too... but additionally, you can export the code and data sections of your build DLL and import them as raw data into your Executable, so that the whole lot is still packaged up as one file for your users, and still have your creative development environment. If you do this instead of adding your code to a section like KPTTrans, your code is actually better secured by the CLSafe_Code... and not to advertise flaws, I'll let you work out why.
If you keep the source of all your DLLs and make notes about where they patch into the main game.exe, and all your modifications are made in DLLs, you are no longer tied to any particular exe, and can switch and upgrade as you choose. You can also take sections of code from other executables and place them in DLL source files, and link those into your executable, replacing the routines in it.
You can reduce the size of your game.exe by moving code out in to DLLs. This doesn't reduce the overall game size, but if you late-load the DLLs you can get the game to load very very fast and show a progress bar as the libraries are loaded into the client.
You can move levels, ages, maps, item lists etc. etc. into DLL source files. Then you can add new ones at will.
If we all share the DLLs and how we patch them into a client, we can build up an open source alternative client. We may end up with no parts of the original client left. :o
Okay... so most of you who work on Private Servers that are open to the public want to keep your little trade secrets, but once you have a library which you are confident simply replicates the existing code, you can share it at that point, then we can all work on our own ideas of what it should be replaced with. You can keep your Uber version for your in-house client and still take advantage of ideas from the community. In return, we have had your help isolating a particular sub-system in the client. We can play with your ideas and you haven't had to give away your client, or server or anything else you'd rather not. You can be confident that you have given away no more, and no less than you intended to.
Therefore, I humbly submit that this is and excellent way for us to collaboratively work on improving PT. I hope you agree, and by all means, please, post below any ideas you have, any improvements you think you can make to this process and any concerns you may have with using it.
CreditsOkay... Lots of people to thank for this. Most of them not RageZoners, but very deserving non-the-less. Let's see if I can remember them all. :)
SantMat for IIDKing without which this Tutorial would be even longer.
Jeremy Gordon (Jorgon) for GoASM and the Go tools, which really are an excellent low-level Win32 or Win64 tool set.
Donkey for the 2.0 version of his Win32 Headers for GoASM which are the main part of my DLL Build Environment.
Yuri and E^cube of the MASM forum for helping me solve my build issues.
Iczelion for his fantastic Win32 Assembler tutorials and examples, and of course, the DLL skeleton mine is based on.
Oleh Yuschuk for OllyDbg.
Mark Russinovich for DebugView and all the other fantastic utilities from his SysInternals days and his contributions to the Windows Kernel since he joined Microsoft.
DarkKnightH20 for mentioning that you can link in to different parts of a Tutorial if it's broken into multiple posts, which gave me the idea of posting each chapter as a different post. (naughty though it may be)
And finally, MentaL and all the other Admins here at RaGEZONE, for not slapping me with an infraction despite all the rules I broke posting this tutorial. (Please? I hope you can see why it needed them, and I hope you agree that it's worth it.)
I'm sorry to interupt, but why not simply use LoadLibrary?
That's for later on, exactly because (IMHO) it is not so simple. Thanks for the suggestion, but it had been considered, and was passed out as one of the things I decided to trim out of this first part for simplicity.
I mentioned late loading a couple of times, and while LoadLibrary() is great when everything goes well, if there is a problem with a DLL loaded into your program by the KE from the import section, it's the Kernels job to sort that out. If you use LoadLibrary() there's no safety net, it's your responsibility to check everything and handle any problems. :wink:
When people are looking to secure libraries calculating checksums before loading them and so on, or picking DLLs from multiple user options, LoadLibrary() is a winner, but to start with, lets let the Kernel tell us exactly what's wrong while we are developing the basic principals. Yea? :)
You have an interesting part where you talk about the way PT draws its frames. Are you suggesting the 3D is faked in PT like Doom? It is possible to move the camera of PT in any direction giving it the feeling of true 3D..
Also, do you have experience with the DDRAW library?
It's 3D, but it's software 3D. DOOM 3D wasn't exactly "wrong" though it did take a couple of shortcuts. Shortcuts in Pritson Tales software 3D renderer include rendering separate 3D objects and overlaying them on the view. Partial alpha blending done at the wrong times etc. All of which leads to the strange effects you can see when one texture with an alpha layer overlaps another. :s
It also makes no use of Direct3D or OpenGL and therefore no use of your GPU, which is great if you have a crappy GFX card that overheats when you play WoW or EverQuest, but sux for frame rate, CPU load and general gameplay and presentation.
The experience I have of Direct Draw (the DDraw.dll) has usually been bad. But my experience with all things ActiveX (COM, COM+ etc.) has always been bad. The only Object Oriented libraries I've ever really liked using where the Amiga Intuition libraries (Based on BCPL, not C++) and Borlands VCL (Visual Control Library).
I'm afraid I just don't think in Object Oriented terms, and would rather access Direct Draw through a functional wrapper.
I understand Object Orientation, and can use it. But I understand a Tuxedo and can (with some effort) tie my own Bow-tie. I'd still rather wear jeans and a T-Shirt. XD
Like a Tux, Object Oriented code looks good, but to me it just seems like hard work, for me and the computer. Functional programming is easy, comfortable and looks a little scruffy... like jeans and a T.
Almost i see everyone isn't interest about problem check debug :D...
Errm. I'm confused by the negatives in that statement. Are you saying that people are, or are not interested in checking for a debugger? Or are you saying that there is a problem with using DLLs in this method, related to debuggers?
I'm not sure.:?:
Anyway... yes, you can check for debuggers, and yes, it's hard to debug a DLL on it's own, but... when you have the source, and can debug it when it's loaded in your program, where is the problem really?
I admit the problem of theft when your code is a DLL, but that isn't as likely server side... and there are ways of securing DLLs to your client, and your client to specific DLLs... it just takes some brain work. :wink:
-----
You can use function:
IsDebuggerPresent
Example:
call IsDebuggerPresent
test eax, eax
jne @DebuggerDetected
...
IsDebugged:
mov eax, fs:[30h]
mov eax, byte [eax+2]
test eax, eax
jne @DebuggerDetected
NtGlobalFlags
mov eax, fs:[30h]
mov eax, [eax+68h]
and eax, 0x70
test eax, eax
jne @DebuggerDetected
more and more.... blah blah
@zahara: Oh yes, quite. I understand you now.
The API is too easy to disable before you start debugging IMHO, but there are many ways to detect debugger and many ways to deal with it.
There are even some reasonable ways to deal with basic disassemblers and decompilers, and in my experience they work better for hindering prying eyes than encrypting.
Thanks for that note Gregoo. I believe I built that one with PeaZip 2.7 and tested it also with ALZip 6.7 and WinRAR 3.80. WinRAR reads the catalogue of files, but decompression results in an "epic fail". XD WinRAR really is a sucky program.
6.7 is the last "free for all, for ever" version of ALZip IMS, and I bloged elsewhere on how to remove the most annoying banners for their other tools from it. XDIt is compressed using the newer LZMA2, but they are in final beta for the .xz format, (which should utilize LZMA2 better than 7z and be more portable) and LZMA2 (according to benchmarks) is faster and uses less memory than standard LZMA for the same compression level.
The fact that it is under the Free Software Foundations GNU license means it will always be freely available to all, and a choice of programs will always be available to deal with the archive. Which is one big reason I like to use it.
Very nice guide, I like how you organized it with a Contents table.
Thanks.
Thanks AB. It's nice to know these tutorials are being read and people find them useful. :D:
This one has some waffle in it, because different people come here with different levels of skills and depths of understanding. I've tried to indent and aside anything that isn't key to the tutorial, but just background info.
You can see from the edits that I do also try to maintain my tutorials... if people are still posting about them. :wink:
So are Xternal okay with using DLLs to extend future clients?
It's definitely nice to start to write real code by attaching a DLL.
Though, you seem to attach a dll, then write that DLL in asm, that's just confusing. Go write it in C++! Or atleast C, but that's still stupid since PT is written in C++ ;)
offtopic: whats with the tags of this topic? 'nerd loser, retarded faggot'.
:o I hadn't noticed that. I guess one of the Admins holds this opinion of me, as nobody else has the power to change my topic tags I think. :(: Maybe because I forgot to tag, or just because I multi-posted and filled in the info later (to avoid a tutorial interspersed by chat, that I could add chapter links to) or just because I hold some strong, and unusual, opinions that I will keep. (not that I insist everyone agrees with me on them) Like Object Orientation does not make code more reusable, good documentation does that. Or Instant Messengers and Mobile Phones are EVIL. :lol: I've fixed them now.
---EDIT---
Correction... I can edit DarkKnightH20s left click tags and call him names anonymously, it seems... (of course I wouldn't) :(:
---EDIT---
Hmm. Well, yes you could use C++, but I hate it, it's too Object Orientated and even COBOL is easier to read. You could use C, but I don't really see any advantage to writing C when you can build fast, clean, clear assembler. It builds quicker, is more within your own control and despite popular opinion, I find Assembler just as readable as C. I learned and used C when I needed code to run on Z80, 68000 and 80x86 CPU based systems, but if I'm targeting only one CPU family, I don't need C.
As I've already said, you could use "useful" languages, which are more readable than Assembler, like Basic, or Pascal. XD But since the first things we are likely to want to do, is export code that is already in the game.exe or server.exe, and add to it (maps, levels, items, ages, file & data formats etc. etc.) and know that our DLL is isolated, and works just as it did when it was part of the main exe, before we port that code to a higher level language... Assembler is where I shall start.
Besides, most people here wanted to write in C# or VB.net when I mentioned importing DLLs, which is clearly not practical... you want C++ or C, I want Basic or Pascal (but I'm fine with C)...
x86 Assembler is one language we all have to be familiar with if we are even going to attempt this. If we are not comfortable with Assembler, Olly will be too meaningless to achieve the connections between our DLL and the main game. So x86 Assembler it is for Part 1.
What should be Part 2?
- Using DLLs written in a variety of other languages?
- Loading DLLs, and importing their functions dynamically? (using LoadLibrary() or LoadLibraryEx())
- Exporting functions and data from game.exe to a DLL?
I have some I've built in Dev-C++, (and they are C++, rather than C as well; interestingly) and some I've built in FreeBASIC, I'd like to have some in Free Pascal and maybe VB6, as that used to be popular here before MS went all .NET on us. XD
I can build BCB DLLs, but there's too little difference between that and Dev-C++ unless you use the VCL. (There's no point for PT I think) I don't have Delphi or Visual Studio, and don't see any reason to pollute my existing Development Environment (Borland / GNU) with MS nonsense. Lazarus is sufficient for my Pascal needs. :wink:
I have exported some sections with ease. I've Implemented most of the common KPTTrans code section patches in DLL form. I've written up patch code others have suggested putting in the exe as a DLL... but probably exporting all SQL functions in the server to a new PTSQL.dll that reads registry or ini configuration and doesn't need "hexing" and / or could be re-targeted at a different SQL database engine would be far more useful and interesting as a study.
If you're concerned with the client and the way it looks and feels on a users PC, then the LoadLibrary() routine, and a means of locking to specific DLL checksums would be far more useful to you. The ability to choose software, Direct3D or OpenGL as the rendering engine, setting volume levels and number of channels on sound, dynamic resolution and render (fog) distance all spring to mind and would make a great difference to performance vs. quality decisions for the users.
Each of these things needs discussing... but which should be given the greater priority for Part 2, I'm not yet sure... I hope the comments here will give me an idea of what people want most. Certainly, there is too much there to place in a single thread. No???
i cannot download the GoASM, GoRC and GoLink..can you give me any other link? thank you..bobsobol
Posted mirrors of the present releases using DepositFiles... hope that's okay for you rxaicy. :)
i am so sorry ,bobsobol,i cannot download them from Deposit..can you use rapidshare? thank you very much..thank you.
I'm afraid I can't / won't use RapidShare on principal... they won't let me upload any 1 file to more than 10 persons without me giving them much personal information and money. (That's uncalled for)
Please try these links, using hosts I know are popular among my Far East Asian friends.
Goasm.zip
Golink.zip
Gorcjorg.zip
GoASM
GoRC
GoLink
I also like http://uploading.com, http://www.zshare.net/, http://www.mediafire.com/ (thought they can mess you around long term moving their servers:(:), http://www.megaupload.com/ (though many people complain about the commercials on that one. I tried to use it from IE once and saw what they meant, but with my setup they all get filtered. XD)
I was offline for the last weeks, only now I noticed..
this is so usefull and so interesting, I never had a clue how to add a dll to my server.
I think this is the most usefull guide in the tutorial section.
really thank you bobsbol.
Soon when my school tests will be finished I will work on that.
off topic: I've saw that UserName said that pristontale was writen in c++.
If I want to edit the client/server exe with C + + this is not possible in the current situation. What to do with the EXE? Need to convert it?
Sorry about the lack of knowledge on the subject, thanks.
You need to buy the source code from Triglow. There is no way you are ever going to get C++ back from the exe, the compiler optimizer has destroyed the reversibility. Besides, you would struggle to understand C++ code with all the "formatting" removed. (new lines, indentation etc, not to mention code comments)
I've had several attempts at re-writing a complete client from scratch, using Olly as my template, and even from Assembler, it's clear that most of the code is C, not C++. C++ is used for the Component Object Model (DirectX and OLE) and very little else.
I don't "know" this because I've seen the source, I "smell" it in the library routines (part of the C runtime in all Visual C programs) which are, and are not called.
You can, of course, rename a C file CPP and it will still compile as C++... but it's still C code. XD
Ok the how a c++ or c progrommar can help in the pristontale world?only build a launcher..
so c and c++ is useless for pt am I right?
and just wondering, triglow will sell the sources?and if they does you have any idea what is the range?just wondering.
thanks..
For example you can write clan files in C#...
Actually C programmer would be great help for me right now :)
Heres server4096.c (attached) C code(pseudocode :)) migrate it to 64bits or compile it under Winelib so it will be more compatible with linux.
I wonder if its possible.
If its possible to do something with this we could start project where we would name all functions we know and than produce .c file that will be understandable.
Split in 2 files because ragezone cant take big files... if 1.10 MB is big XD
Only if your C# compiler allows you to set a build target of x86 Win32 PE... the last Visual C# I used would only build MSIL CLR assembly .exe / .dll files... and these cannot be called from x86 PE.
Can Mono do any better, or do newer, or more expensive VC# implementations allow you to compile machine native code.
C, C++, Basic, Fortran, Pascal (Delphi etc), ADA, even COBOL can be useful. But you must be able to build an x86 Win32 Portable Executable.
Visual Basic 5 and 6 could, VB4 and below only produced PCode... and the interoperability of those is questionable. VB 2005 + produce MSIL CLR binaries which are completely useless (for present PT servers). If you look at something like RapidQ, XBasic and many PHP, Python or Perl compilers, they actually only add an executable header stub, and maybe make some form of tokenised version of the source... Like VB 3 - 6 PCode. That's probably not going to help much.
On the other hand, if you took PHP, Python, Perl or Java and built the executable core (VM or whatever) into the server then you could use them to script it, just as it already supports LUA scripts for particle effects.
I've heard people talk about proxying between x86 / x64 and MSIL, but I've never seen any proof of a workable solution... which is a shame, because I know a lot of you like VB.net. :(
Here's the thing, if you have an x86 Windows OS, you can run 16 Bit LE, 16-bit MZ, 32-bit PE or MSIL CLR binary executables, but all the DLLs they link to (MZ can't link to DLLs but can load .OVLs or .MODs) must be of the same type as the primary executable, except MSIL CLR EXEs which can call pretty much anything, and that's the point of them. If you have an x64 Windows OS you can run 32-bit PE, or 64-bit PE+ or MSIL CLR EXEs, but any DLLs they call must be of the same type as the main executable, unless it is MSIL CLR EXE.Not sure what you mean about range... price or size I'm guessing. It's been suggested that the PT source code has been purchased / licensed either by Sandurr or other MMO developers like Yetime. I don't know if it's true, it's just a rumour, but the fact that Triglow still advertise PT as it was before Yedang got involved suggests that if the price was right, they may. I also don't know if one who wanted to make inquiries should contact Triglow directly, or via Yedang. That all depends on the agreement they have between them.
--- Edit ---
Triglow via HanGame (their owner?)
That is so hard to make any sense of, let alone compile.
Here are links to a Disassembly of the original jPT 4096 that will almost compile in FASM, and is much more readable. More readable even that the code listing in Olly.
Hotfile
4shared
Ziddu
You have to set the compiler memory above the normal 65536 that fasmw.exe will list... you can type 131072 to give 128Meg instead of 64Meg... and it will start to compile.
The code needs some cleaning, and humanised labels. There are some labels in the "undefined" allocated space in the exe that have no definition. It's easy, you look at those addresses in Olly and see how big they are, then define a "db x DUP (?)" where x is the number of bytes between that label and the next one.
You also need to fix imports and exports listed in the "Imports.txt" and "Exports.txt" files in FASM syntax.
macro.inc fixes some common MASM syntax that is meaningless to FASM, and I've done some extensive Search & Replace to fix "F8h" which is invalid in FASM, as you can say "0F8h" or "$F8" or "0xF8" but anything starting with a non-numeric character is a label, even if it ends with "h". And fixed the fmul, fmulp syntax from MASM style "fmul ST,ST(0)" to FASM "fmul st0,st0" and such.
MASM doesn't seem to cope with the source as well as FASM (In my tests) even though the syntax was produced to match MASM. :s
I've fixed at least one Korean text string which the disassembler can't recognise as anything other than plain bytes of data... it misses some references DWords too.
Anyway... so it needs some fixing. I have the source for the .res which still has some missing Korean text because I can't quite get an accurate decompilation of Korean "forms" yet... but that I built with GoRC so... Again, it's not hard.
If you want to create complete "compilable" source code for a PT server (or client) this is really the way to go. Once it builds, you can start re-writing routines in C or C++ and create .lib files from them that you can import into this, until such time as there is no pure x86 dependant assembler left.
BTW... from the point of view of an x64 (AMD64) or Linux build, FASM will build PE+ (Win x64) and Linux from the same source... you would have to use some "ifdef" type statements and write / re-write OS calls to equivalent routines. But this is not so difficult either... Time consuming, but not difficult.
For the client, it would be much harder, because nothing (OpenGL, Quartz Extreme, Core Services, Software Direct Layer etc.) works like DirectX, and PT doesn't use DirectX in any way Microsoft documents, so it's calls are very hard to understand.
Oh yes; Okay so the source packs to 1.2Meg from a 3.07Meg executable!!! :o However, using the same compression the binary compresses to 776Kb so, it's not so impressive. :wink:
I also used LZMA, not LZMA2 in this 7zip since the new compression method seems to cause people so much trouble with their "old archivers". (poke poke :wink:)
Playing with pseudo code giving a lot of information, like:
/Blowing DAVID! <-- this command must rock, whatever it do lolCode:/날린다뇨!
because right after it is txt:
}
{Code:T.T 흑흑 난 이제 틀렸어 ~~~
Boo-hoo ~ ~ ~ now I was wrong T.T
@_@ But who would be so crazy to fix ~389314 lines of code? Anyone Crazy here XD?
My x86 source is weighing in at 1'554'842 lines of code without the resource section. XD Once it works, I can define the string data the disassembler couldn't "guess" and the number of lines will reduce by the number of characters in each misidentified string... and once it works, we can if-def out the client code and imports from a server build, so I think it's well worth the effort.
I've got my x86 code to a point where it "compiles"... but it doesn't run yet, because the load time linking for DLLs isn't being filled in. ^_^ So yea... It looks like I'm that crazy, provided the errors are simple syntax variation.
A little search and replace, and a few macros go a long way.
The translation I get for "/날린다뇨!" is "/snowed!" or Yahoo! says "/Distinguishes the [nyo]! which"
Those strings can all be seen in Olly if your system codepage is set to Korean. :wink:
The "MrLee" commands are tantalizing too.Code:db '이방범: 스킬ShortKey 초기화',0
Align 4
SSZ005EBD44__LeeShotKeyReset:
db '/LeeShotKeyReset',0
Align 4
SSZ005EBD58__________________:
db '/이방범숏키초기화',0
Align 4
SSZ005EBD6C_MrLee__I_like_force_________:
db 'MrLee: I like force /(-_-)/ ',0
Align 4
SSZ005EBD8C__LeeForce:
db '/LeeForce',0
Align 4
SSZ005EBD98____________:
db '/이방범포스:진행중인퀘스트초기화',0
SSZ005EBDA4_____________________________:
db '/이방범퀘스트초기화',0
Align 4
SSZ005EBDC4____________________:
db '/이방범전업퀘스트',0
SSZ005EBDD8_MrLee_Reconnect_again___:
db 'MrLee:Reconnect again!! ',0
Align 4
SSZ005EBDF4_MrLee__Clear_Job_Quest_of_3th_:
db 'MrLee: Clear Job Quest of 3th ',0
Align 4
SSZ005EBE14_MrLee__Clear_Job_Quest_of_Moryon:
db 'MrLee: Clear Job Quest of Moryon 2th',0
Align 4
SSZ005EBE3C_MrLee__Clear_Job_Quest_of_Temscr:
db 'MrLee: Clear Job Quest of Temscron 2th',0
Align 4
SSZ005EBE64__LeeResetRankUp:
db '/LeeResetRankUp',0
SSZ005EBE74__________________:
db '/이방범전업퀘스트',0
Align 4
SSZ005EBE88_MeLee__it_s_good_________:
db 'MeLee: it',27h,'s good /(-_-)/ ',0
Align 4
SSZ005EBEA4__PassRankUp:
db '/PassRankUp',0
SSZ005EBEB0____________:
db /이방범전업',0
SSZ005EBEBC______________________:
db '잘쓰게(-_-)/ ',0
Align 4
SSZ005EBED4__________________:
db '/이방범증정아이템',0
Align 4
SSZ005EBEE8___________________________:
db '이방범: 스탯초기화/(-_-)/ ',0
Align 4
SSZ005EBF04__LeeHelpMeStat:
db '/LeeHelpMeStat',0
Align 4
SSZ005EBF14____________________:
db '/이방범도와줘요스탯',0
SSZ005EBF28___________________________:
db '이방범: 스킬초기화/(-_-)/ ',0
Align 4
SSZ005EBF44__LeeHelpMeSkill:
db '/LeeHelpMeSkill',0
SSZ005EBF54____________________:
db '/이방범도와줘요스킬',0
SSZ005EBF68_MrLee__OK__finished_Level_UP____:
db 'MrLee: OK! finished Level UP /(-_-)/ ',0
Align 4
SSZ005EBF90__LeeWhereIs:
db '/LeeWhereIs',0
SSZ005EBF9C________________:
db '/이방범순찰갔네',0
SSZ005EBFAC_MrLee__Try_again_Quest_:
db 'MrLee: Try again Quest~',0
SSZ005EBFC4_MrLee__Clear_90th_Quest__:
db 'MrLee: Clear 90th Quest ~',0
Align 4
SSZ005EBFE0_MrLee__Clear_80_2th_Quest__:
db 'MrLee: Clear 80_2th Quest ~',0
SSZ005EBFFC_MrLee__Clear_90_2th_Quest__:
db 'MrLee: Clear 90_2th Quest ~',0
SSZ005EC018_MrLee__Clear_85th_Quest__:
db 'MrLee: Clear 85th Quest ~',0
Align 4
SSZ005EC034_MrLee__Clear_80th_Quest__:
db 'MrLee: Clear 80th Quest ~',0
Align 4
SSZ005EC050_MrLee__Clear_70th_Quest__:
db 'MrLee: Clear 70th Quest ~',0
Align 4
SSZ005EC06C_MrLee__Clear_55th_Quest__:
db 'MrLee: Clear 55th Quest ~',0
Align 4
SSZ005EC088_MrLee__Clear_30th_Quest__:
db 'MrLee: Clear 30th Quest ~',0
Align 4
SSZ005EC0A4__LeeResetQuest:
db '/LeeResetQuest',0
Align 4
SSZ005EC0B4__________________:
db '/이방범레벨퀘스트',0
Align 4
SSZ005EC0C8_MrLee_Retry_change_Job_Quest__:
db 'MrLee:Retry change Job Quest~ ',0
Align 4
SSZ005EC0E8__Lee3thRankUp:
db '/Lee3thRankUp',0
Align 4
SSZ005EC0F8________3________:
db '/이방범3차퀘스트',0
Align 4
SSZ005EC10C__Lee4thRankUp:
db '/Lee4thRankUp',0
Align 4
SSZ005EC11C________4________:
db '/이방범4차퀘스트',0
Align 4
SSZ005EC130__________________________:
db '임군:초기화해주는 센스!! ',0
Align 4
SSZ005EC14C________________:
db '/임군초보퀘스트',0
SSZ005EC15C_________________________________:
db '임군:머 그까이것 그냥 대충 해주지.. !! ',0
SSZ005EC184______100______:
db '/임군100퀘스트',0
Align 4
SSZ005EC194__s__s:
db '%s %s',0
Align 4
SSZ005EC19C__T_T_________________________:
db 'T.T 흑흑 난 이제 틀렸어 ~~~',0
Align 4
SSZ005EC1BC___________:
db '/날린다뇨!',0
Align 4
SSZ005EC1C8__________________:
db '/마군오오오오에요',0
I really enjoy reading your posts bobsobol you explain an incredible way, I always added a. dll directly by adding the entry point, after starting the operation, i demand free memory, add the calls correctly etc. Finally, you explained in an easier way to show how to give the function. dll, very good
Thank you Exellsior, both for caring to read, and for reminding me of this guide so I can add bits I've decided are easier along the way and fix my awful spellink. :ott1: