Replies: 13 (Who?), Viewed: 15951 times.
Field Researcher
Original Poster
#1 Old 13th Jul 2009 at 8:46 PM Last edited by lemmy101 : 15th Jul 2009 at 6:52 AM.
Default IL and DLL hooking overview / tutorial
IL and DLL overview / tutorial

First versions I'll elaborate on the different parts in more detail as I go on.

When writing core-mods, you will unavoidably have to delve into the world of Intermediate Language (IL) at some point or other. This is not a tutorial on setting up a DLL, or editing packages, but some basics on the IL language and how to read it and use it alongside the awesome Reflector to do all your modding.

Depending on how complicated your mod is going to be, your IL editing could be simply adding basic calls or removing the contents of methods, or changing the visibility of a class or method so it can be accessed from outside. More complicated use of IL may see you changing the execution path of the code, such as adding if statements, to make existing methods work differently.

This tutorial doesn't go into the specifics of ILDASM, ILASM, editing packages and whatnot. For a tutorial on these processes, check out the cool tutorial: http://www.modthesims.info/showthread.php?t=354419 by BailaBaila99. This tutorial will assume you can recreate those steps.

Differences between C# and IL

The main difference between C# and IL is the syntax. You'll notice when you first look at a bunch of IL code is it's a lot less immediately obvious what it is doing. There's no nesting with high level syntax such as 'for loops', 'while loops' or 'switch statements', instead everything is expressed as a long list of abbreviated instructions few if none of which are immediately obvious what they are doing.
Take this for example, which is the GetSide method inside the Door class in the Sims3GameplaySystems.dll, first of all in C#:

Code:
public tSide GetSide(uint side)
{
    if (side == 0)
    {
        return tSide.Front;
    }
    return tSide.Back;
}


So this is a method that takes in a number (side) and if that number equals 0, then it returns Front, otherwise it returns Back.

Now in C#, since we have the braces to nest blocks of code together, we can have an if statement that, if the result is true (side == 0) then the game will execute the code inside the braces. Otherwise, if the side == 1 or more, then the game will jump to the closing brace and continue execution to retun the 'Back' value.

In IL, there are no such things as braces within a method, and the code is on a much lower level, relying on slightly obscure inline instructions. Therefore it looks a little less clear what's happening:

Code:
  .method public hidebysig instance valuetype Sims3.Gameplay.Abstracts.Door/tSide 
          GetSide(uint32 side) cil managed
  {
    // Code size       7 (0x7)
    .maxstack  8
    IL_0000:  ldarg.1
    IL_0001:  brtrue.s   IL_0005

    IL_0003:  ldc.i4.0
    IL_0004:  ret

    IL_0005:  ldc.i4.1
    IL_0006:  ret
  } // end of method Door::GetSide


A little more scary than C#, but we'll disect what it's doing:

Code:
 .method public hidebysig instance valuetype Sims3.Gameplay.Abstracts.Door/tSide 
          GetSide(uint32 side) cil managed


The above code is the method's definition, just like:

Code:
public tSide GetSide(uint side)


Just in a little more verbose form, it's giving all the same information there, so we'll just take it that these are equivalent.

Then there is the open braces for the method (though no braces allowed inside here)
Ignoring comments ( lines beginning with // ) as they have no impact on the generated dll.

And then the following line:

Code:
.maxstack  8


This line is specifying how big the stack is.

The Stack

The stack, in case you're wondering, is like a list of things (number, objects, whatever) that you are using at a time. Though unlike a list, a stack behaves like... well, a stack of objects. So when you add something to the stack, it is piled on the top of it. 'Push' another thing to the stack and that is piled on top of that. Then the first time you take an object off the stack, it is taken off the top, so you'll get the last object you put on the stack back off of it. 'Pop' another off the stack and you'll get the second last object you placed. Keep going and last of all you'll take the first object you put on there.

So you 'push' objects onto the top of the stack, and 'pop' objects off the top of the stack.
In this case we have 8 'slots' on the stack, so we can push 8 objects on before having to 'pop' any off.

It's unlikely you'll need to worry too much about this.

The IL Code

If we look at the first line of the actual IL code we can see how it's structured:

Code:
    IL_0000:  ldarg.1


IL_0000: specifies a label for a line, so it can be referenced in other parts of the method, and can be anything you want (without a space, I assume, though I've not tried with a space). If you were to write some new IL code from scratch, you would only tend to have a label on a line of code of some importance, where you may want to 'jump' to from another section of code. But since this dll wasn't written in IL in the first place, but rather compiled in Visual C#, it doesn't have sensibly named labels and as such when you ildasm the DLL, it will simply label EVERY line with a default label in the format IL_XXXX (where XXXX is a number that I assume is related to the offset of the instructions, though the only important thing is they are all different)

So what does this instruction do? Well ignoring the IL_0000: label, the next thing is 'ldarg.1'. This is a 'load' instruction, as you can tell by the fact it starts with 'ld'.

A load instruction loads 'something' and plops it on top of the 'evaluation stack'. Remember that a stack is a 'last on, first off' pile of things, so if you need to use a value, it needs to be ON TOP of the stack.

The above 'load' is a 'load argument' or 'ldarg'. An argument is the variables passed into a method. To go back to the C# code:

Code:
public tSide GetSide(uint side)


As we can see, there is one argument, which is a uint number representing the 'side' that the method will use to determine which of the two return values it uses.

Thing is, this method is inside a class, therefore objects in the game that are of this class will have this method that others can call on it. In IL we do not have 'this' identifier to reference the object that the method is part of. In C# you could do 'this.Something()' inside a method to reference another method or variable on the instance that you're calling the method on. If this makes no sense then you'll need to read up a bit on C# on how instances of classes work.

So anyway, such a thing as 'this' doesn't exist in IL, so instead we treat the method like it's got another invisible first argument that's not present in the C# code:

Code:
public tSide GetSide(Door this, uint side)


So argument 0 (we always count from 0) is 'this door' and argument 1 is the 'side' unsigned integer number that would actually be argument 0 in the C# code, which uses 'this' instead of adding another argument.

So... from all this we can determine that the line:

Code:
    IL_0000:  ldarg.1


Is loading the number value of 'side' to the 'top of the evaluation stack'.

Next line is as follows:

Code:
    IL_0001:  brtrue.s   IL_0005


A 'br' is a jump instruction. A 'br' instruction will jump to another part of the IL method. After a br instruction will be the target label that the br should jump to. There are different br instructions, the two most common ones used being 'brfalse' and 'brtrue' which will jump if the thing on the top of the evaluation stack is 'false' or 'true' respectively (The .s means 'short-form' though I'm not entirely sure what the relevance of this is yet. :D) If the check fails, instead of jumping to the target instruction, it will carry on on the next line. 'br' is another command that will ALWAYS jump regardless of what is on the evaluation stack.
It is using all these that we can have IL code that does checks and has numerous paths of execution.

So in the above two lines:

Code:
      IL_0000:  ldarg.1
IL_0001: brtrue.s IL_0005

The first line loads the value of 'side' onto the top of the evaluation stack, and the second line sees if what the thing on top of the evaluation stack is 'true' and if so, will jump to the IL_0005 instruction. In programming, any non-zero number is classed as true, and 0 is classed as false. So in the above case, if 1 was passed as the 'side', then the game would jump to IL_0005. If the 'side' equals 0, then it would carry on on the next line of IL_0003 as such:

Code:
    IL_0003:  ldc.i4.0


This ld command loads a number between 0 and 8 onto the top of the evaluation stack. In the above case it's loading 0 onto the evaluation stack... then...

Code:
    IL_0004:  ret


This 'ret' command simply returns out of the method. Since this method returns a value (of the type 'tSide') which looks like this in C# code:

Code:
public enum tSide : uint
{
Back = 1,
Front = 0
}


The return returns the thing on top of the evaluation stack as the tSide that this method returns, in this case it has returned a 0, and so has returned tSide.Front.
If the 'side' value passed in was 1 instead of 0, then the call to:

Code:
      IL_0000:  ldarg.1


would instead be putting a 1 at the top of the evaluation stack, therefore the following 'br' jump line:

Code:
	IL_0001:  brtrue.s   IL_0005


would instead of carrying on at the next line, make the jump to IL_0005, skipping the code that returned the Front and running the following two instructions:
Code:
	IL_0005:  ldc.i4.1 
IL_0006: ret

The first one loads 1 onto the top of the evaluation stack, and the next line returns, returning 1 or 'Back' to the calling method.

Other IL instructions

Loads

Another two useful 'load' instructions are ldfld and ldsfld.

These two methods load a field from inside a class onto the evaluation stack. From example:

Code:
public class Bob
{
public int field;
}


In C#, if you had a Bob variable called 'myBob' you would access 'field' by calling:

Code:
myBob.field


Or if you were inside one of Bob's methods, you could access the 'field' by using:

Code:
this.field


In IL we access fields inside objects using ldfld.

To get at the 'field' variable inside the Bob class, we first need to get the Bob we want the field from onto the top of the evaluation stack. The Bob may be passed as an argument, or we may already be inside one of Bob's methods. Whatever way, we need to get him onto the evaluation stack, so if he was passed as the first argument into the method we would do:

Code:
ldarg.1


If we are inside the Bob we want the field from we would do the equivelent of 'this':

Code:
ldarg.0


Now, either way, we have our Bob that we need the field from on the top of the evaluation stack. Then we can call:

Code:
ldfld 	int Bob::field


And now the value of 'field' of this particular Bob should be at the top of the evaluation stack. This would generate a dodgy DLL if a Bob isn't on top of the evaluation stack when the ldfld is called, and likely it would generate a crash if the game hit that code.

Code:
ldsfld 	int Bob::field


You would also call the above ldsfld method in cases where the field is declared as 'static', which means it is a single value stored inside the class, and not a seperate value for each object of that type:

Code:
public class Bob
{
public static int field;
}


In the non-static version if you have 10 Bobs, each would have their own 'field' value, but in the static version all Bob's share the same 'field' value. In these cases you don't need a Bob on top of the evaluation stack for ldsfld, as there is only one field value we're interested in. So just call ldsfld without needing to push a Bob onto the evaluation stack first.

Rounding off the 'load' instructions, there is also 'ldloc', which we will cover in a later tutorial.

Calls

The most common IL instructions you will likely be adding. A 'call' will call a method on an object. There are two flavours:

Code:
call


and...

Code:
callvirt


The callvirt is a little more complicated so we'll leave that for now, but generally you won't have to worry about them.

A call can be used to call a static method inside your DLL. For example, if you download Visual C# Express and create a new 'Windows Class Library' project called MyMod and added the following class to it:

Code:
public namespace MyMod
{
public class MyClass
{
public static void Initialise()
{

}
}
}


If you create the above class in your DLL in Visual C#, and compile, you will then have this method exposed so that your Sims3GameplaySystems classes could call it.

To do this you first need to add a reference to your freshly ILDASM'ed Sims3GameplaySystems.il code.

At the top of the file, you'll notice a section with a bunch of .assembly bits at the top like this:

Code:
.assembly extern SimIFace
{
.ver 1:0:0:50
}


These specify a link or 'reference' between this DLL and other DLLs, so you can access the classes inside those DLLs.

We'll need to add a reference to our mod in there. In the above example where the name of the mod DLL and main namespace is MyMod we would add the following code:

Code:
.assembly extern MyMod
{
.ver 1:0:0:0
}


If you change the version of the mod in your C# DLL project then you will need to update it here. Most people just leave it as 1.0 as the two files will always be distributed together and you can assure they're both for the same version, so you can leave this alone.

So save, and there you have it. Provided your MyMod.dll is present in a package The Sims 3 can see, this Sims3GameplaySystems.DLL can access all the classes in your mod.

So now we've done this, open up the Sims3GameplaySystems.DLL in Reflector and have a look around for somewhere to add a hook into your MyClass.Initialise(). We're looking for somewhere that's called once when you load a saved world, after the world has loaded. The place we use is:

Code:
Sims3.Gameplay.InWorldState.Startup()


So search for that in Reflector by opening out the various namespaces / classes until you find the Startup() method. You'll see the following code:


Code:
public override void Startup()
{
base.Startup();
Responder.GameStartup();
if (World.IsEditInGameFromWBMode())
{
Sims3.Gameplay.Autonomy.Autonomy.DisableMoodContributors = true;
}
MapTagWin.LoadMapTagLayout();
this.mStateMachine = StateMachine.Create(1, "InWorld");
this.mSubStates[0] = new LiveModeState();
this.mSubStates[1] = new BuildModeState();
this.mSubStates[2] = new BuyModeState();
this.mSubStates[6] = new ShoppingModeState();
this.mSubStates[3] = new CASFullModeState();
this.mSubStates[4] = new CASDresserModeState();
this.mSubStates[5] = new CASMirrorModeState();
this.mSubStates[9] = new PlayFlowState();
this.mSubStates[8] = new EditTownState();
foreach (InWorldSubState state in this.mSubStates)
{
this.mStateMachine.AddState(state);
}
StateMachineManager.AddMachine(this.mStateMachine);
this.mPostWorldInitializers = new Initializers("PostWorldInitializers", this);
this.mPostWorldInitializers.Initialize();
Gameflow.GameSpeed pause = Gameflow.GameSpeed.Pause;
bool flag = false;
if (GameStates.StartupState == SubState.LiveMode)
{
flag = (GameStates.ForceStateChange || !PlayFlowModel.Singleton.GameEntryLive) || (PlumbBob.SelectedActor != null);
if (flag)
{
if (Gameflow.sGameLoadedFromWorldFile)
{
pause = Gameflow.GameSpeed.Normal;
}
else
{
pause = Gameflow.sPersistedGameSpeed;
}
}
}
Gameflow.sGameLoadedFromWorldFile = false;
string s = CommandLine.FindSwitch("speed");
if (s != null)
{
int num;
if (int.TryParse(s, out num))
{
pause = (Gameflow.GameSpeed) num;
}
else
{
ParserFunctions.TryParseEnum<Gameflow.GameSpeed>(s, out pause, Gameflow.GameSpeed.Normal);
}
}
Gameflow.SetGameSpeed(pause, Gameflow.SetGameSpeedContext.GameStates);
NotificationManager.Load();
GameUtils.EnableSceneDraw(true);
LoadingScreenController.Unload();
switch (GameStates.StartupState)
{
case SubState.EditTown:
this.GotoEditTown();
return;

case SubState.PlayFlow:
if (World.IsEditInGameFromWBMode())
{
this.GotoLiveMode();
return;
}
if (!PlayFlowModel.PlayFlowEnabled || !PlayFlowModel.Singleton.GameEntryLive)
{
this.GotoLiveMode();
return;
}
this.GotoPlayFlow();
return;

case SubState.LiveMode:
if (flag)
{
this.GotoLiveMode();
return;
}
this.GotoPlayFlow();
return;
}
}


We need to add a call to our 'Initialise' method from inside this method. It's a static method so we don't need a MyMod object with which to call it on, so the call will be quite simple. The best place to add it is after it unloads the loading screen, on the line:

Code:
LoadingScreenController.Unload();


So we do a search in the IL code with Ctrl-F in Notepad++ for LoadingScreenController and we see the following code:

Code:
IL_016f:  call       void [UI]Sims3.UI.NotificationManager::Load()
IL_0174: ldc.i4.1
IL_0175: call void [SimIFace]Sims3.SimIFace.GameUtils::EnableSceneDraw(bool)
IL_017a: call void [UI]Sims3.UI.LoadingScreenController::Unload()
IL_0184: call valuetype Sims3.Gameplay.InWorldState/SubState Sims3.Gameplay.GameStates::get_StartupState()
IL_0189: stloc.s V_7
IL_018b: ldloc.s V_7
IL_018d: ldc.i4.0


Note that your label number (IL_BLAH) may be different to mine, but that's not important for this example.

So we want to add the call after the LoadingScreenController::Unload() call, you may want to be careful when placing something after a call, as if the call has a return type (i.e. Not void like the Unload call) then the line after may store the return value. Storing (instructions beginning with st) are another important element of IL I'll explain in a follow-up tutorial. In fact that's exactly what's happening in the call immediately after it. The get_StartupState() is returning a 'Substate' and that is being stored in V_7. Exactly what V_7 means is outside the scope of this tutorial, just know if you wanted to add this call after that one, you would put it after the 'stloc' instruction, not after the call itself.

But we're placing ours after the LoadingScreenController:Unload() se we will copy and paste that line to make a copy of it directly on the line below, as such:

Code:
IL_017a:  call       void [UI]Sims3.UI.LoadingScreenController::Unload()
IL_017a: call void [UI]Sims3.UI.LoadingScreenController::Unload()
IL_0184: call valuetype Sims3.Gameplay.InWorldState/SubState Sims3.Gameplay.GameStates::get_StartupState()
IL_0189: stloc.s V_7


Remember that no two IL instructions inside a single method can have the same IL label, so change our new line (the second one) to have a different label:

Code:
IL_017a:  call       void [UI]Sims3.UI.LoadingScreenController::Unload()
MYMOD01: call void [UI]Sims3.UI.LoadingScreenController::Unload()
IL_0184: call valuetype Sims3.Gameplay.InWorldState/SubState Sims3.Gameplay.GameStates::get_StartupState()
IL_0189: stloc.s V_7


The call we've copied is accessing a class inside the UI.DLL. That is what the square brackets [UI] means. So we need to change this to point inside our mod, which is called MyMod:

Code:
MYMOD01:  call       void [MyMod]Sims3.UI.LoadingScreenController::Unload()


To access our class, we first write every namespace that the class is inside, seperated by a dot. In our case it is a single namespace called MyMod, so we do MyMod.MyClass...

Code:
MYMOD01:  call       void [MyMod]MyMod.MyClass::Unload()


Finally it's not Unload we want to call, but Initialise

Code:
MYMOD01:  call       void [MyMod]MyMod.MyClass::Initialise()


And that's it! If you ILASM the Sims3GameplaySystems.dll and package it with your mod, using the right instance Id for the core dll and a unique instance for your own dll, The Sims 3 should call this method the first moment after the loading screen is unloaded after the town is loaded.

Now using Visual C#'s intellisense, you can import Sims3 namespaces using:

Code:
#using Sims3.


And should be able to poke about in C# code at any aspect of Sims3 gameplay logic.

A useful thing to do in the initialise is this:

Code:
  mTimer = AlarmManager.Global.AddAlarmRepeating(StartTime,
TimeUnit.Minutes, new AlarmTimerCallback(OnTimer), Frequency,
TimeUnit.Minutes, "MyMod", AlarmType.NeverPersisted, null);


Where mTimer is an 'AlarmHandle' stored in your class, and StartTime and Frequency are the amount of time in in-game minutes between updates of your mod.

Then all you need to do is add the method:

Code:
public static void OnTimer()
{

}


And when your mod is first initialised on town load, it will start a timer that will call the OnTimer method every 'Frequency' in-game minutes. The first update being 'StartTime' minutes of game-time.

Now you have your mod initialised and running alongside game-time as frequently as you'd like, so you can poke about with what you like, add new hooks on game events in IL, or adding 'listeners' in your code that are informed of Sims 3 events and respond to them.

For figuring out all these things, the best way is to nose about at existing mods and see how they did something.

Good hunting!
Advertisement
Lab Assistant
#2 Old 13th Jul 2009 at 9:15 PM
Awesome, this is really helpful info.

Side question -- when poking around with Reflector i sometimes get pop ups saying it can't resolve 'SimsIFace'. I saw that mentioned in the code here and wonder if I missed a step? I extracted out the Sims3GameplaySystems/Objects.dll and UI.dll, but is there another DLL I should have too for SimsIFace?

edit: also, a future tutorial request or just basic tip request, is for advice on where to poke around as far as adding pie menu options to things.
Field Researcher
Original Poster
#3 Old 13th Jul 2009 at 9:19 PM
Yup, SimsIFace. It's in the scripts package I think.. one of the packages in Bin at least.
Warrior Gryphon
site owner
#4 Old 13th Jul 2009 at 10:24 PM
Great work lemmy!

Story books are full of fairy tales, of Kings and Queens, and the bluest skies.
Lab Assistant
#5 Old 13th Jul 2009 at 11:03 PM
Code:
  .method public hidebysig specialname static 
          int32  get_KleptomaniacTraitObjectsStealPerDay() cil managed
  {
    // Code size       6 (0x6)
    .maxstack  8
    IL_0000:  ldsfld     int32 Sims3.Gameplay.TuningValues.TraitTuning::kKleptomaniacTraitObjectsStealPerDay
    IL_0005:  ret
  } // end of method TraitTuning::get_KleptomaniacTraitObjectsStealPerDay


I see this. What I am trying to do is change the amount of objects a Klepto can steal a day(from 3 to 1, it's a little un-fair). I found the file in reflector and then managed to locate the method in Notepad++. I'm wondering how values are stored in IL, and how to decipher them, and change them. Do I need a hex editor?
Lab Assistant
#6 Old 13th Jul 2009 at 11:15 PM
Quote:
Originally Posted by jadaytime
Code:
  .method public hidebysig specialname static 
          int32  get_KleptomaniacTraitObjectsStealPerDay() cil managed
  {
    // Code size       6 (0x6)
    .maxstack  8
    IL_0000:  ldsfld     int32 Sims3.Gameplay.TuningValues.TraitTuning::kKleptomaniacTraitObjectsStealPerDay
    IL_0005:  ret
  } // end of method TraitTuning::get_KleptomaniacTraitObjectsStealPerDay


I see this. What I am trying to do is change the amount of objects a Klepto can steal a day(from 3 to 1, it's a little un-fair). I found the file in reflector and then managed to locate the method in Notepad++. I'm wondering how values are stored in IL, and how to decipher them, and change them. Do I need a hex editor?


When something refers to a 'TuningValue' or Tuning-anything, that's almost always something stored in an XML file. You should read the tutorial on XML Tuning Mods and then look for the Traits file in the GameplayData.package (if you read the XML tutorial that last bit will make more sense).
Field Researcher
Original Poster
#7 Old 13th Jul 2009 at 11:17 PM Last edited by lemmy101 : 13th Jul 2009 at 11:55 PM.
If you see something like: kKleptomaniacTraitObjectsStealPerDay, i.e. beginning with k, they are static constants. They may be tunable and therefore loaded from XML, so it would be best to change it there. But in general if you search for the variable you will find it being assigned in the .cctor method of the class the static constant is in:

Code:
IL_04c7:  ldc.i4.3
IL_04c8:  stsfld     int32 Sims3.Gameplay.TuningValues.TraitTuning::kKleptomaniacTraitObjectsStealPerDay


This is loading '3' onto the top of the evaluation stack and then stsfld (opposite of ldsfld that stores a value in a field) is storing the value on top of the stack (3) into the static field kKleptomaniacTraitObjectsStealPerDay. Since it's static you don't need a TraitTuning object on the top of the stack to call this store instruction as you would with the non-static 'stfld' version.

So simply reduce the 3 to a 1 in the first instruction and that should do it:

Code:
IL_04c7:  ldc.i4.1
IL_04c8:  stsfld     int32 Sims3.Gameplay.TuningValues.TraitTuning::kKleptomaniacTraitObjectsStealPerDay


Then in the ldsfld it will be grabbing the 1 you stored there instead of a 3. But if it's marked as tunable, the best option would be to find it in the XML files. This is for those cases when a variable is not tuned.
Lab Assistant
#8 Old 14th Jul 2009 at 12:12 AM
Quote:
Originally Posted by quetzilla
When something refers to a 'TuningValue' or Tuning-anything, that's almost always something stored in an XML file. You should read the tutorial on XML Tuning Mods and then look for the Traits file in the GameplayData.package (if you read the XML tutorial that last bit will make more sense).


I didn't know it was XML tunable. I looked for it, guess I was wrong. Thanks. And thanks lemmy, for this tutorial!
Lab Assistant
#9 Old 14th Jul 2009 at 1:55 AM
Quote:
Originally Posted by jadaytime
I didn't know it was XML tunable. I looked for it, guess I was wrong. Thanks. And thanks lemmy, for this tutorial!


Not sure if it came across this way, but I didn't mean it to sound like a reprimand or anything, not a 'YOU SHOULD DO X' but more of a 'this is how you do X' kind of thing.
Test Subject
#10 Old 19th Jul 2009 at 9:46 AM
Has anyone had the following error and know what it means?

"Failed to open managed resource file 'Sims3.Gameplay.GpeDebug.DreamsAndPromisesControl.resources'

I have got it after adding a few function calls to a method.
Test Subject
#11 Old 8th Aug 2009 at 7:18 PM Last edited by eccles1975 : 8th Aug 2009 at 7:31 PM.
Quote:
Originally Posted by Thistleryver
Has anyone had the following error and know what it means?

"Failed to open managed resource file 'Sims3.Gameplay.GpeDebug.DreamsAndPromisesControl.resources'

I have got it after adding a few function calls to a method.


'Sims3.Gameplay.GpeDebug.DreamsAndPromisesControl.resources' is a special resource file that is extracted from 'Sims3GameplaySystems.dll' (otherwise known as resource ID 0x03D6C8D903CE868C) when you convert it to .IL format with ildasm. Do not delete this file, because ilasm will need it to recompile your .IL back into a usable .DLL.

Assuming that you have the original .DLLs, ilasm and ildasm in the same folder as recommended (and that you have deleted this file before compiling your new .DLL, if you are getting this error), simply convert 'Sims3GameplaySystems.dll' into a .IL again with ildasm (with an '/output=' name different than the one you're editing, so it doesn't get overwritten by accident); the resource file will be extracted again, and you can then use ilasm to compile your .DLL and go on with your day.

Also, a note I'd like to make about the 'IL_xxxx' tags at the beginning of each line: they are in fact (as suggested) offsets in bytes from the stack initialisation block of the current function. Most of these are simply 1-byte offsets (as the majority of instructions are only 1 byte, such as assignments, bitwise operators, etc), but numeric tests (including boolean) appear to take 2 bytes, and function calls take 5 bytes. At this point, it does appear that ilasm ignores the actual values of these, and so far my use of a standard but distinctive label system (to help keep track of my own changes) causes no issues. (At one point I thought they were referenced, because I was getting a 'bad offset' error with a block of code, but it turned out I'd tried to reference a long address with a byte pointer. )
Lab Assistant
#12 Old 27th Aug 2009 at 6:41 AM
Oh wow. I'm uh... slightly confuzzled (complete modding idiot over here), but I will bookmark this page and try my darnedest to make sense of it when I am not so tired.

Thanks for the tutorial, Lemmy.

I write Sims stories and do YouTube videos, too!

Check out my YouTube channel here.

You can read my stories here.

Oh and I have this nifty thing called a Simblr, too. Find it here.
Test Subject
#13 Old 16th Jan 2010 at 1:32 PM Last edited by soulfulwriter : 16th Jan 2010 at 1:58 PM. Reason: Just realized there is a button to include attachment!
Default As Awesome As Awesome!
Thank you so much!!! It would be great if there was an option/button for a printable version. I was able to create a PDF file of the tutorial only without comments and other site page features. I would like to share it right here for anyone that would like to have a printed version. If anyone wants it, send me a message because for some reason I don't have the option to attach anything right now. Thank you so much for such a great tutorial. You are awesome as well (Not just Pescado. LOL)

Time for me to go and surrender myself to the embrace of Morpheus. I just realized that there is a button for attachment! Duh! I am attaching the file for anyone who might want to have it.
Download - please read all instructions before downloading any files!
File Type: rar IL and DLL overview _ tutorial.rar (80.5 KB, 47 downloads) - View custom content
Test Subject
#14 Old 11th Feb 2010 at 6:48 PM Last edited by fglrxandyou : 11th Feb 2010 at 6:48 PM. Reason: typo
Default Transitivity of Ilasm/Ildasm
Greetings,
I've been attempting to modify existing DLLs by altering an existing mod in a trivial way. My attempts have thus far failed (leading to exceptions being thrown on application load). I've identified the problem as being related to the disassembly or assembly operations, as follows:

I attempted to do a null case to figure out where my problem was beginning.

Targeting the primary dll of the package, I extracted it from the package and then returned that same dll to the package. The result was that there were no errors. Null case 1 worked fine. So I iterated.

I extracted the DLL from the package, ran Ildasm to produce an il file. Then I ran Ilasm to return it to the DLL _without_ making changes. I noticed at this point that the size of the DLL went from 667 to 668 KB. This seems unusual to me, as one would think that the process is transitive.

I then placed the dll back into the package and ran the application-- this time it threw an exception.

I can conclude that there is something wrong with something that occurs from when I disassemble to reassemble.

Am I missing something in this process?

Could it perhaps be related to my OS, versus the OS of the mod's creator? I use Windows7x64. I specifically chose the x86 (and also tested with the x64) versions of the Ildasm/Ilasm apps, as well as the different versions of the fusion.dll. Perhaps more arguments need to be provided to the reassembler?

Currently the only arguments I provide are

ilasm 1.il /dll

I think most baffling of all of this is the manner in which the file size changes. This indicates to me that there must be a difference in the manner in which my ilasm versus the mod creator's ilasm is running.

Are there any thoughts or guidance on this?
Back to top