ME3Tweaks Forums

Modding forum for the Mass Effect series

Mass Effect 3 ASI Mods

Discuss Mass Effect 3 mods and modding tools here. This includes multiplayer mods.

Postby Mgamerz » Fri Feb 12, 2016 1:27 am

ErikJS and I (well... mostly him) have tested out an ASI loader that loads mods that run in real-time rather than precompiled. It works by loading a .asi file in the asi folder near MassEffect3.exe and loads them into memory. (.asi files are .dll files renamed). Using ErikJS' ASI loader binkw32 we can load up a lot of these at once.

ASI MODS ARE PRETTY ADVANCED. C++/C KNOWLEDGE IS PRETTY MUCH REQUIRED.

What's an ASI File
ASI files are named after... GTA Vice... city? modding method? Can't recall. But essentially it loads a DLL that contains our custom code and we can "hook" onto a function call to execute our code before the actual function call. In doing this it is also possible to entire negate a function call by modifying the stack.

Why is an ASI file useful
ASI files are loaded into memory and allow us to write our own code. Not exactly like "write a new class for Mass Effect 3" but more like "reset this variable on this trigger" or modify parameters going to a function. In some instances we can write additional tools like a new HUD item (like GTA V's native trainer). Essentially you can call any method that ME3 can with whatever parameters you want and modify any variables.

ASI mods essentially autoinject a dll for you, like an advanced version of ME3 On-The-Hook that Warranty Voider worked on.

What's the risk of ASI mods
ASI mods are loaded into main memory and are essentially executables when the game is run. As such there is inherent risk while using such mods from untrusted developers. It's very unwise to run untrusted code, so make sure if you have an ASI mod, you compile it from source code or use only vetted ASI mods.

How do I get/use ASI mods
Right now there isn't really any ASI mods beyond a couple ErikJS made for demonstration purposes. Mod Manager will eventually be able to manage these for you once we get some up and running.
User avatar
Mgamerz
Site Admin
 
Posts: 571
Joined: Wed Jan 06, 2016 1:13 am

Postby Mgamerz » Fri Feb 12, 2016 3:38 am

I am trying to hook into a function so I can call some of my code on every call of this unreal function.

I'm using the following code with the MP SDK:

Code: Select all
void __declspec(naked) ProcessEventHooked()
{
   __asm mov pCallObject, ecx;
   __asm
   {
      push eax
      mov eax, dword ptr[esp + 0x8]
         mov pUFunc, eax
         mov eax, dword ptr[esp + 0xC]
         mov pParms, eax
         mov eax, dword ptr[esp + 0x10]
         mov pResult, eax
         pop eax
   }
   __asm pushad
   if (pUFunc)
   {
      /*
      Perform logic here for function that was hooked.
      This is called before the event is processed.
      */
      printf("Searching for object...");
      UObject* pWaveHorde = UObject::FindObject<UObject>("Class sfxgamempcontent.SFXWave_Horde");
      printf("Completed hook search.");
      if (pWaveHorde) {
         printf("Entered hook function");
         USFXWave_Horde* horde = static_cast<USFXWave_Horde*> (pWaveHorde);
         printf("Casted hook");

         if (originalMaxEnemies == 0) {
            originalMaxEnemies = horde->MaxEnemies;
         }
         int fuzzy = rand() % 8 + 1;
         //USFXWave_Horde: public Usfxwave
         horde->MaxEnemies = originalMaxEnemies + 4 - fuzzy;
      }
      
   }
   __asm popad
   __asm
   {
      push pResult
      push pParms
         push pUFunc
         call ProcessEventOrig
         retn 0xC
   }
}

bool Hook()
{
   bool hookedFunction = false;
   GObjObjects = (TArray< UObject* >*)GObjects;
   while (hookedFunction == false) {
      printf("Searching hooked function\n");
      pWaveStartFunc = UObject::FindObject< UObject >("Function sfxgamempcontent.SFXWave_Horde.BeginWave");
      printf("Searched for obj");
      if (pWaveStartFunc)
      {
         printf("Found hooked function\n");
         dwOldVMT = *(PDWORD*)pWaveStartFunc;
         ProcessEventOrig = dwOldVMT[70];
         ProcessEventOrig = (DWORD)DetourFunction((PBYTE)ProcessEventOrig, (PBYTE)ProcessEventHooked);
         printf("Hooked the wave start event.\n");
         hookedFunction = true;
      }
      else {
         printf("Hook not found, waiting 1s\n");
         Sleep(1000);
      }
   }
   return true;
}


void onAttach()
{
   AllocConsole();
   AttachConsole(GetCurrentProcessId());
   freopen("CON", "w", stdout);
   printf("ATTACHED DLL.");
   Hook();
   printf("END OF ONATTACHED()");
}


Game crashes instantly when I inject it (dies same on asi loading too).Seems to crash when I call this:

UObject* pWaveHorde = UObject::FindObject<UObject>("Class sfxgamempcontent.SFXWave_Horde");


I took the "class ... " from the comments section above that class definition in the SDK. I don't know what's wrong here. I noticed some GObject's point to null values... would that cause it?
User avatar
Mgamerz
Site Admin
 
Posts: 571
Joined: Wed Jan 06, 2016 1:13 am

Postby Erik JS » Fri Feb 12, 2016 1:04 pm

I'm having troubles with FindObject here too, still haven't found out what's causing this...
User avatar
Erik JS
 
Posts: 110
Joined: Sun Jan 10, 2016 8:03 pm
Location: Brazil

Postby Mgamerz » Fri Feb 12, 2016 3:36 pm

Time for some printf's =)
User avatar
Mgamerz
Site Admin
 
Posts: 571
Joined: Wed Jan 06, 2016 1:13 am

Postby Erik JS » Fri Feb 12, 2016 10:47 pm

So I used a text comparison tool to confront my generated files against WV's. Turns out it was a case of RTFM:

Image

Hint: Core_classes.h.

Everything is working as intended now (that is, when it doesn't crash lol).

Edit: testing some shit now, I'll come back with one screen and one video. :)
User avatar
Erik JS
 
Posts: 110
Joined: Sun Jan 10, 2016 8:03 pm
Location: Brazil

Postby Mgamerz » Fri Feb 12, 2016 10:51 pm

So can I just use warrantyvoiders processeventhooked code for all hooked calls? Does the ASM intro and outro work or do I have to custom write ASM to manipulate the stack? I haven't tested yet but I also am only kind of sure of what it's doing (pushing VARS on stack. When our code is complete, the stack is popped and original execution continues)...?

Edit: Commented out that pointer, and sure enough, it didn't immediately crash. We'll see if my hook works still though.

Edit: Don't know if something is wrong, or if you can't have any mods installed (e.g. controller mod...) because when I call FindObject... sfxgamempcontent.SFXWave_Horde, cast it, and hten access ->MaxEnemies, it returns 0. Setting this value will crash the game. It should return 8.
User avatar
Mgamerz
Site Admin
 
Posts: 571
Joined: Wed Jan 06, 2016 1:13 am

Postby Erik JS » Sat Feb 13, 2016 12:58 pm

I added the SDK to ClientMessage Exposer, and managed to do this:
http://www.mediafire.com/view/40r8bur0bjplxxg/consoletext.png

Test of a MP-specific mod which reads current number of consumables and shows it as a ticker:
https://youtu.be/TsQOgUFYkBU

Mgamerz wrote:So can I just use warrantyvoiders processeventhooked code for all hooked calls?

Depends on the call... As we saw back in ME3Ex, not everything shows up there.
Also depends on what you want to do (some things have to be done outside ProcessEvents, VS warns about stuff).

Mgamerz wrote:Edit: Don't know if something is wrong, or if you can't have any mods installed (e.g. controller mod...) because when I call FindObject... sfxgamempcontent.SFXWave_Horde, cast it, and hten access ->MaxEnemies, it returns 0. Setting this value will crash the game. It should return 8.

Looking at the code you posted earlier, I'd say this is something I wouldn't use in this case:
Code: Select all
UObject* pWaveHorde = UObject::FindObject<UObject>("Class sfxgamempcontent.SFXWave_Horde");.

You're looking for a class, which like is a model for an object. You have to find an actual instance of that class. Something like:
Code: Select all
UObject* pWaveHorde = UObject::FindObject<UObject>("SFXWave_Horde TheWorld.SFXWave_Horde");


For the console text thing I made above, I needed to find "LocalPlayer"... so I searched for "LocalPlayer " (with one trailing space for obvious reasons):
Code: Select all
77113 : 0x1F4AAF00 SFXLocalPlayer SFXGame.Default__SFXLocalPlayer
103422 : 0x0492D700 LocalPlayer Engine.Default__LocalPlayer
105800 : 0x0C92F500 SFXLocalPlayer Transient.SFXEngine.SFXLocalPlayer

"Default_" also doesn't work for this, so I went with the third result.
User avatar
Erik JS
 
Posts: 110
Joined: Sun Jan 10, 2016 8:03 pm
Location: Brazil

Postby Mgamerz » Sat Feb 13, 2016 7:10 pm

The item I am looking for in this instance:

SFXWave_Horde's Max Enemies count (max num enemies that can be spawned at once). This is:

SFXGameMPContent.SFXWave_Horde.MaxEnemies (as an export). However, when I do an object dump, SFXWave_Horde doesn't show up beyond the "Class ..." one I listed above. The only other instance is the default__ which is just some predetermined values to load into the instance it makes. I don't see the instance made, or it's subclass, anywhere it seems.

However I do see this:
113252 : 0x1B799920 IntProperty sfxgamempcontent.SFXWave_Horde.MaxEnemies

Do exports get made into their own objects? I can fetch this object but I am not how to actually access the data for a UIntProperty. There are no methods.

I seem to be noticing that once I inject the ConsoleUtil.dll that WV made and close the game, I can no longer open it unless I log out and back into windows.
User avatar
Mgamerz
Site Admin
 
Posts: 571
Joined: Wed Jan 06, 2016 1:13 am

Postby Erik JS » Sat Feb 13, 2016 7:47 pm

Mgamerz wrote:I seem to be noticing that once I inject the ConsoleUtil.dll that WV made and close the game, I can no longer open it unless I log out and back into windows.


New version of ObjNameDumper, dumps full names now. Should be faster than ConsoleUtil (not really, maybe...), and can be used at anytime (and more than once without closing the game):
https://github.com/Erik-JS/Misc-Stuff/blob/master/ObjNameDumper/ObjNameDumper.cs

Binary for those who want a binary:
http://www.mediafire.com/download/7ff1dd7qku71tok/ObjNameDumper.zip

Edit: I forgot to say that some entries which return "(null)" under ConsoleUtil will appear with proper name (those are marked with "(no Outer)").

Edit 2: Also, I'll make another version where ObjNameDumper will accept parameters (for logging only specific names).

Edit 3: update v3. Now, ObjNameDumper will use the "Num" part of GObjects as shown in Feckless' video (and also what WV used) to walk through the list of objects. More objects appear on the log now!
Output now uses some color. 8-)

When parameter starts with "0x":
ObjNameDumper 0x02FA1C00 - will show the name of object at 0x02DA1C00

Anything else:
ObjNameDumper command - will log into file anything where its fullname contains "command" (case insensitive).
ObjNameDumper "function " - will log all functions (use quotes to preserve space as part of the parameter).

Edit 4:
Mgamerz wrote:ASI files are named after... GTA Vice... city?

This post on GTAForums explains how people started using ASI files for modding:
http://gtaforums.com/topic/834970-questions-about-asi-plugins/?p=1068327193

Also, following a link to his site on Wikipedia (Miles Sound System) I talked to someone who works at RAD Games Tools... I guess this is pretty much "straight from the horse's mouth", considering the person's name. ;)
http://www.mediafire.com/view/f9bdsxawq9uoow8/asi.png
User avatar
Erik JS
 
Posts: 110
Joined: Sun Jan 10, 2016 8:03 pm
Location: Brazil

Postby Mgamerz » Fri Feb 19, 2016 5:01 am

Any progress on this front Erik JS?
User avatar
Mgamerz
Site Admin
 
Posts: 571
Joined: Wed Jan 06, 2016 1:13 am

Next

Return to Mass Effect 3