Difference between revisions of "Tutorial:Sims 3 Core Modding Basics"
m |
|||
Line 393: | Line 393: | ||
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. | 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: [http://www.modthesims.info/showthread.php?t=434341 Q&A thread Tutorial: Sims 3 Core Modding Basics] |
Revision as of 18:59, 18 February 2011
Introduction
|
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
- Sims3 Package Editor - simply called S3PE later in this tutorial
- redgate .NET Reflector - simply called Reflector later in this tutorial
- Notepad++ - or any text editor that can handle very large files
- Windows SDK
- .NET Framework 2.0 Software Development Kit (SDK) - The link points to the x86 version of the framework! Scroll down to the end of the linked page to find a link to the x64 version if you use a 64 Bit Windows.
- 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 Examing 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:
- Open S3PE and click on File -> Open…
- 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
- Open one of these packages.
- 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.
- 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…
- Navigate to the libraries folder and save it under the name you remembered from the ManifestModule entry.
- Repeat steps 4 to 6 for every S3SA resource in the package.
- Repeat steps 3 to 7 for every package listed under step 2.
- 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.
- 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.
- 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