[Basic] Python Injecting into The Sims 4

TURBODRIVER
4 min readMay 3, 2021

This is a tutorial with a Basic Difficulty, meaning that you must already have basic knowledge on how Python code works, how to decompile game source code, and how to compile and run your own code.

Injecting/Wrapping is a method of taking the original code of the game and wrapping it with your custom code. This method allows you to:

  • Add more code to the game without removing the original game code.
  • Replace code of the game and prevent original game code from running.
  • Make your code trigger at specific circumstances.

A function that is injected will first run your code. It is your decision if you want to run the original function afterward.

This tutorial focuses on how to do it safely, avoid issues in the future, and respect the work of the game developers and other mod creators.

Below I’m sharing my own injector function that is used as a decorator to inject various types of functions found in the game code. A decorator is used to add extra functionality to existing functions or for improved comfortability and clarity of code, both apply in this case. This injector is capable of wrapping regular functions, methods, bound methods, classmethods, staticmethods, properties, classproperties, flexmethods, and generators.

The example below is an injection into the “start_pregnancy” method inside of the “PregnancyTracker” class that is used to start pregnancy for a Sim. This injection adds an additional test that requires involved Sims to not be aliens for the pregnancy to start.

The injection code below both adds code and replaces default functionality of the game. The added code checks if any of the involved Sims are aliens, introducing new code to the game. If any of the involved Sims are aliens, the original code is prevented from running, therefore replacing the default functionality of the game.

In addition, if you’re in a need for code that runs when a pregnancy starts, this could be used for that purpose. Every single time a pregnancy starts, this function will run and include information on who is involved in the pregnancy.

Why is there an “original”, “self”, “*args” and “**kwargs”?
After injecting, the injector returns the original function you’re injecting into as the first argument. The rest of the arguments are arguments that are passed into the original function so you can read them. Because this example injects into a method, the first argument is always “self”, so it can be explicitly visible.
When injecting into a function that does not have a “self” argument, you cannot include it as an explicitly visible argument.

Why use “*args” and “**kwargs”?
The original method “start_pregnancy” has two arguments and one keyword argument — “parent_a”, “parent_b”, and “pregnancy_origin”. The practice of using “*args” and “**kwargs” assures that any additions to the original method by the developers will not cause issues, as the game is still being actively developed. If a new argument or keyword argument is added, this injection will continue to work without any errors.
Additionally, this is why the variables are read as “parent_a = args[0]” and “parent_b = args[1]”, it’s very unlikely their order would be changed as the game continues development, and they can be captured inside of the try and except clause which assures catching errors from potential alterations.

Why is the code in a try and except clause?
It’s mostly courtesy. In a situation where your code is going to cause errors in the future, because it’s being caught by the try and except clause, it won’t halt the game (and other mods) from working. If the test for Sims being aliens were to fail due to code error reasons, the game will continue to work, allowing players to continue playing. Additionally, by using the “sims4.log.exception()” function, the error will be reported into a lastException.txt file which can help identify the issue.
Do not raise an exception inside of the except, as doing so will defeat the entire purpose of using a try and except clause. Instead, using a custom logger, if you have one, is advised.

Why is the original argument being called and returned?
The original function should always be called unless the injection is specifically made to prevent it. In this case, if a Sim involved in starting the pregnancy is an alien, this injection function stops and prevents reaching the original function. Because the original function is not called, pregnancy never starts, as the original “start_pregnancy” function must run for the pregnancy to start.

The example below is an injection into a classmethod to detect when a Sim spawns into the world zone. The difference with classmethods is that they don’t have a “self” argument, instead they have a “cls” (class) argument that needs to be explicitly ignored by replacing it with an underscore.

The injection first lets the game run the original function, allowing the Sim to spawn and store the result of that function into the variable “result”. If the spawning succeeded, the “result” variable will be positive. If the variable “result” is positive, the spawned Sim fat value is changed to 100%, making them very fat. This code will make every Sim that spawns very fat. And at the end, the “result” variable is returned by the injection back to the game, so the game too knows what was the outcome of spawning that Sim.

Why is the “cls” variable ignored and replaced with an underscore?
The original function doesn’t take the “cls” variable, so running the original function with it will simply cause an error. Anything can be ignored in Python by replacing its name with an underscore.

Why store the result of the original function and return it later?
Your injection wraps the original function, which means your function runs first and returns last. You need to returns the original function outcome so the game can receive it. Do this even if the function doesn’t return anything, as it might do it in future game updates. This is especially important when multiple mods inject into the same function. Additionally, knowing what was the outcome of the original function allows you to run your code only when necessary.

For additional help or explanation, visit the Creator Musings Discord or contact me TURBODRIVER#0001.

--

--