Tutorial:Sims 3 Core Modding Basics

From SimsWiki
Jump to: navigation, search

Introduction

Contents

Core mods, just like tuning mods, are a type of resource override. Pretty much every content of the game, no matter if it's CC or vanilla content, is stored in a DBPF file, usually and later on called a package file. Inside of these files are resources that are recognized by their instance value. You can override a vanilla resource by adding a package to the game with a resource that has this exact instance value.


The game comes with eight core script libraries; three of these belong to the .NET/MONO framework, and the other five form the script engine of The Sims 3. A mod that overrides one of these resources is a core mod.


This tutorial is a basic guide on how to get started on core modding. To learn by a concrete example, we'll make a so-called slider hack that allows you to add more CAS sliders to the game and extends the slider range.


This tutorial will NOT cover how to install any of the used tools, nor how to use them beyond what is specifically needed for this tutorial.


What You Need


  • A basic understanding of the C# syntax or at least any C-like language.
  • A game that is properly set up to support scripting mods.


Getting Started

First of all, create a folder for the core libraries and a folder for your project.


Extracting And Examining The Core Libraries

Extract the core libraries with S3PE and save them to the libraries folder you just created. Here’s how to do that:


  1. Open S3PE and click on File -> Open…
  2. Navigate to the installation folder of The Sims 3 and from there to the sub-folder where the executable is located. In this folder are three packages: gameplay.package, scripts.package, and simcore.package
  3. Open one of these packages.
  4. Click on an S3SA resource. Note that S3PE shows some information about that resource in the preview area. Locate where it says ManifestModule. Remember what comes after the colon, e.g. Sims3GameplaySystems.dll.
  5. Click on Grid at the bottom of the S3PE window, and then click on the Assembly row and the little drop-down arrow on the right. Click on Export…
  6. Navigate to the libraries folder and save it under the name you remembered from the ManifestModule entry.
  7. Repeat steps 4 to 6 for every S3SA resource in the package.
  8. Repeat steps 3 to 7 for every package listed under step 2.
  9. Close S3PE.


Now open Reflector and load the core libraries in it. Double-click on anything in Reflector's browser window to open the Disassembler window in which you can see the actual code.


Finding The Means To Disassemble The Core

To disassemble the core, you'll need five files that, after installing the .NET SDK and Windows SDK, should be somewhere on your harddrive. These files are


  • ildasm.exe
  • ildasm.exe.config
  • ilasm.exe
  • ilasm.exe.config
  • fusion.dll


Just do a search on your harddrive for them and then COPY them to your project folder.


Doing Some Core Modding

A word ahead: I like to leave all applications running, so I won't mention when you should close a tool or app or when you should leave it open for later use. Just leave them all open and then close them all when you're definitely done.


Making A Plan

As mentioned in the introduction, there's a plan. We like plans. We're going to make a slider hack. Right now we have no idea how to do that and what to look for. How about doing a search in Reflector and see if we can find the part where the slider stuff is happening? Maybe we could use Reflector's Analyze feature to trace what is calling stuff of interest?


Now if this is your first contact with the TS3 script engine, it might take you a while to find it. If you're a lame old cheater, read on. Otherwise you will now try to find it yourself and interrupt your reading of this tutorial RIGHT NOW.


The point of interest for a slider hack is in Sims3.UI.CAS.CASFacialBlendPanel located in UI.dll. Have a look at the AddSliderGridItem() method.


private void AddSliderGridItem(ItemGrid grid, ResourceKey itemKey, FacialBlendData sliderData)
{
    WindowBase windowByExportID = UIManager.LoadLayout(itemKey).GetWindowByExportID(0x1);
    if (windowByExportID != null)
    {
        Text childByID = windowByExportID.GetChildByID(0x2, true) as Text;
        if (childByID != null)
        {
            childByID.Caption = StringTable.GetLocalizedString(sliderData.mLocalizationKey);
        }
        Slider slider = windowByExportID.GetChildByID(0x4, true) as Slider;
        if (slider != null)
        {
            slider.SliderValueChange += new UIEventHandler<UIValueChangedEventArgs>(this.OnSliderChanged);
            slider.MouseUp += new UIEventHandler<UIMouseEventArgs>(this.OnSliderMouseUp);
            if (sliderData.mBidirectional)
            {
                slider.MinValue = 0xffffff00;
            }
            else
            {
                slider.MinValue = 0x0;
            }
            slider.MaxValue = 0x100;
            slider.Value = (int) Math.Round((double) (sliderData.Value * 256f));
            if (CASController.Singleton.DebugTooltips)
            {
                windowByExportID.TooltipText = StringTable.GetLocalizedString(sliderData.mLocalizationKey);
            }
            grid.AddItem(new ItemGridCellItem(windowByExportID, sliderData));
            if (this.mNumSliders < 0x14)
            {
                this.mSliderData[this.mNumSliders].mSlider = slider;
                this.mSliderData[this.mNumSliders].mData = sliderData;
                this.mNumSliders++;
            }
        }
    }
}


The points of interest are separated. The shown number might differ for you if you didn't set Reflector to show numbers in hexadecimal format.


0x14 or 20 is the original maximum of sliders the game can handle. We're going to change that number. The minimum value for sliders is either Zero or 0xFFFFFF00, i.e. -256. The maximum value is 0x100, i.e. 256. If hexadecimal numbers don't make sense to you, now is the time to look that up. We're going to alter these values.


Of course your razor-sharp mind didn't miss the fact that the sliders are stored in mSliderData which is an array. Arrays are fixed-size, so unless you like IndexOutOfRange exceptions, we'll need to alter the size of that array, too. It gets assigned in CASFacialBlendPanel's constructor.

public CASFacialBlendPanel(uint winHandle) : base(winHandle)
{
    this.mSliderData = new SliderData[0x14];
}


Disassembling The Core

We now know that we need to make the changes in UI.dll. So copy that library to your project folder. Now open a command console. On Windows Vista or 7, just open the Start menu and type cmd.exe and press Enter. On Windows XP, click on Start and select "Run As", then type in cmd.exe and press Enter.

To change to you project path, just enter "CD {YourProjectPath}" and press Enter. In my case that's "CD C:\Users\Buzz\Documents\Sims3_Stuff\Sliderhack". Type "DIR" and press Enter to make sure it's the right one. Or do it how you like if you're familiar with the command console or even good old DOS.

Now to disassembe UI.dll, enter

ildasm ui.dll /out=ui.il

(The .il extension stands for Intermediate Language.)

Depending on your computer, it might take a bit to finish.


Making The Changes

After the library has been decompiled, open the .il file with Notepad++. Being the smarty-pants we are, we don't go through thousends of lines of CIL manually, but just do a search for AddSliderGridItem. You will find a few calls of AddSliderGridItem, but you know you found the right part when you find something that begins with ".method private hidebysig instance void". That is the actual method.

.method private hidebysig instance void 
          AddSliderGridItem(class Sims3.UI.ItemGrid grid,
                            valuetype [SimIFace]Sims3.SimIFace.ResourceKey itemKey,
                            class Sims3.UI.CAS.FacialBlendData sliderData) cil managed
  {
    // Code size       304 (0x130)
    .maxstack  4
    .locals init (class Sims3.UI.WindowBase V_0,
             class Sims3.UI.Text V_1,
             class Sims3.UI.Slider V_2)
    IL_0000:  ldarg.2
    IL_0001:  call       class Sims3.UI.Layout Sims3.UI.UIManager::LoadLayout(valuetype [SimIFace]Sims3.SimIFace.ResourceKey)
    IL_0006:  ldc.i4.1
    IL_0007:  callvirt   instance class Sims3.UI.WindowBase Sims3.UI.Layout::GetWindowByExportID(int32)
    IL_000c:  stloc.0
    IL_000d:  ldloc.0
    IL_000e:  ldnull
    IL_000f:  call       bool Sims3.UI.WindowBase::op_Inequality(class Sims3.UI.WindowBase,
                                                                 class Sims3.UI.WindowBase)
    IL_0014:  brfalse    IL_012f
    IL_0019:  ldloc.0
    IL_001a:  ldc.i4.2
    IL_001b:  ldc.i4.1
    IL_001c:  callvirt   instance class Sims3.UI.WindowBase Sims3.UI.WindowBase::GetChildByID(uint32,
                                                                                              bool)
    IL_0021:  isinst     Sims3.UI.Text
    IL_0026:  stloc.1
    IL_0027:  ldloc.1
    IL_0028:  ldnull
    IL_0029:  call       bool Sims3.UI.WindowBase::op_Inequality(class Sims3.UI.WindowBase,
                                                                 class Sims3.UI.WindowBase)
    IL_002e:  brfalse.s  IL_0041
    IL_0030:  ldloc.1
    IL_0031:  ldarg.3
    IL_0032:  ldfld      uint64 Sims3.UI.CAS.FacialBlendData::mLocalizationKey
    IL_0037:  call       string [SimIFace]Sims3.SimIFace.StringTable::GetLocalizedString(uint64)
    IL_003c:  callvirt   instance void Sims3.UI.WindowBase::set_Caption(string)
    IL_0041:  ldloc.0
    IL_0042:  ldc.i4.4
    IL_0043:  ldc.i4.1
    IL_0044:  callvirt   instance class Sims3.UI.WindowBase Sims3.UI.WindowBase::GetChildByID(uint32,
                                                                                              bool)
    IL_0049:  isinst     Sims3.UI.Slider
    IL_004e:  stloc.2
    IL_004f:  ldloc.2
    IL_0050:  ldnull
    IL_0051:  call       bool Sims3.UI.WindowBase::op_Inequality(class Sims3.UI.WindowBase,
                                                                 class Sims3.UI.WindowBase)
    IL_0056:  brfalse    IL_012f
    IL_005b:  ldloc.2
    IL_005c:  ldarg.0
    IL_005d:  ldftn      instance void Sims3.UI.CAS.CASFacialBlendPanel::OnSliderChanged(class Sims3.UI.WindowBase,
                                                                                         class Sims3.UI.UIValueChangedEventArgs)
    IL_0063:  newobj     instance void class Sims3.UI.UIEventHandler`1<class Sims3.UI.UIValueChangedEventArgs>::.ctor(object,
                                                                                                                      native int)
    IL_0068:  callvirt   instance void Sims3.UI.Slider::add_SliderValueChange(class Sims3.UI.UIEventHandler`1<class Sims3.UI.UIValueChangedEventArgs>)
    IL_006d:  ldloc.2
    IL_006e:  ldarg.0
    IL_006f:  ldftn      instance void Sims3.UI.CAS.CASFacialBlendPanel::OnSliderMouseUp(class Sims3.UI.WindowBase,
                                                                                         class Sims3.UI.UIMouseEventArgs)
    IL_0075:  newobj     instance void class Sims3.UI.UIEventHandler`1<class Sims3.UI.UIMouseEventArgs>::.ctor(object,
                                                                                                               native int)
    IL_007a:  callvirt   instance void Sims3.UI.WindowBase::add_MouseUp(class Sims3.UI.UIEventHandler`1<class Sims3.UI.UIMouseEventArgs>)
    IL_007f:  ldarg.3
    IL_0080:  ldfld      bool Sims3.UI.CAS.FacialBlendData::mBidirectional
    IL_0085:  brfalse.s  IL_0094
    IL_0087:  ldloc.2
    IL_0088:  ldc.i4     0xffffff00
    IL_008d:  callvirt   instance void Sims3.UI.Slider::set_MinValue(int32)
    IL_0092:  br.s       IL_009b
    IL_0094:  ldloc.2
    IL_0095:  ldc.i4.0
    IL_0096:  callvirt   instance void Sims3.UI.Slider::set_MinValue(int32)
    IL_009b:  ldloc.2
    IL_009c:  ldc.i4     0x100
    IL_00a1:  callvirt   instance void Sims3.UI.Slider::set_MaxValue(int32)
    IL_00a6:  ldloc.2
    IL_00a7:  ldarg.3
    IL_00a8:  callvirt   instance float32 Sims3.UI.CAS.FacialBlendData::get_Value()
    IL_00ad:  ldc.r4     256.
    IL_00b2:  mul
    IL_00b3:  conv.r8
    IL_00b4:  call       float64 [mscorlib]System.Math::Round(float64)
    IL_00b9:  conv.i4
    IL_00ba:  callvirt   instance void Sims3.UI.Slider::set_Value(int32)
    IL_00bf:  call       class Sims3.UI.CAS.CASController Sims3.UI.CAS.CASController::get_Singleton()
    IL_00c4:  callvirt   instance bool Sims3.UI.CAS.CASController::get_DebugTooltips()
    IL_00c9:  brfalse.s  IL_00dc
    IL_00cb:  ldloc.0
    IL_00cc:  ldarg.3
    IL_00cd:  ldfld      uint64 Sims3.UI.CAS.FacialBlendData::mLocalizationKey
    IL_00d2:  call       string [SimIFace]Sims3.SimIFace.StringTable::GetLocalizedString(uint64)
    IL_00d7:  callvirt   instance void Sims3.UI.WindowBase::set_TooltipText(string)
    IL_00dc:  ldarg.1
    IL_00dd:  ldloc.0
    IL_00de:  ldarg.3
    IL_00df:  newobj     instance void Sims3.UI.ItemGridCellItem::.ctor(class Sims3.UI.WindowBase,
                                                                        object)
    IL_00e4:  callvirt   instance void Sims3.UI.ItemGrid::AddItem(valuetype Sims3.UI.ItemGridCellItem)
    IL_00e9:  ldarg.0
    IL_00ea:  ldfld      int32 Sims3.UI.CAS.CASFacialBlendPanel::mNumSliders
    IL_00ef:  ldc.i4.s   20
    IL_00f1:  bge.s      IL_012f
    IL_00f3:  ldarg.0
    IL_00f4:  ldfld      valuetype Sims3.UI.CAS.CASFacialBlendPanel/SliderData[] Sims3.UI.CAS.CASFacialBlendPanel::mSliderData
    IL_00f9:  ldarg.0
    IL_00fa:  ldfld      int32 Sims3.UI.CAS.CASFacialBlendPanel::mNumSliders
    IL_00ff:  ldelema    Sims3.UI.CAS.CASFacialBlendPanel/SliderData
    IL_0104:  ldloc.2
    IL_0105:  stfld      class Sims3.UI.Slider Sims3.UI.CAS.CASFacialBlendPanel/SliderData::mSlider
    IL_010a:  ldarg.0
    IL_010b:  ldfld      valuetype Sims3.UI.CAS.CASFacialBlendPanel/SliderData[] Sims3.UI.CAS.CASFacialBlendPanel::mSliderData
    IL_0110:  ldarg.0
    IL_0111:  ldfld      int32 Sims3.UI.CAS.CASFacialBlendPanel::mNumSliders
    IL_0116:  ldelema    Sims3.UI.CAS.CASFacialBlendPanel/SliderData
    IL_011b:  ldarg.3
    IL_011c:  stfld      class Sims3.UI.CAS.FacialBlendData Sims3.UI.CAS.CASFacialBlendPanel/SliderData::mData
    IL_0121:  ldarg.0
    IL_0122:  dup
    IL_0123:  ldfld      int32 Sims3.UI.CAS.CASFacialBlendPanel::mNumSliders
    IL_0128:  ldc.i4.1
    IL_0129:  add
    IL_012a:  stfld      int32 Sims3.UI.CAS.CASFacialBlendPanel::mNumSliders
    IL_012f:  ret
  } // end of method CASFacialBlendPanel::AddSliderGridItem

If that is your first contact with CIL or anything like it, give yourself a moment to let the shock wear off. If you're lucky, you have two screens and can look at Reflector on one screen and Notepad++ on the other. If not, you'll need to switch back and forth. Now compare the C# code from Reflector to the CIL code and you should at least get a basic idea of what's happening where. You should also find where the numbers we want to change are pushed to the stack, especially since they are separated.


  • Change 0xFFFFFF00 in IL_0088 to 0xFFFFFE00, i.e. -512.
  • Change 0x100 in IL_009c to 0x200, i.e. 512.
  • Change 20 in IL_00ef to 100.


Finished? Not yet. Scroll up to find the constructor.

.method public hidebysig specialname rtspecialname 
          instance void  .ctor(uint32 winHandle) cil managed
  {
    // Code size       21 (0x15)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.s   20
    IL_0003:  newarr     Sims3.UI.CAS.CASFacialBlendPanel/SliderData
    IL_0008:  stfld      valuetype Sims3.UI.CAS.CASFacialBlendPanel/SliderData[] Sims3.UI.CAS.CASFacialBlendPanel::mSliderData
    IL_000d:  ldarg.0
    IL_000e:  ldarg.1
    IL_000f:  call       instance void Sims3.UI.Window::.ctor(uint32)
    IL_0014:  ret
  } // end of method CASFacialBlendPanel::.ctor

This method is pretty small, so you should have no problem to find where the 20 gets pushed to the stack.


  • Change 20 in IL_0001 to 100.


Save the file.


A Word About Constants

While looking at CASFacialBlendPanel, you might have stumbled over the constants kMaxSliders, kSliderMaxFloat and kSliderMaxValue. You even may have concluded that these were the points to make your changes. Well, just analyze these constants in Reflector to find what code uses these constants. You'll find ... nothing. What's the point then? Well, always keep in mind that we don't see what the EAxian coders wrote and saw. We look at decompiled code. In the actual source code, these constants are being used of course. The compiler however will use their values instead of making the compiled code access the constants. That way the code will run a little bit faster. So don't bother if you find a constant that looks like its value is what you're looking for. Go on looking to find where that value turns up and change it there.


Recompiling

Switch back to your opened command console. To compile the changed .il file back into a fully-fledged library, type the following and press Enter.

ilasm ui.il /dll

It will take a bit to compile the library. The last prompt from ilasm should be "Operation completed successfully". If it's anything else, you made an error in your edit.


Building The Package

Open S3PE.


Before you start, open the package that contains the original UI.dll (gameplay.package). Double-click on the resource that contains UI.dll. Hint: You'll see it in S3PE's preview at "ManifestModule". Copy the hex value from the instance field or write it down if you are keen on giving yourself the chance to prove yourself the ability for slip ups. Look at the top of the preview. Write down or memorize the numbers after Version and GameVersion.


CMT NewResource.jpg


  • Now click on File->New to create a fresh package.
  • Click on Resource->Add...
  • As type choose S3SA.
  • Paste the instance value to the Instance field, and enter 0 for the Group.
  • For convenience tick "Use resource name" and enter UI.dll in the Name field.


Once you're done, click OK.



S3PE will now show the S3SA resource and a _KEY resource. You can just ignore the latter one. Select the S3SA resource and on the bottom of the S3PE window, click on Grid.


CMT ImportScript.jpg
  • Enter the Version and GameVersion numbers from the original resource. That is to keep the final package nag screen compliant.
  • Select Import/Export/Edit..., click on the drop-down button on the right and click on Import...
  • Navigate to your project folder. Select UI.dll and back in S3PE's Data Grid window, click on Commit.


Save your package. Usually, it’s a good idea to begin the filename with your username or something that will identify all your mods, especially if you have any plans of releasing the mod to the public.


The End

Assuming you didn't slip up on the way, you now have made a slider hack. Give it a spin in the game, and if it works well, eat a cookie. Alter the slider range some more if you want it to be smaller or bigger. You know how to do that now.


Always keep in mind that a core mod is only compatible with exactly one game version! Every patch will force you to rip out "fresh" versions of the libraries from the core packages, disassemble them and make your changes to build a new core mod.


Where Does The Newborn Go From Here?

You probably couldn't fail to notice that this tutorial doesn't cover what the heck all this CIL code means. There is an introduction to IL by lemmy (you know, the guy who wrote the Indie mod) on this topic. Apart from that, you'll have to figure it out yourself.


Just don't try to make major changes directly in the core. Write your own libraries and only alter the core to call your code instead.


Questions

Ask them here: Q&A thread Tutorial: Sims 3 Core Modding Basics

Personal tools
Namespaces

Variants
Actions
Navigation
game select
Google AdSense
Toolbox