fuzziqersoftware / newserv

Phantasy Star Online game server, proxy, and reverse-engineering tools
MIT License
159 stars 35 forks source link

[BLUE BURST][Feature Request]Draw distance client-function patch? #556

Open PalasX opened 4 weeks ago

PalasX commented 4 weeks ago

Is your feature request related to a problem? Please describe. Not related to any specific problem

Describe the solution you'd like Possible to implement ASM draw distance patch for either a set float value or integer multiplier to be used with the run time client patches for BB? Idealy for all objects (floor items/meseta, boxes/crates, monsters, teleporters, switches, doors, etc.)

Game version(s) (choose one or more of the following): BB

Additional context I have this snippet of conversation from Sodaboy that i ALMOST grasp the concept of, but could never do anything with. Even with the blueburst patch project compiled and working my my copy of the client, im still drastically lost. I think the example he provided is only for floor items/meseta anyway.

All that said, I'll show you how our floor item draw distance modifier works.

First we modify the x86 at 5C5267h to call our own adjustFloorItemDraw procedure.

Original:

Code:
005C5267: 8B 54 24 14        mov         edx,dword ptr [esp+14h]

New C++ DLL code:

Code:
NOPSpace(0x05C5267, 0x08);  // This actually puts 8 NOPS in the existing code, it NOPS out the "fld dword ptr [esp+10h]" as well...
addrpatch = ((long)&adjustFloorItemDraw - 0x05C5267) - 5; // Offset between our function and the code we want to patch...
*(unsigned char*)0x05C5267 = 0xE8; // x86 "CALL"
*(long*)0x05C5268 = addrpatch;
// Calculated offset above

This effectively makes that line something along the lines of:

Code:
005C5267: E8 XX XX XX XX      call        ephineaDLL.adjustFloorItemDraw

Our adjustFloorItemDraw function is a code of ASM that looks like this:

Code:
void __declspec(naked) adjustFloorItemDraw()
{
    __asm {
        mov edx, [esp + 18h];
        fld dword ptr[drawModifier]; // Ephinea's own value for draw distance
        fld dword ptr[esp + 14h]; // Draw distance
        fmulp st(1), st(0);
        ret;
    }
}

FLD loads a floating point into the floating point register st(0), while also moving the current st(0) to st(1) and so on down the line...
FMULP multiplies floating points.

We use the __declspec(naked) to make sure that the C++ compiler doesn't try to modify or save any registers on it's own. Basically, making this a pure code block without any compiler interference like adjusting esp, ebp, pushes, pops, unless we explicitly do so.

Since we used a call for our function, we had to adjust esp by 4 because of pushing the return address to the stack. RET will pop the address and return to where we called from.

Hopefully this is some help to you.

You should be able to make your own procedures for adjusting enemy, object, player item and fade.

Internet Wayback machine has my back on this one: https://web.archive.org/web/20221210014611/https://www.pioneer2.net/community/threads/increasing-draw-distance-in-psobb-exe-12513.24387/#post-191868

If this is totally infeasible, or out of scope, or just way more work than it's worth, that's cool too! Never hurts to ask :) Thanks again!

fuzziqersoftware commented 2 weeks ago

This one is tricky because you have to write code in a location where it doesn't fit. The Ephinea DLL does this by simply writing the code within the DLL itself, and patching the source location to call that code. For an online (server) patch, you could call malloc(), write the code there, and patch the callsite to call the new code, similar to what Ephinea does. (They also take advantage of the ability to use global variables in their DLL's address space, which you don't necessarily have to do.) If you're patching the executable file statically, you would have to create a new code section or find unused space in an existing code section, write your code there, and patch the callsite in the existing code section to call code in your new section.

Creating a draw distance patch would probably be a lot of work, not because doing the above is hard (though it is tricky), but because it sounds like Sodaboy's patch above only applies to floor items. It could be that many different object types have many different draw distance functions, in which case we'd have to find and patch them all. I haven't done research on this personally, but I wouldn't be surprised if this is the case.

If you were to go the server-patch route, an adaptation of Sodaboy's patch might look something like this. (This won't work on newserv as-is because the x86 assembler doesn't support the float opcodes yet, but that may change soon.)

entry_ptr:
reloc0:
  .offsetof start

start:
  # Call write_call_to_code(patch_code, sizeof(patch_code), 0x005C5267, 8)
  push      8  # Callsite size
  push      0x005C5267  # Callsite address

  # Hack: the x86 assembler doesn't yet support label deltas in arguments, so
  # we instead put the label delta in memory, use call to get its address, then
  # read the value from there and push it on the stack.
  call      get_code_size
  .deltaof  patch_code, patch_code_end
get_code_size:
  pop       eax  # eax = address of .offsetof above
  push      dword [eax]  # Push value from .offsetof above

  # Hack (again): use the call opcode to push the address of patch_code to the
  # stack, for the first argument to write_call_to_code
  call      patch_code_end
patch_code:
  # This is the code that gets copied (permanently) to allocated memory.
  # First, execute the opcode that was replaced with a call to this function.
  mov       edx, [esp + 0x18]
  # Next, push 2.0 to the st register stack (without reading it from memory)
  fld1      # Push 1.0
  fld1      # Push 1.0
  faddp     # st0 = 1.0 + 1.0
  # Load the original draw distance (this is the other opcode that was replaced
  # by the call to this function)
  fld       [esp + 0x14]
  # Multiply the original draw distance by 2.0
  fmulp     st1
  ret
patch_code_end:

  call      write_call_to_code
  ret

write_call_to_code:
  .include  WriteCallToCode-59NL