Welcome!

Join our community of MMO enthusiasts and game developers! By registering, you'll gain access to discussions on the latest developments in MMO server files and collaborate with like-minded individuals. Join us today and unlock the potential of MMO server development!

Join Today!

[v90] Orion - Eric Class

Custom Title Activated
Loyal Member
Joined
Jan 18, 2010
Messages
3,109
Reaction score
1,139
Eric Class
Eric - [v90] Orion - Eric Class - RaGEZONE Forums

Since I'm bored and was testing some client stuff out for a friend, I figured why not release this now that implementing custom skills was publicly released.

Before I start, in case you're confused about what the heck an "Eric Class" is, check out Nexon's official April Fools video:


Furthermore, please note that this was specifically intended for v90 only, but can be altered obviously to fit any version. I never ended up updating this when we had moved to v92 since I was too busy working on resolution hacks, so this is all I got.

In order to pull this off you'll need a few tools:
- (password: tuts4you)
-
- STREDIT
- HaRepacker (or any alt. WZ editing tool)
-
-
- Basic understanding of assembly code

Step 1. Creating a new Section Header
After you've downloaded and configured the above tools, the first thing we're going to do is construct a new section within the MapleStory application. Go ahead and open CFF Explorer, then browse and open your Localhost.exe application. Once you've loaded it into the suite, navigate to the 'Section Headers' tab - you should see the current sections within the client:
Eric - [v90] Orion - Eric Class - RaGEZONE Forums

Scroll down to the bottom of the list like I've done, and right-click anywhere within the tab. Upon right-clicking, select the "Add Section (Empty Space)" option for a new window to appear.
Eric - [v90] Orion - Eric Class - RaGEZONE Forums

Within the new dialogue, input 1000h and click OK - this is going to allocate a new 4096-byte section.
Eric - [v90] Orion - Eric Class - RaGEZONE Forums

After the new section is created and loaded into the tab, double-click into the empty namebox and name the section. If done correctly, it will now look like this:
Eric - [v90] Orion - Eric Class - RaGEZONE Forums

Finally, you can save your new (and bigger!) client via File->Save As, and name it whatever you want. This is the client we'll be using to further perform assembly changes.

Step 2. Code-caving to create a new class
Since we've successfully created an entire new section for ourselves in the client, we're going to make the client jump there and continue executing additional instructions before jumping back. The first modification we're going to be doing is appending jobs to the client's get_job_name function.

Funfact: Ever wondered why adding a new job XML to the WZ files would always crash your client at startup? This is because that JobID doesn't exist in the get_job_name function. that we're about to modify ;-)

Load up OllyDbg and open the new client you just exported from CFF Explorer. If you open the Memory Map, you'll notice that it has loaded in your newly created section and its starting point address:
Eric - [v90] Orion - Eric Class - RaGEZONE Forums

Assuming you're using a clean v90 client without any previous changes and you did everything correctly, the address will be the same as mine (01161000).

Alright, so we need to move to the get_job_name code-cave point. Using the CTRL + G hotkey, jump to the address 004D299F. Now, select all of the instructions from that address down to 004D29CD and right-click on them. Navigate to Binary->Fill with NOPs and select the option.
Eric - [v90] Orion - Eric Class - RaGEZONE Forums

Once you've nopped out the instructions, assemble the instruction jmp 00D95F86 on the initial nopped address (SPACE hotkey). Then, click/highlight the new instruction and follow it (ENTER hotkey).

Let's repeat the same process - highlight and NOP from the initial address (00D95F86) to 00D95F88 (the first two lines). So now we're going to rewrite what we just removed, but we'll be executing it at the very end of the PE's code section. To make it easier for you so that there's no need to manually re-write each instruction, simply copy this to your clipboard:
Code:
C7 45 E4 B8 0D 00 00 68 48 17 00 00 8D 45 E0 50 E8 10 83 AD FF 8B C8 E8 16 0B 67 FF 50 8D 45 E4
50 8B CE E8 7F E1 73 FF 8D 4D E0 89 5D FC E8 89 09 67 FF E9 43 B0 3C 00
With the binary data copied to clipboard, highlight from address 00D95F89 to 00D95FBE and right-click to navigate to Binary->Binary Paste. This should restore the instructions that were previously removed.
Eric - [v90] Orion - Eric Class - RaGEZONE Forums

Before we continue, save your current changes (right-click, Copy to executable->All changes) and then load the saved executable! Since the next code-cave will be assembled outside of the PE's code section, it will not copy all of your modifications; you must save them separately.

If you've done everything correctly, then you can follow the last instruction (the custom section code-cave jump) by using the ENTER hotkey at address 00D95FBC. The location that you're at now is where you can add as many new jobs as you wish. Let us start by adding Eric Class - copy the following binary data to clipboard:
Code:
C7 45 E4 A0 0F 00 00 68 A4 06 00 00 8D 45 E0 50 E8 95 D2 70 FF 8B C8 E8 9B 5A 2A FF 50 8D 45 E4
50 8B CE E8 04 31 37 FF 8D 4D E0 89 5D FC E8 0E 59 2A FF E9 95 19 37 FF
Just like you did before, highlight from address 01161004 to 0116103C and perform a binary paste. Perfect, you're done! The client will now successfully register this new job, and once you've added it to your server so that you're able to job change into it, it will load the name of the job properly. Make sure the instructions that you just pasted are all highlighted, and save your changes (right-click, Copy to executable->Selection).

So how does the above structure work exactly? Each block of instructions that you just pasted contains two key identifiers: 1) the JobID to push into the hashmap, and 2) the StringPool ID that links to the string containing the name of the job. I have highlighted both below.
Eric - [v90] Orion - Eric Class - RaGEZONE Forums

Code:
01161004     C745 E4 D80E0000            MOV DWORD PTR SS:[EBP-0x1C],0xFA0 ; Job ID
0116100B     68 A4060000                 PUSH 0x6A4 ; StringPoolID of Class Name
01161010     8D45 E0                     LEA EAX,DWORD PTR SS:[EBP-0x20]
01161013     50                          PUSH EAX
01161014     E8 95D270FF                 CALL 0086E2AE ; StringPool::GetInstance
01161019     8BC8                        MOV ECX,EAX
0116101B     E8 9B5A2AFF                 CALL 00406ABB ; StringPool::GetString
01161020     50                          PUSH EAX
01161021     8D45 E4                     LEA EAX,DWORD PTR SS:[EBP-0x1C]
01161024     50                          PUSH EAX
01161025     8BCE                        MOV ECX,ESI
01161027     E8 043137FF                 CALL 004D4130 ; ZMap<long_ZXString<char>_long>::Insert
0116102C     8D4D E0                     LEA ECX,DWORD PTR SS:[EBP-0x20]
0116102F     895D FC                     MOV DWORD PTR SS:[EBP-0x4],EBX
01161032     E8 0E592AFF                 CALL 00406945 ; ZXString<char>::_Release
01161037   - E9 951937FF                 JMP 004D29D1 ; Return back to get_job_name

Step 3. Updating the StringPool
Open up STREDIT and browse for your new Eric Class client. On the right hand side you'll see a command textbox. Recalling that the StringPoolID was 1700 (decimal), type the command Id=1700 and click Run. It should fetch some unused korean string, just double-click the Content textbox and change it to the job name "Eric Class".
Eric - [v90] Orion - Eric Class - RaGEZONE Forums

Finally, simply File->Save it, and your client is all setup to become the Eric Class in-game!

Step 4. Adding new skills to the WZ files
At this point, the client is now capable of job changing into the Eric Class and will properly display it above your name. However, once you attempt to open your skill window, you'll crash with some sick WZ errors. This is because you're missing the skill tree for the class in the game files.

Go ahead and load up HaRepacker, and browse for your Skill.wz file. Select the WzFile node itself ("Skill.wz"), and navigate to Tools->Import->XML and select the Eric Class XML that you downloaded. You'll notice that the XML contains skills, but don't worry because we're going to be code-caving their functionality next. After the skill tree has imported ("4000.img"), you're done - go ahead and save it.

Unload the Skill.wz file, load String.wz, and then repeat the process. However, this time before importing the new Skill.img XML, delete the current one (select the "Skill.img" node and use DEL hotkey). Save it once it's finished importing, and you're finished with the WZ Editing portion.

Step 5. Code-caving to add custom skills
From here, you should now be able to get in-game with your new job and open up your skill window/allocate skill points, but unable to physically use any of the Eric Class skills. Let's fix that, shall we?

Open up OllyDbg and browse for your latest custom client. We must first redirect CUserLocal::DoActiveSkill to our custom region before it's able to register our dank new skills. Go to address 00AA43CC and assemble the instruction JG 01161105. Just like last time, the next edits are outside of this section and must be saved separately. So, save your changes to file and then load that new file into Olly.

Once you've loaded your last updated client, move to address 01161103. Right-click and navigate to Binary->Fill with NOPs on that address. Copy this last block of binary data to clipboard:
Code:
81 FE E8 5D 62 02 0F 8C 67 33 94 FF 81 FE E9 5D 62 02 0F 84 47 2F 94 FF 81 FE EA 5D 62 02 0F 84
1A 20 94 FF 81 FE EB 5D 62 02 0F 84 C0 29 94 FF 81 FE EC 5D 62 02 0F 84 B4 29 94 FF E9 54 33 94
FF 90
Then, highlight from address 01161105 to 01161147 and perform a binary paste. Voila, you're done! Make sure your changes are highlighted, and save the selection to file. This is the final result, so if you've followed all of my steps correctly then the Eric Class should be functional for you!

Alright, so now to explain what we just did in case you want to add more skills in the future, I've commented the checks it's performing at each jump below.
Code:
00AA43CC  - 0F8F 33CD6B00   JG      01161105 ; if (nSkillID > 35111012) jmp

01161103    90              NOP
01161104    90              NOP
01161105    81FE E8542502   CMP     ESI, 2625DE8
0116110B  - 0F8C 673394FF   JL      00AA4478 ; if (nSkillID < 40001000, aka invalid skill) jmp
01161111    81FE 69D94302   CMP     ESI, 2625DE9 ; Eric Class - Retreat (40001001)
01161117  - 0F84 472F94FF   JE      00AA4064
0116111D    81FE 6AD94302   CMP     ESI, 2625DEA ; Eric Class - Jump (40001002)
01161123  - 0F84 1A2094FF   JE      00AA3143
01161129    81FE 6BD94302   CMP     ESI, 2625DEB ; Eric Class - Camouflage (40001003)
0116112F  - 0F84 C02994FF   JE      00AA3AF5
01161135    81FE 6CD94302   CMP     ESI, 2625DEC ; Eric Class - Eric Blessing (40001004)
0116113B  - 0F84 B42994FF   JE      00AA3AF5
01161141  - E9 543394FF     JMP     00AA449A
01161146    90              NOP

Notice how I'm jumping to a few different locations based on the SkillID? This is because I'm calling to specific skill action functions. Below are the ones that I've personally noted and used.
Code:
// -- Buffs => 00AA3AF5		; CUserLocal::DoActiveSkill_StatChange
// -- Melee Attacks => 00AA2765 ; CUserLocal::DoActiveSkill_MeleeAttack
// -- Teleport => 00AA4064 	; CUserLocal::TryRegisterTeleport
// -- Flying => 00AA3C28 	; CUserLocal::DoActiveSkill_Flying
// -- Wings => 00AA3143		; CUserLocal::TryDoingWings

Well, that should be everything - hopefully I didn't forget anything. Enjoy!

- Eric
 
Experienced Elementalist
Joined
Apr 29, 2016
Messages
269
Reaction score
3
Great writeup, I look forward to digging further into this!

I'm intrigued by this, "I figured why not release this now that implementing custom skills was publicly released."
Is there some thread about custom skills that I missed? I would love to read about this.
 
Custom Title Activated
Loyal Member
Joined
Jan 18, 2010
Messages
3,109
Reaction score
1,139
Great writeup, I look forward to digging further into this!

I'm intrigued by this, "I figured why not release this now that implementing custom skills was publicly released."
Is there some thread about custom skills that I missed? I would love to read about this.

There was a new years release about adding new skills into the client by jumping out from the DoActiveSkill checks to handle your own custom skills. While I didn't explain how all this worked here (was just meant to be a how-to in adding the class), we're doing the same thing that they mentioned. One thing to note here is that they were using hooks, and this is very old so I had manually done all of this by hand through assembly. I would suggest using hooks to do this kind of stuff nowadays instead :p
 
Experienced Elementalist
Joined
Apr 29, 2016
Messages
269
Reaction score
3
There was a new years release about adding new skills into the client by jumping out from the DoActiveSkill checks to handle your own custom skills. While I didn't explain how all this worked here (was just meant to be a how-to in adding the class), we're doing the same thing that they mentioned. One thing to note here is that they were using hooks, and this is very old so I had manually done all of this by hand through assembly. I would suggest using hooks to do this kind of stuff nowadays instead :p

I found the thread. Thanks!
 
Junior Spellweaver
Joined
Apr 10, 2010
Messages
183
Reaction score
27
Nice work! BTW, some newer versions seems would detect skill roots(Job id) that outside their hard-coded range and would raise exception there. In that case, extra efforts to bypass CSkillInfo::IterateSkillInfo or CSkillInfo::LoadSkillRoot are necessarily required.
 
Newbie Spellweaver
Joined
Jan 31, 2019
Messages
50
Reaction score
18
Cool release!

One way you could make it easier is using a function that creates a ZXString<char> object from a given char* (it was probably called ZXString<char>::ZXString<char>(ZXString<char>* this, char* text) or something like that.) That way you could add those new conditions in get_job_name without having to change the stringpool at all and just have string literals in the client instead. I don't think anyone would add so much jobs for this to even matter but it's fun to think about :p

I'm wondering how well all these skill functions would handle custom IDs given that lots of them have a million nested if statements which their compiler optimized the hell out of. Probably lots of trial and error to figure out how each function would handle new IDs :/
 
Custom Title Activated
Loyal Member
Joined
Jan 18, 2010
Messages
3,109
Reaction score
1,139
Nice work! BTW, some newer versions seems would detect skill roots(Job id) that outside their hard-coded range and would raise exception there. In that case, extra efforts to bypass CSkillInfo::IterateSkillInfo or CSkillInfo::LoadSkillRoot are necessarily required.

Thanks, this is nice to know! I've already found in older clients they have a dynamic initializer for job names and have a separate function that returns the name from an array, and that function has job checks there too.


Cool release!

One way you could make it easier is using a function that creates a ZXString<char> object from a given char* (it was probably called ZXString<char>::ZXString<char>(ZXString<char>* this, char* text) or something like that.) That way you could add those new conditions in get_job_name without having to change the stringpool at all and just have string literals in the client instead. I don't think anyone would add so much jobs for this to even matter but it's fun to think about :p

I'm wondering how well all these skill functions would handle custom IDs given that lots of them have a million nested if statements which their compiler optimized the hell out of. Probably lots of trial and error to figure out how each function would handle new IDs :/

Indeed! In fact, I just hook get_job_name itself now and handle a list of custom jobs simply by checking the JobID and returning a custom string value :p

Depending on the skills you're trying to add, it's not too much of a hassle. If you wanted to duplicate some more extensive skills like flash jumps and stuff, you'll need to update a lot more than just jumping to a function haha.
 
Newbie Spellweaver
Joined
Mar 3, 2016
Messages
14
Reaction score
0
Can you tell me the addresses for v90's range attack and summons?
 
Back
Top