Got this working on my 2017-06-14b Client with Secret Nemo w/ the Patch for 64k from both people on this page.
I have 84 hairstyles working, but problem I'm having is 1-29 for Male and Female Hairstyles, about half of them work with Color Palette's 1-8 (which are the default hair colors). I'm using the 553/251 Cloth/Hair Dye pack. I figure it's somewhere in the Coding for 64k Hairstyles. With it turned off the Palette's work perfectly, but of course I can only use 29 Hairstyles then.
My Enable64kHairstyle.qs:
//####################################################//# Purpose: Disable hard-coded hair style table and #//# generate hair style IDs ad-hoc instead #//####################################################
function Enable64kHairstyle(){//Step 1a - Find address of Format String
var code ="\xC0\xCE\xB0\xA3\xC1\xB7\\\xB8\xD3\xB8\xAE\xC5\xEB\\%s\\%s_%s.%s";// "인간족\머리통\%s\%s_%s.%s"
var doramOn =false;
var offset = exe.findString(code, RAW);if(offset ===-1){//Doram Client
code ="\\\xB8\xD3\xB8\xAE\xC5\xEB\\%s\\%s_%s.%s";// "\머리통\%s\%s_%s.%s"
doramOn =true;
offset = exe.findString(code, RAW);}if(offset ===-1)return"Failed in Step 1 - String not found";//Step 1b - Change the 2nd %s to %u
exe.replace(offset + code.length -7,"75", PTYPE_HEX);//Step 1c - Find the string reference
offset = exe.findCode("68"+ exe.Raw2Rva(offset).packToHex(4), PTYPE_HEX,false);if(offset ===-1)return"Failed in Step 1 - String reference missing";//Step 2a - Move offset to previous instruction which should be an LEA reg, [ESP+x] or LEA reg, [EBP-x]
var fpEnb =HasFramePointer();if(!fpEnb)
offset = offset -4;else
offset = offset -3;if(exe.fetchUByte(offset)!==0x8D)// x > 0x7F => accomodating for the extra 3 bytes of x
offset = offset -3;if(exe.fetchUByte(offset)!==0x8D)return"Failed in Step 2 - Unknown instruction before reference";//Step 2b - Extract the register code used in the second last PUSH reg32 before the LEA instruction (0x8D)
var regNum = exe.fetchUByte(offset -2)-0x50;if(regNum <0|| regNum >7)return"Failed in Step 2 - Missing Reg PUSH";if(fpEnb)
regc =(0x45|(regNum <<3)).packToHex(1);else
regc =(0x44|(regNum <<3)).packToHex(1);//Step 2c - Now look for the location where it is assigned. Dont remove the AB at the end, the code size is used later.if(fpEnb){//VC9-VC10
code =" 83 7D AB 10"//CMP DWORD PTR SS:[EBP-y], 10 ; y is unknown+" 8B"+ regc +" AB"//MOV reg32, DWORD PTR SS:[EBP-z] ; z = y+5*4+" 73 03"//JAE SHORT addr ; after LEA below+" 8D"+ regc +" AB"//LEA reg32, [EBP-z];}else{
code =" 83 7C 24 AB 10"//CMP DWORD PTR SS:[ESP+y], 10 ; y is unknown+" 8B"+ regc +" 24 AB"//MOV reg32, DWORD PTR SS:[ESP+z] ; z = y+5*4+" 73 04"//JAE SHORT addr ; after LEA below+" 8D"+ regc +" 24 AB"//LEA reg32, [ESP+z];}
var offset2 = exe.find(code, PTYPE_HEX,true,"\xAB", offset -0x50, offset);if(offset2 ===-1){//VC11if(fpEnb){
code =" 83 7D AB 10"//CMP DWORD PTR SS:[EBP-y], 10 ; y is unknown+" 8D"+ regc +" AB"//LEA reg32, [EBP-z] ; z = y+5*4+" 0F 43"+ regc +" AB"//CMOVAE reg32, DWORD PTR SS:[EBP-z];}else{
code =" 83 7C 24 AB 10"//CMP DWORD PTR SS:[ESP+y], 10 ; y is unknown+" 8D"+ regc +" 24 AB"//LEA reg32, [ESP+z] ; z = y+5*4+" 0F 43"+ regc +" 24 AB"//CMOVAE reg32, DWORD PTR SS:[ESP+z];}
offset2 = exe.find(code, PTYPE_HEX,true,"\xAB", offset -0x50, offset);}if(offset2 ===-1)return"Failed in Step 2 - Register assignment missing";//Step 2d - Save the offset2 and code size (We need to NOP out the excess)
var assignOffset = offset2;
var csize = code.hexlength();//Step 3a - Find the start of the function (has a common signature like many others)
code =" 6A FF"//PUSH -1+" 68 AB AB AB 00"//PUSH value+" 64 A1 00 00 00 00"//MOV EAX, FS:[0]+" 50"//PUSH EAX+" 83 EC"//SUB ESP, const;
offset = exe.find(code, PTYPE_HEX,true,"\xAB", offset2 -0x1B0, offset2);if(offset ===-1){//const is > 0x7F
code =" 6A FF"//PUSH -1+" 68 AB AB AB 00"//PUSH value+" 64 A1 00 00 00 00"//MOV EAX, FS:[0]+" 50"//PUSH EAX+" 81 EC"//SUB ESP, const;
offset = exe.find(code, PTYPE_HEX,true,"\xAB", offset2 -0x2A0, offset2);}if(offset ===-1){// 2017 +
offset = exe.find(code, PTYPE_HEX,true,"\xAB", offset2 -0x2A0, offset2);}if(offset ===-1)return"Failed in Step 3 - Function start missing";
offset += code.hexlength();//Step 3b - Get the Stack offset w.r.t. ESP/EBP for Arg.5
var arg5Dist =5*4;//for the 5 PUSHes of the argumentsif(fpEnb){
arg5Dist +=4;//Account for the PUSH EBP in the beginning}else{
arg5Dist +=7*4;//Account for PUSH -1, PUSH addr and 5 reg32 PUSHesif(exe.fetchUByte(offset -2)===0x81)// Add the const from SUB ESP, const
arg5Dist += exe.fetchDWord(offset);else
arg5Dist += exe.fetchByte(offset);//Step 3c - Account for an extra PUSH instruction (security related) in VC9 clients
code =" A1 AB AB AB 00"//MOV EAX, DWORD PTR DS:[__security_cookie]; +" 33 C4"//XOR EAX, ESP+" 50"//PUSH EAX;if(exe.find(code, PTYPE_HEX,true,"\xAB", offset +0x4, offset +0x20)!==-1)
arg5Dist +=4;}//Step 3d - Prep code to change assignment (hairstyle index instead of the string)if(fpEnb){
code =" 8B"+ regc + arg5Dist.packToHex(1);//MOV reg32_A, DWORD PTR SS:[EBP + arg5Dist]; ARG.5}elseif(arg5Dist >0x7F){
code =" 8B"+(0x84|(regNum <<3)).packToHex(1)+" 24"+ arg5Dist.packToHex(4);//MOV reg32_A, DWORD PTR SS:[ESP + arg5Dist]; ARG.5}else{
code =" 8B"+ regc +" 24"+ arg5Dist.packToHex(1);//MOV reg32_A, DWORD PTR SS:[ESP + arg5Dist]; ARG.5}
code +=" 8B"+((regNum <<3)| regNum).packToHex(1);//MOV reg32_A, DWORD PTR DS:[reg32_A]
code +=" 90".repeat(csize - code.hexlength());//Fill rest with NOPs//Step 3e - Replace the original at assignOffset
exe.replace(assignOffset, code, PTYPE_HEX);//Step 4a - Find the string table fetchers
code =" 8B AB AB AB AB 00"//MOV reg32_A, DWORD PTR DS:[addr]+" 8B AB 00"//MOV reg32_B, DWORD PTR DS:[EBP]+" 8B 14"//MOV EDX, DWORD PTR DS:[reg32_B * 4 + reg32_A];
var offsets = exe.findAll(code, PTYPE_HEX,true,"\xAB", offset, assignOffset);if(offsets.length ===0){
code =" 8B AB"//MOV reg32_B, DWORD PTR DS:[reg32_C]+" 8B AB AB AB AB 00"//MOV reg32_A, DWORD PTR DS:[addr]+" 8B 14"//MOV EDX, DWORD PTR DS:[reg32_B * 4 + reg32_A];
offsets = exe.findAll(code, PTYPE_HEX,true,"\xAB", offset, assignOffset);}if(offsets.length ===0){
code =" 8B AB"//MOV reg32_B, DWORD PTR DS:[reg32_C]+" A1 AB AB AB 00"//MOV reg32_A, DWORD PTR DS:[addr]+" 8B 14"//MOV EDX, DWORD PTR DS:[reg32_B * 4 + reg32_A];
offsets = exe.findAll(code, PTYPE_HEX,true,"\xAB", offset, assignOffset);}if(offsets.length ===0){// 2017 +
code =" 8B AB"//MOV reg32_B, DWORD PTR DS:[reg32_C]+" A1 AB AB AB 01"//MOV reg32_A, DWORD PTR DS:[addr]+" 8B 14"//MOV EDX, DWORD PTR DS:[reg32_B * 4 + reg32_A];
offsets = exe.findAll(code, PTYPE_HEX,true,"\xAB", offset, assignOffset);}if(offsets.length ===0){// 2017 +
code =" 8B AB"//MOV reg32_B, DWORD PTR DS:[reg32_C]+" A1 AB AB AB 01"//MOV reg32_A, DWORD PTR DS:[addr]+" 8B 14"//MOV EDX, DWORD PTR DS:[reg32_B * 4 + reg32_A];
offsets = exe.findAll(code, PTYPE_HEX,true,"\xAB", offset, assignOffset);}if(offsets.length ===0)return"Failed in Step 4 - Table fetchers missing";//Step 4b - Remove the reg32_B * 4 from all the matchesfor(var i =0; i < offsets.length; i++){
offset2 = offsets[i]+ code.hexlength();
exe.replaceWord(offset2 -1,0x9010+(exe.fetchByte(offset2)&0x7));}//Step X - extra for 2017 clients, again Find the string table fetchers
code =" 8B AB AB AB AB AB"//MOV reg32_B, DWORD PTR DS:[EBP - 8C]+" A1 AB AB AB 00"//MOV reg32_A, DWORD PTR DS:[addr]+" 8B 14"//MOV EDX, DWORD PTR DS:[reg32_B * 4 + reg32_A];//need find probably 2 occurences
offsets = exe.findAll(code, PTYPE_HEX,true,"\xAB", offset-1000, assignOffset);if(offsets.length !==0){for(var i =0; i < offsets.length; i++){
offset2 = offsets[i]+ code.hexlength();
exe.replaceWord(offset2 -1,0x9010+(exe.fetchByte(offset2)&0x7));}}//Step 5a - Find the Hairstyle limiting comparison within the function
code =" 7C 05"//JL SHORT addr1; skips the next two instructions+" 83 AB AB"//CMP reg32_A, const; const = max hairstyle ID+" 7E AB"//JLE SHORT addr2; skip the next assignment - AB should be 06 or 07+" C7"//MOV DWORD PTR DS:[reg32_B], 0D;
offset2 = exe.find(code, PTYPE_HEX,true,"\xAB", offset +4, offset +0x50);//VC9 - VC10if(offset2 ===-1){
code =" 78 05"//JL SHORT addr1; skips the next two instructions+" 83 AB AB"//CMP reg32_A, const; const = max hairstyle ID+" 7E AB"//JLE SHORT addr2; skip the next assignment - AB should be 06 or 07+" C7"//MOV DWORD PTR DS:[reg32_B], 0D;
offset2 = exe.find(code, PTYPE_HEX,true,"\xAB", offset +4, offset +0x50);//VC11}if(offset2 ===-1&& doramOn){//For Doram Client, its farther away since there are extra checks for Job ID within Doram Range or Human Range
offset2 = exe.find(code, PTYPE_HEX,true,"\xAB", offset +0x100, offset +0x200);}if(offset2 ===-1)return"Failed in Step 5 - Limit checker missing";
offset2 += code.hexlength();//Step 5b - Change the JLE to JMP
exe.replace(offset2 -3,"EB", PTYPE_HEX);//Step 5c - Change 0D to 02 in MOV instruction
code = exe.fetchUByte(offset2);if(code ===0x04|| code >0x07)
exe.replace(offset2 +2,"02");else
exe.replace(offset2 +1,"02");//Remove the && 0 to enable for Doramif(doramOn &&0){//Repeat 5a & 5b for Doram race which appears before offset2. //Step 6a - Find the Hairstyle limiting comparison within the function for Doram race
code =" 7C 05"//JL SHORT addr1; skips the next two instructions+" 83 AB AB"//CMP reg32_A, const; const = max hairstyle ID+" 7C AB"//JLE SHORT addr2; skip the next assignment - AB should be 06 or 07+" C7"//MOV DWORD PTR DS:[reg32_B], 06;
offset = exe.find(code, PTYPE_HEX,true,"\xAB", offset2 -0x75, offset2 -0x10);if(offset ===-1)return"Failed in Step 6 - Doram Limit Checker missing";
offset += code.hexlength();//Step 6b - Change the JLE to JMP
exe.replace(offset -3,"EB", PTYPE_HEX);//Step 6c - Change 0D to 02 in MOV instruction
code = exe.fetchUByte(offset);if(code ===0x04|| code >0x07)
exe.replace(offset +2,"02");else
exe.replace(offset +1,"02");}returntrue;}//=================================//// Disable for Unsupported Clients ////=================================//
function Enable64kHairstyle_(){return(exe.getClientDate()>20111102);}
fix Enable 64k Hairstyle NEMO patch for 2017 clients
in Client Releases
Posted · Edited by Sukiyomi
added code
Got this working on my 2017-06-14b Client with Secret Nemo w/ the Patch for 64k from both people on this page.
I have 84 hairstyles working, but problem I'm having is 1-29 for Male and Female Hairstyles, about half of them work with Color Palette's 1-8 (which are the default hair colors). I'm using the 553/251 Cloth/Hair Dye pack. I figure it's somewhere in the Coding for 64k Hairstyles. With it turned off the Palette's work perfectly, but of course I can only use 29 Hairstyles then.
My Enable64kHairstyle.qs: