3

Using the New Vegas Script Extender C++ API for Fallout New Vegas, I am able to write to the process' memory directly at runtime through a .dll plugin that's loaded by the NVSE injector. But it is not a debugger, like CheatEngine, so there's no automatic handling of inserting new code to an existing assembly function.

Beginning of the assembly function, TryOpenPipboy, I want to inject code into: https://i.stack.imgur.com/xIqmA.png

EquipPlayerPipboy() function I want to inject: https://i.stack.imgur.com/Gaujf.png

The game has a loop called MainUILoop which runs every frame, and when the player presses tab without holding it down, TryOpenPipboy is called.

My guess is that I need to somehow create a new empty block of memory, edit a copy of the original TryOpenPipboy function, then replace all TryOpenPipboy calls with my modified variant. But that would make a hard incompatibility with any other NVSE plugins that also modify the MainUILoop in any way, as well as any that edit TryOpenPipboy.

Edit: Another possible way would be to change the jmp assembly to my C++ function, then call TryOpenPipboy from my function. That would mean TryOpenPipboy is still called, but is unmodified, increasing potential compatibility.

So how should I go about doing this?

5
  • Can you copy paste the assembly/code instead of using a screenshot?
    – Avery3R
    Sep 19, 2019 at 19:54
  • Sorry about that. Here is the assembly code for TryOpenPipboy: pastebin.com/feMycWYW
    – FiftyTifty
    Sep 19, 2019 at 20:05
  • And here is the assembly code for where TryOpenPipboy is called: pastebin.com/mT0szzek
    – FiftyTifty
    Sep 19, 2019 at 20:08
  • @FiftyTifty you can edit the question to add the code inline instead of images or external links (use code formatting)
    – Igor Skochinsky
    Sep 20, 2019 at 8:44
  • So basically you just want a hook? There are a huge number of ways to achieve this but the simplest is probably a JMP hook. I'm happy to explain the details of how it works if you want, but I suggest just using an existing library like MinHook either way. Note that if this game is online then it's very likely you'll be kicked or banned, CRC hashes on code sections is about the simplest detection of unwanted modifications you can have. Oct 21, 2019 at 10:55

2 Answers 2

1

If you just want to run code before or after TryOpenPipboy is called, you can overwrite the prologue of the function with a jmp to your function. Before you overwrite it, copy the instructions in the prologue to a new memory buffer, and add a jump to the rest of the function on the end, this is called a trampoline. You can use the trampoline to call the original function.

Before:

:originalFunction
push ebp ;55
mov ebp, esp ;89 E5
sub esp, 0x40 ;83 EC 40
;...
ret

After patching:

:originalFunction
jmp yourFunction ;?? ?? ?? ?? ??
nop ;this used to be the 0x40 byte at the end
:trampolinetarget
;...
ret

yourFunction:
;do stuff
call trampoline ;calls the original function
;do stuff
ret

trampoline:
push ebp
mov ebp, esp
sub esp, 0x40
jmp trampolinetarget

If something else tries to hook the original function, they'll just overwrite that jmp instruction, and their trampoline will call your function. Microsoft's Detours library can handle all of this for you.

5
  • The examples provided in the Detours library are not that intuitive, especially with the lack of fully fledged documentation, so I tried to make use of NVSE's WriteRelCal() function, and C++'s ThisStdCall(), but I'm hopelessly lost as there's no documentation for them either. I did however find a more simple example of Detours: github.com/Alenett/TES-Reloaded-Source/blob/master/TESReloaded/… So how would I use this? I've got the address where the ASM function is called, and I want to insert my own C++ function in the call's place, and then call the ASM function from it.
    – FiftyTifty
    Sep 20, 2019 at 2:53
  • Here is the project as it is now: mega.nz/#!HgMm2YJK!b75ABK-s4wuu63cm8FIxkNmMinmWrVW7xiLBibQv42E
    – FiftyTifty
    Sep 20, 2019 at 2:54
  • Take a look at this example, github.com/microsoft/Detours/blob/master/samples/opengl/… they start a transaction, tell the library which thread is the current thread, give it a pointer to the original function that will be replaced with a pointer to the trampoline, then commit it. When they commit it, the first thing it does is suspends all other threads, this is to prevent race conditions and is why you need to tell it what thread you're running on. After that, it does similar patches to what I showed in my answer. Just give it the addrof the func you want to hook, and ur func
    – Avery3R
    Sep 20, 2019 at 7:25
  • Thanks for that, the explanation cleared it up for me. However, Detours now throws four errors when compiling, all of them being unresolved external symbol. That error should just apply when calling a function from an #include that isn't exposed, but that's not the case here. They're too long to post in a comment, so I put them on pastebin: pastebin.com/fT7jk1Um
    – FiftyTifty
    Sep 20, 2019 at 19:14
  • @FiftyTifty Some of the errors seem to originate from not linking with detours.lib (or a similar name). But especially the first and the last seem to be different. Could you post the exact code, including compiler-options here? Sep 22, 2019 at 10:19
0

Intel PIN in Probe Mode can do just. You can inject C code and PIN will even handle the context switching for you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.