Replies: 8 (Who?), Viewed: 385 times.
Test Subject
Original Poster
#1 Old 9th Apr 2021 at 7:56 PM Last edited by Same4254 : 9th Apr 2021 at 9:33 PM. Reason: Previous edits were typos. For this one I forgot to change the namespace. Also included a note about Battery's script extender that I just learned about
Default A (maybe new?) class of modding that allows modders to use FIle I/O, Sockets, and printing to the screen: Bridge Modding
Over half a year ago I made a post titled "I need Sockets or Native Code...". There I outlined the many ways I tried to insert more of the native .NET framework into the game. That obviously did not work. However, today I bring you, to my knowledge, a novel approach to modding the Sims 3 that will allow modders to open Sockets, Files, and even print to the screen. What a concept.

Edit: Apparently something like this has been done. Battery's Script extender allows for File I/O. To be clear I did not know this existed until after I made this. I don't know if we are doing the same thing since they did not upload source code or explain how they did it...


Now, before I get into this I want to note that the way this works is incredibly toxic and cheeky but it does what I claim it can do. If you have never messed with game memory or don't know what the memory stack looks like or have never seen a pointer before, then this is going to be cryptic at best.

Now let's get into the modding.

So the problem we are having here is that we want access to standard library stuff, File I/O, Sockets, etc... As we all know the DLLs you get from the package files do not include any of these things. You cannot open a socket, read a file, or pipe into another process. Thus all standard means of communicating to the outside world of the Sims process will not work here.

To get around this we are going to take a more gritty approach.

Consider the run of the mill pausinator script. That is where we will be starting. My thought process was that the script is loaded only once, at the start of the game. So what I did was inside the pausinator class I put a small byte array of length 100, which I will refer to as the buffer for the rest of the post. Inside of this buffer I put a few... special numbers. The importance of these numbers is to be random such that they can be found in the program later. So with that array in the pausinator I then loaded the game up. At the main menu, I then opened Cheat Engine (a program that lets you investigate the memory of a process) and did a memory scan for these special numbers (whose role is only to be unique such that scanning for those numbers in memory will only return one address: the address to the buffer). With that, it is shown that you can scan memory to get the address of that buffer in memory after the mod has been loaded.

Okay, so what do we need that for? Well, the idea from here is that we are going to have an external program that is going to read and write into this buffer (this is something you can do with relative ease in C#). Note that the external (outside of the sims) does not run with the MONO library. It's sole purpose is to open the sims process, find the buffer, and then read and write to that buffer.

From here you can probably see where this is going.

The idea is that the script inside the game will create the buffer and every x amount of seconds it will read that buffer in order to check if the external program is telling it to do something. In addition, the internal program will be able to write to the buffer to tell the external program to do something as well. How? Well, that's when we get into building up some kind of communication protocol for this to be all neat and tidy. However for now I have rigged up a proof of concept that has the external program send a String (read from the command line) and puts it into the buffer. Then the internal program checks the buffer every once and a while. When it sees that there is a new string in the buffer it will send out a styled notification of that String.

What this proof of concept shows is that communication in and out of the game is possible with some work. In addition, this external program can run any variety of libraries. All it needs to do is deal with that buffer to get information to and from the internal script.

Here is the code I used to do this.

The External Program

Code:
using System;
using System.Text;
using Jupiter;

namespace TheBridge {
	class Class1 {
		static void Main(string[] args) {
			//****** Scan The Sims process for our buffer. These are the bytes of the numbers at the start of the buffer
			byte[] scanFor = { 0x45, 0x00, 0x00, 0x00, 0xA4, 0x01, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0xC1, 0x06, 0x00, 0x00, 0x52, 0x0E, 0x00, 0x00 };

			//Let Jupiter handle the scanning, reading, and writing
			MemoryModule m = new MemoryModule("TS3");
			var addresses = m.PatternScan(scanFor);

			IntPtr bufferPointer = new IntPtr(0);
			foreach (IntPtr p in addresses) {
				bufferPointer = p;
			}

			if (bufferPointer.ToInt32() == 0) {
				Console.WriteLine("Did not find any addresses for the buffer ");
				System.Environment.Exit(-1);
			}

			Console.WriteLine("Buffer Address: {0:x}", bufferPointer.ToInt32());

			//Now that we have an address to the buffer (bufferPointer) let's do something with this to show it works
			unsafe {
				String s = "";
				while (s != "exit") {
					//Read a string from the console
					s = Console.ReadLine();
					if (s == "exit")
						continue;

					//Setup an array to write to the remote process
					//We need to tell the internal script how long the string is (4 byte integer) along with the bytes of the string
					byte[] bufferToSend = new byte[s.Length + 4];
					fixed(byte *p = bufferToSend) {
						((int*)p)[0] = s.Length;// Cheeky pointer cast to set the first 4 bytes to the length of the string

						//set the rest of the bytes to be the bytes of the string when converted into ascii
						Encoding.ASCII.GetBytes(s, 0, s.Length, bufferToSend, 4);
					}

					//Write the bytes that represent the length and the content of the string into the buffer at an offset of 20 bytes
					//These first 20 bytes are from the unique numbers that identify the location of the buffer
					//We want to NEVER change those that way if this external program crashes we can restart it and still find that buffer again
					//This will need to be replaced with proper communication at some point (think of something akin to a three-way TCP handshake)
					m.WriteVirtualMemory(bufferPointer + 20, bufferToSend);
				}
			}
		}
	}
}


A few notes:
  • This needs to be compiled with the "unsafe" checkbox checked in the build tab in the project properties
  • This needs to be compiled for an x64 CPU.
  • This uses the Jupiter library. You can find this in NuGet in Visual Studio. It is very easy to download and use.
  • You need to run this in the main menu or in the game. That's when the buffer is loaded in memory. Don't start it in the loading screen. I'm not sure why but scanning the memory in that time window causes Jupiter to crash. I'm not very concerned as to why, just don't do it

The Internal program (based on the standard Pausinator script setup with the XML file and stuff)


Code:
using System;
using System.Text;
using Sims3.SimIFace;
using Sims3.Gameplay.Utilities;
using Sims3.UI;

namespace BridgeInator {
    public class Class1 {
        // Entry point for class
        [Tunable]
        public static bool kInstantiator = false;

        // DO NOT MOVE
        public static byte[] buffer = new byte[100];

        static Class1() {
            World.OnWorldLoadFinishedEventHandler += new EventHandler(OnWorldLoadFinished);

            //Plug this into cheat engine to confirm the address: 45 00 00 00 A4 01 00 00 15 00 00 00 C1 06 00 00 52 0E 00 00

            //Toxic unique numbers for toxic code 
            //69: 45 00 00 00
            //420: A4 01 00 00
            //21: 15 00 00 00
            //1729: C1 06 00 00
            //3666: 52 0E 00 00
            unsafe {
                //Cheeky pointer cast to make writing the bytes ez
                fixed (byte* p = buffer) {
                    int* p2 = (int*)p;
                    p2[0] = 69;
                    p2[1] = 420;
                    p2[2] = 21;
                    p2[3] = 1729;
                    p2[4] = 3666;
                }
            }
        }

        private static void OnWorldLoadFinished(object sender, EventArgs e) {
            //Start up the alarm system to call the onAlarm function
            AlarmManager.Global.AddAlarm(2f, TimeUnit.Seconds, new AlarmTimerCallback(onAlarm), "Hello World Alarm", AlarmType.NeverPersisted, null);
        }

        //Dummy variable to store last result to see if the string has changed
        //This check needs to be replaced with some header bits in the buffer to express whether the "packet" has been read before
        public static String previous = "";

        private static void onAlarm() {
            unsafe {
                fixed (byte* p = buffer) {
                    //Cheeky pointer cast to read off the integer length of the string
                    //This is the 6th integer because the first 5 integers are our *special* and unique number to find the location of the buffer
                    int length = ((int*)p)[5];

                    //Convert the bytes after the length to ascii and store it as a string
                    //This is why we needed the length of the string
                    String temp = Encoding.ASCII.GetString(buffer, 24, length);

                    if(temp != previous) {
                        previous = temp;
                        StyledNotification.Show(new StyledNotification.Format("Text (" + length + ") " + temp, StyledNotification.NotificationStyle.kSystemMessage));
                    }
                }
			}
            
            //Set up a new alarm to run this function again
            //Should find a better way to get this code to run every duration of time, but this works
            AlarmManager.Global.AddAlarm(2f, TimeUnit.Seconds, new AlarmTimerCallback(onAlarm), "Hello World Alarm", AlarmType.NeverPersisted, null);
        }
	}
}


A few notes:
  • This must also be compiled with the unsafe check box. It's not strictly required at the moment, but I expect to be using more pointer casts in the future. You need to enable this so C# will let the raw/unmanaged pointers to muck about.
  • I'm not too happy with that alarm system trick. Is there a better way to get the code to run every duration of time?

How To Use

  • [1] So to run this you are going to compile and insert the internal program into the game like you would the pausinator. The only difference is that this needs the unsafe flag enabled in Project > Properties > Build > Allow unsafe code.

    [2] Launch the game

    [3] In the main menu launch the external program from Visual studio. This needs Jupiter installed (you can get it through NuGet), compiled for x64, and allow unsafe code.

    [4] Wait for it to print out the buffer address to the console (it will use up CPU to search for this address, don't be alarmed)

    [5] Go into the world

    [6] Once your sims are out and about, you should be able to type some words into the console (keep the message short. If you make it longer you will overflow the buffer and crash the script runtime environment), and it will appear on the screen as a Styled notification.

The Result

Well, now we have communication to and from the Sims process. From here, the external program can do whatever, send it to the internal program, or receive from the internal program. On the other side, the internal program can send and receive and act according to what is sent.

However, what I have shown you here is not a robust and tested system. This is just the initial and raw setup of a means of communication. I have only tested this on a 64 bit AMD processor and the code is not set up to check the system and make sure to have the correct endian-ness and such. In the future, I plan to make this communication more reliable and abstract it away into some easy-to-use classes so it is easy to add new functions and messages.
Advertisement
Field Researcher
#2 Old 9th Apr 2021 at 9:02 PM Last edited by gamefreak130 : 9th Apr 2021 at 9:12 PM.
This is awesome! It looks very similar to what @Battery has been experimenting with in his script extender, but this looks like it could be a bit more stable.

Quote:
Originally Posted by Same4254
I'm not too happy with that alarm system trick. Is there a better way to get the code to run every duration of time?


I don't know if this is what you're looking for, but I've used a repeating alarm task that runs a callback after a given real-time interval in milliseconds:


"The Internet is the first thing that humanity has built that humanity doesn't understand, the largest experiment in anarchy that we have ever had." - Eric Schmidt

If you enjoy the mods I put out, consider supporting me on patreon: www.patreon.com/Gamefreak130
Test Subject
Original Poster
#3 Old 9th Apr 2021 at 9:42 PM
Quote:
Originally Posted by gamefreak130
This is awesome! It looks very similar to what @Battery has been experimenting with in his script extender, but this looks like it could be a bit more stable.


Thanks for telling me about this. I didn't know it existed... Do you know how it works? I couldn't find the source code for it, which seems silly to me that they didn't include it... I suppose it is possible we are doing something similar, but the fact that you have to start their exe as the sims loads makes me think they are doing something different since my method relies on a normal script injection. However, by requiring a second program like it does, I think we are doing something of the same flavor....

Also, thanks for the simulator suggestion! I didn't know about that class. That looks nice!
Field Researcher
#4 Old 10th Apr 2021 at 12:48 PM
what a crazy coincidence indeed, and just one month after S3Se released dot dot dot

@Gamefreak130 did you do extensive tests to check the stability between the two, or did you compare the source (oh wait you cant on the cpp side).
At least give it a bit more time than ~one hour before you start wildly speculating so it is not too obviuous that its mere speculation based on zero facts
Test Subject
Original Poster
#5 Old 10th Apr 2021 at 3:01 PM Last edited by Same4254 : 10th Apr 2021 at 3:14 PM.
Quote:
Originally Posted by Battery
what a crazy coincidence indeed, and just one month after S3Se released dot dot dot


I have no interest in reverse engineering CPP code... that sounds like a nightmare. I came up with this between a few friends after some discussion. I know that there's not really much I can tell you to support the claim that I didn't use your project as a starting point. I take it from your reaction that this was a similar approach (surely we did something different)? I did not know your project existed until it was pointed out to me by the other commenter.
Lab Assistant
#6 Old 23rd Apr 2021 at 4:38 PM
Quote:
Originally Posted by Same4254
I have no interest in reverse engineering CPP code... that sounds like a nightmare. I came up with this between a few friends after some discussion. I know that there's not really much I can tell you to support the claim that I didn't use your project as a starting point. I take it from your reaction that this was a similar approach (surely we did something different)? I did not know your project existed until it was pointed out to me by the other commenter.


please tell me you did not abandon this! I understand about the thing that happen. but I think this very different and I think the community NEEDS this!
Test Subject
Original Poster
#7 Old 23rd Apr 2021 at 5:58 PM
Quote:
Originally Posted by dinadine
please tell me you did not abandon this! I understand about the thing that happen. but I think this very different and I think the community NEEDS this!


No, I most certainly have not hahahaha. I am still working on this. I am currently working on breaking this down to a nicer science with messaging classes and having a protocol.

For an update, what I have done so far is merge these two into a single project, and allow the modder to use C# preprocessor directives to essentially macro what pieces of code gets compiled. So if you compile with .NET 4.x you get the external program and a main and all that. But if you compile with .NET 2.0, your macros will remove all of the .NET 4.x related code. This makes it so that you can have one set of source files that can easily end up on both sides.

However, I urge modders to play with this and do their own thing. That is why I posted the source code and an explanation of what it does. I am a busy university student working towards two degrees and a job and stuff. So I don't have the time to get this up to spec for everyone on this site. If you want this, I have given you everything you need to do it yourself. With that being said, I will probably post a github repo at some point with what I have, which should be a nice foundation for anyone that wants to use this.
Test Subject
Original Poster
#8 Old 23rd Apr 2021 at 9:16 PM
To the two people that upvoted the disagree button, I am curious why. Please return a message post haste.
Lab Assistant
#9 Old 23rd Apr 2021 at 11:33 PM
Quote:
Originally Posted by Same4254
To the two people that upvoted the disagree button, I am curious why. Please return a message post haste.


This far beyond my skill so I have to wait till someone far more skilled then myself finishes it I am eagerly awaiting the grand finally.
Back to top