- Site Map >
- Modding and Creation >
- Sims 4 Creation >
- Modding Discussion >
- Injecting/Modifying SimData and Other Assets by Poking Memory
- Site Map >
- Modding and Creation >
- Sims 4 Creation >
- Modding Discussion >
- Injecting/Modifying SimData and Other Assets by Poking Memory
Replies: 1 (Who?), Viewed: 698 times.
#1
19th Sep 2024 at 5:08 AM
Last edited by kronosta : 19th Sep 2024 at 5:42 AM.
Posts: 4
Thanks: 23 in 3 Posts
Injecting/Modifying SimData and Other Assets by Poking Memory
It's been a long-standing problem with Sims 4 mods that SimData makes things uninjectable by Python script mods, meaning some level of incompatibility is required for many things. The Venue List mod is required because of this, for example.I thought maybe I could inject SimData and strings using Cheat Engine, and after a few hours of work, I actually managed to rename a trait through its SimData, construct entirely new SimData that I could switch between by messing with pointers, and through a similar method to the previous, create an entirely new string at runtime. It's kinda hard for me to concisely describe how to do it, so I'll put my notes here.
A fair word of warning though: most operating systems have functions to read and write the memory of other processes, but they always lock it behind administrator privileges. Doing this through a Python mod (which I have not done yet) would require invoking another executable somewhere, and that executable would require administrator privileges, so users of a mod would have to trust that it isn't malware, as some pretty terrible things can be done with those privileges.
Here are my notes (indentation is important for organization here but that requires preformatting so I'm using the code tag, please ignore the colors):
Code:
-!This was all tested by manually looking at and editing the memory with cheat engine. - A mod that makes use of this would have to be a Python script mod that invokes another program to do the work. That program would require administrator privileges to be able to do stuff like this to another process's memory. - Lots of strings are stored consecutively in memory, null-terminated. - A large region of these are HTML/XML. - In one case they started at memory address 7FF4F4240000, in another 7FF4F3E20000. Location appears to be random - This region starts with some sort of header "00 00 00 00 00 00 00 00 E2 AF 33 00 00 00 00 00". - 3rd relaunch leads to 7FF4F5080000 - For Rent stuff comes first in my game. - The address 7FF4F3E20010 (the string table offset by the header, on 2nd relaunch) appeared to have a pointer at 7FF4F68CD7A8, in little-endian form. A ton of similar values are nearby, along with other weird non-pointer data - 3rd relaunch led to 7FF4F2316CC8 - Looking at the other pointers further preceding the pointer to the strings, there seem to be multiple string tables. - Each of the pointers seemingly points to a separate table of addresses, then there's a break in pointer data, then the start of the table of tables, then the start of the total amalgamation of all string - Later pointers pointed to some seemingly CAS-related stuff, some partially repetitive structured data, and some weird data that had tiny bits of English words in them - This doesn't appear to be consistent across relaunch, as the 2nd relaunch had a dds UTF-16 filename (maybe storing some sort of reference to a texture. - It's possible that this array is the full index of assets - There's also another pointer. On 3rd relaunch it was located at 7FF4F5C50020 along with some other similar data - This actually appears to be an index of all the strings. Addresses increase and all point to the start of strings - Some pointers lead back to this same table. Following these recursively eventually leads you to a zeroed-out entry (???) - This pointer is pointed to by the 8 bytes directly preceding the first. So it looks like the 1st pointer sometimes stores pairs of index tables and component data. It's much more complex than that though, some stuff is stored besides pointers - Checked out one address nearby the pointer, 4 64-bit increments later: 7FF4F1E5EC20 - A 32-byte start appears here, followed by some binary data containing a UTF-16 LE string "engagementlibrary_IC.dds" - Tried the above again with new addresses: 00007FF4FCF10910 at 7FF4F2316CC8 - Different this time, strings such as "ThumbnailSimTextureSize" and "CASSinglePassShaderEnabled" - Base address of pointer array is 7FF4F16F0000 - [SimData modification] An array of structures occurs somewhere (at 7ff4bcdf0000 on 4th relaunch) where some contain the hex string: 7a c6 5a 54 - This is notable because little endian would flip those bytes into 545ac67a, the type code for a SimData asset. - They also have some text data, an array of null-terminated strings corresponding to the names of the XML values in the SimData. - Not all of them have those hex bits - Furthermore, by searching for a SimData name string in CheatEngine, you can find its specific data. -!Confirmed to be SimData, editing the display_name property of the Gloomy trait changes its name in the UI. - Trait needs to be clicked before it gets refreshed - Alternatively, resetting the current household or randomizing (no matter how little you randomize, just voice will do) - Some SimDatas occur twice in memory. For example the Gloomy trait I was testing on had one copy of the SimData for the trait picker and another copy for the trait in the hexagon - The SimData structure seemingly starts on the ASCII string "DATA", based on a pointer I found for the gloomy trait. Another pointer to it starts 176 bytes later, suggesting that the important data starts there. - The first pointer had it as part of a structure composed of the Instance, Type, and Group of the Gloomy trait SimData (all reversed because little-endian). - There are other structures nearby for other SimData, though they have variable lengths. - This array of structures and pointers is quite massive and stored 16 megabytes of data - This structure is good for finding the SimData, but the UI (at least trait UI) doesn't display changes to the pointer addresses here - The second pointer also exists next to a bunch of SimData pointers, with a more haphazard structure. - In here, you can find the location of the pointer by searching through memory for its address. - Here, you can actually create dynamic SimData structures by copying an existing structure to an unused part of memory, maybe modifying it, then changing the address in the table. - It's possible (on Windows at least) to add memory to another process's virtual address space using the VirtualAllocEx function, so that could be used to create unused memory for this to use. - What CheatEngine says is the AllocationBase of the SimData region actually starts by defining CAS parts - Procedure for finding SimData addresses since they are different each launch: - Search memory for the hexadecimal string "44 69 73 70 6C 61 79 49 6E 64 65 78 00 43 6C 69 65 6E 74 5F 54 61 67 73 54 72 61 69 74 47 72 6F 75 70 4D 65 74 61 64 61 74 61 2E 4D 65 74 61 64 61 74 61 56 61 6C 75 65" - This should occur exactly twice. - One instance will be followed by "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41", the other will be followed by "00 00 00 00 00 00 00 18 00 00 00 CC CC CC CC 41" - The second has it in the 3rd SimData on my system, but one of the earlier ones involves a pack (it's the Leader of the Pack aspiration track, so it might depend on what packs you have, and it's probably good to assume the 1st one might as well (I don't have all the packs). - So, loop the following from each address found: - Search the previous 4096 bytes for the string "DATA" - It's reasonable to assume SimData isn't longer than this - If found, record the address into a variable and continue - If not found, stop the loop and use the value of the variable - The value of your variable is the base address - The base address as far as I know isn't very useful other than to make searches for SimData by tuning name more efficient - Pointers get put in some weird places though, so searching through the entire memory may be necessary to find stuff. - Searching for the Instance, Type, and Group (each little-endian) often gets you what you want in one of the next two 8-byte aligned parts - If you search for the hex ID of a string, one of the results is a table of IDs to their locations as pointers - By changing the pointers here and causing a refresh by selecting the trait, resetting the CAS household, or randomizing, a dynamic string can be constructed - The memory location does not have to be in a string table group - A massive table exists (in one relaunch is started at 7ff48afa1000 - Searching for "_IKtarget" as text leads to an area that I can't quite tell what it's storing. Some of the strings present in certain areas look kind of animation-related, and some look like they might be tunings with their keys in string form - Searching for "buff_replacements" (a tuning key in some traits) brings you to data that looks really similar to the possible tunings, so I think they probably are tunings - A few strings like "request", "urlopen", "git+ssh" exist at some point. Maybe this region belongs to Python? - Right before it "urllib.parse", "XMLFilterBase.error", this is probably Python memory. - It makes sense tunings would be here, since the Python server handles them
Advertisement
#2
14th Nov 2024 at 2:32 AM
Posts: 3,141
Thanks: 4739 in 9 Posts
Hooboy, we're getting really into the weeds here. Technically, I don't think you'd need another executable to poke the memory or admin privileges. You'd only need to poke the memory of your own process, and since you're only poking data memory, this wouldn't even trip memory protections since programs are expected to poke their own data. If you start asking programs to execute instructions in that memory, you might trip DEP, though. I've not worked with Python enough to know if there are functionalities for letting you directly write memory addresses in a low-level manner like this. You could definitely injection-load a regular C library at runtime to gain this capability, though,
Grant me the serenity to accept the things I cannot change, the courage to change the things I cannot accept, and the wisdom to hide the bodies of those I had to kill because they pissed me off.
Grant me the serenity to accept the things I cannot change, the courage to change the things I cannot accept, and the wisdom to hide the bodies of those I had to kill because they pissed me off.
Who Posted
|