Pre-Alpha hype thread

Since I was not able to locate the code that updates +0xfc, I wrote the code myself. I've verified that the simulator runs at mostly the correct speed. It simulates 100 game minutes in 600 real world seconds, which is close enough to the target of 120 (1 game minute per 5 real world seconds). This is probably because the game defaults sim_speed to 0x15 (21), and if you multiply that by 120/100, you get 25.2, so sim_speed is probably *supposed* to be 25 (which is a "rounder" number than 21), but the developers probably temporarily changed it to 21 for defaulthouse.dat. I'll come up with a patch to share soon.

Next on the list is getting the action queue to work. It turns out that if you spawn NPCs on the lot, you will control the first NPC that you put on the lot; if you click around, you'll get that NPC's head in the pie menu, and if you click on the NPC itself, you get "This is the active character". (Clicking on other NPCs shows you the regular pie menu actions for those NPCs.) Clicking pie menu commands does not do anything yet.

If you pass "-playerid 0" to Play Test, you'll get the default sim named "Guy" on the lot, with a green plumb-bob above him. (I'm not sure if you can control Guy if you apply the simulator hack to Play Test.) You'll notice that Play Test comes with "netuser00000.iff" in the userdata/characters folder, but Pre-Alpha doesn't come with it. Copying and pasting this file into Pre-Alpha's characters folder and running with -playerid 0 doesn't cause Guy or any other sim to show up, so it's possible that the format of netuser00000.iff changed.

What's interesting is that, though I can't seem to get the AddPlayer cheat to work (you just get the error "You cannot create a cheat avatar copied from a previous clone. Use the original."), if you keep netuser00000.iff in your characters folder, and you replace the conditional jump just above the reference to the error message string with an unconditional jump, and then run "AddPlayer" and skip over the access violations that result, then the game will put a plumb-bob over the active character and also create a new file, netuser00001.iff. If you rename this new file to netuser00000.iff, and you run KillPlayer, then the game doesn't crash the next time you run the AddPlayer cheat.

If you restart the game though, and your lot doesn't load and you have to delete nonetworkhouse.dat, then when the game creates a new nonetworkhouse.dat, it also deletes all files that exist with the name "netuser%d.iff", even if you pass -avatarid 0, and it still does not load any characters.

The main goal though is just getting the action queue to work.
 
Well getting the action queue to work is a tough subject, so for now, here is the patch to enable the simulator. :D

http://niotso.org/files/nonetworksimulator.bps

Apply to Pre-Alpha's TSOSimulatorClientD.dll using beat (hosted here, and I'm also hosting my own copy here).

Then run Pre-Alpha's TSOClient.exe with the -NoNetwork argument (-debug_objects is also recommended). Click OK to the "Some objects in the game could not be loaded" error. We probably get this error simply because nonetworkhouse.dat does not exist by default and needs to be created. Run the "fullopt" cheat (using ctrl+shift+C), and run the cheat "sim_speed 25" to play at normal speed. Run the "edith" cheat to open Edith, and use Window -> New Object Browser to add things to the lot. Use Sims -> Show Module Inspector to view running objects on the lot.

If the game starts crashing whenever you try to run it, delete nonetworkhouse.dat in the UserData/Houses folder. Note also that UserData/tso.syslog can grow to a large size, so you can also delete this from time to time.

Also, the simulator did stop working for me after fooling around for a while with Edith. If that ever happens to you, setting dword [cTSOSimulator+0xfc] to dword [cTSOSimulator+0xcc] fixes the issue. (cTSOSimulator is allocated dynamically, but you can catch its value by putting a breakpoint on cTSOSimulator::ClientIdle() at base+0x1ecfc in TSOSimulatorClientD.dll and looking at ecx.)

I'll describe each modification that I made.

I expanded the size of cTSOSimulator by 0x18 bytes, by changing the instruction at base+0x6a0a from "push 0x130" to "push 0x148". The 0x18 bytes hold three qwords: something I call time_frequency (which is set during program initialization to the result of QueryPerformanceFrequency(), which Microsoft says is guaranteed not to change until the computer restarts), time_counter (which is set to the result of QueryPerformanceCounter() during each call to ClientIdle()), and fractional_time (which holds the remaining "fraction of a tick" that needs to be added to time_counter in the next call of ClientIdle()).

I hooked the cTSOSimulator constructor to initialize these three dwords; time_frequency is initialized by QueryPerformanceFrequency(), and time_counter and fractional_time are set to 0. At base+0x1cd02, "call base+0x96623" was changed to "call base+0xbc331". I overwrote the 00s at base+0xbc331 with this function:

Code:
lea edi, [esi+0x130]
push edi
call dword [eax+0xa032d] ; base+0x1ccef+0xa032d = base+0xbd01c -> QueryPerformanceFrequency IAT entry
xor ebx, ebx
mov dword [edi+0x08], ebx
mov dword [edi+0x0c], ebx
mov dword [edi+0x10], ebx
mov dword [edi+0x14], ebx
mov ecx, esi
jmp base+0x96623

Also, in the function where the house gets loaded and the value of the tick counter (cTSOSimulator+0xcc) gets read from the house file (defaulthouse.dat or nonetworkhouse.dat), I placed a hook to also store this value in the "simulate until" value (cTSOSimulator+0xfc). This was done by changing the instruction at base+0x1d95b from "call base+0xa0055" to "call base+0xbc353". I overwrote the 00s at base+0xbc353 with this function:

Code:
mov dword [edi+0xbc], ebx
jmp base+0xa0055

Lastly, I patched ClientIdle() to call my new "AdvanceSimulator()" routine (as I'm calling it). The instruction at base+0x1ed0e was changed from "mov eax, dword [esi+0xcc]" to "call base+0xbc35e; nop". (AdvanceSimulator() returns dword [esi+0xcc] in eax, but is not stdcall compliant.) I overwrote the bytes at base+0xbc35e with this (quite nicely optimized) function:

Code:
  sub esp, 0x08
  add esi, 0x130
  push esp
  call dword [eax+0x9e31a] ; base+0x1ed06+0x9e31a = base+0xbd020 -> QueryPerformanceCounter IAT entry
  pop edi
  pop eax

  ; read time_counter and then store current_time into time_counter
  mov edx, dword [esi+0x08] ; time_counter
  mov ecx, dword [esi+0x0c] ; time_counter+4
  mov dword [esi+0x08], edi ; time_counter
  mov dword [esi+0x0c], eax ; time_counter+4

  ; perform the 64-bit subtraction current_time - (previous value of time_counter, before writing the new one)
  sub edi, edx
  sbb eax, ecx

  ; if the time difference from the last simulation is greater than time_frequency (1 second), or if the is_paused byte is set, do not count this as simulation time
  mov edx, eax
  xor ecx, ecx
  movsx ebx, byte [esi-0x3c] ; +0xf4: cTSOSimulator::is_paused byte
  cmp edi, dword [esi] ; time_frequency
  sbb edx, dword [esi+0x04] ; time_frequency+4
  sbb ebx, ecx
  mov ebx, dword [esi-0x64] ; cTSOSimulator+0xcc
  jns return

  ; multiply the time difference by cTSOSimulator::speed (25 decimal for normal speed)
  mov ecx, dword [esi-0xc8] ; cTSOSimulator+0x68: speed
  mul ecx
  xchg eax, edi
  mul ecx
  add edx, edi

  ; add the fractional tick left over from the last simulation
  mov ecx, dword [esi-0x34] ; cTSOSimulator+0xfc
  add eax, dword [esi+0x10] ; fractional_time
  adc edx, dword [esi+0x14] ; fractional_time+4

  ; back up the existing x87 FPU control word, and then change it to set the rounding mode to "truncate" and disable floating point exceptions
  push ecx
  fstcw word [esp]
  push 0x07ff
  fldcw word [esp]

  ; perform the calculation:
  ; add_ticks = speed_multiplied_time_counter_difference_plus_fractional_time / time_frequency
  ; fractional_time = time_frequency - a*time_frequency
  push edx
  push eax
  fild qword [esi] ; time_frequency
  fild qword [esp]
  fld st0
  fdiv st0, st2
  frndint
  fist dword [esp]
  fmul st0, st2
  fsubp st1, st0
  fistp qword [esi+0x10] ; fractional_time
  fstp st0 ; align the FPU stack

  ; restore the original control word
  fldcw word [esp+0x0c]

  ; the rest of the code uses sbb tricks to avoid branch instructions
  pop eax
  add ecx, eax ; Take cTSOSimulator+0xfc, and add only the low 4 bytes of add_ticks
  sbb eax, eax ; set eax to all FFs if this would overflow (cTSOSimulator+0xfc would go past 0xFFFFFFFF), all 00s otherwise
  lea edx, [ecx+1] ; temp = add_ticks + 1
  add esp, 0x0c ; deallocate everything that remains on the stack
  and eax, edx ; set eax to add_ticks+1 if cTSOSimulator+0xfc would overflow, 0 otherwise
  sub ecx, eax ; subtract cTSOSimulator+0xfc by this amount (set it to 0 on overflow, do nothing otherwise)
  sub ebx, eax ; subtract cTSOSimulator+0xcc by this amount
  sbb eax, eax ; see if *this* would overflow
  not eax ; set eax to all 00s on overflow, all FFs otherwise
  and ebx, eax ; set cTSOSimulator+0xcc to all 00s if it would overflow
  mov eax, ecx ; temp = cTSOSimulator+0xfc
  add eax, 0x3c ; ClientIdle() always advances this field by 0x3c, so, see if temp+0x3c would overflow
  sbb eax, eax ; set eax to all FFs on overflow, all 00s otherwise
  and eax, ebx ; set eax to cTSOSimulator+0xcc on overflow, 0 otherwise
  sub ebx, eax ; subtract cTSOSimulator+0xcc by this amount (set it to 0)
  sub ecx, eax ; subtract cTSOSimulator+0xfc by this amount
  sbb eax, eax ; see if *this* would overflow
  not eax ; set eax to all 00s on overflow, all FFs otherwise
  and ecx, eax ; and cTSOSimulator+0xfc by this amount (set it to 0 on overflow, do nothing otherwise)
  mov dword [esi-0x34], ecx ; store cTSOSimulator+0xfc
  mov dword [esi-0x64], ebx ; store cTSOSimulator+0xcc
return:
  mov eax, ebx ; return cTSOSimulator+0xcc
  sub esi, 0x130 ; readjust esi
  ret

Note that I'm using x87 FPU instead of SSE since there's no efficient way to convert a 64-bit integer to a floating point value with SSE on x86-32, and I'm using floating point rather than fixed point for the division step because there is no "divide 64-bit integer by 64-bit integer" instruction anywhere on x86-32; you have to perform an algorithm in software, and this is at probably least as slow as converting to floating point and back in hardware.
 
Last edited:
Incredibly impressive, amazing work! I was surprised when attempting some call trees how fragile maxis's implementation of simantics is - It's incredibly easy to cause an exception if you call trees meant for avatars using a null caller (the module inspector). Our vm is as fragile (if not more, since it crashes on exceptions so i can debug them...) but I thought that was because we haven't implemented many error checks.
 
At the request of Rhys, I gave a shot at enabling the simulator in Play Test. I got the simulator working in Play Test at blazing fast speed now, so now I just need to port my modifications over. The default character on the lot "Guy" can't be controlled even with the simulator working, but he animates.

It's interesting how unlike Pre-Alpha, even though Play Test comes with defaulthouse.dat, house.dat, and house10.iff, it does not even touch any of those files. The default house appears to be hardcoded into the game. default_house.xml was not introduced until the first boxed version of TSO.
 
Last edited:
Have you tried running the Play Test version of Person A Possess in place of an idle anim to see for sure if they broke the animation instead of changed the format?
 
I've made the patch for Play Test: http://niotso.org/files/nonetworksimulator_playtest.bps

You will have to also change the byte in TSOServiceClientD.dll at offset 0xbf25 from "15" to "19" to play at normal speed. I didn't bother making a bps patch for that.

The changes from Pre-Alpha to Play Test are summarized as follows:
cTSOSimulator constructor: TSOSimulatorClientD_base+0x1cce5 -> TSOSimulatorClientD_base+0x54df2
cTSOSimulator::ClientIdle() function: cTSOSimulatorClient_base+0x1ecfc -> TSOSimulatorClientD_base+0x5699d
Game speed: esi+0x68 -> esi+0x58 (decreased by 0x10)
Current tick value: esi+0xcc -> esi+0xbc (decreased by 0x10)
Is paused byte: esi+0xf4 -> esi+0xe8 (decreased by 0x0c)
"Simulate until tick" value: esi+0xfc -> esi+0xf0 (decreased by 0x0c)

The results of the a2o-soc-death-possessed-sima.anim animation are:
Play Test animation played in Play Test: Glitched legs, with vox_death_possessor sound
Pre-Alpha animation played in Play Test: Normal legs, no sound
Play Test animation played in Pre-Alpha: Glitched legs, no sound
Pre-Alpha animation played in Pre-Alpha: Normal legs, no sound

http://niotso.org/files/possessed_animation.png

Interestingly, in Play Test, the animation also shows up in the avatar panel.
 
I think the Sim is rendered playing the idle-breathe animation in the avatar panel in Play Test (so they don't look like a dead stuffed body). So that animation is definitely broken, not great... It's weird that the playtest animation in the pre-alpha played no sound, since other animations in the pre-alpha (like a2o-bull-hi2-ccw.anim) have the same sound events as in playtest and the final game, implying sound events were meant to be in the game at that point.
 
By the way, with the pre-alpha you can run any interaction on a sim using a simple custom global tree, that I just worked out brainstorming with franco. Here's the tree:

upload_2015-2-1_1-58-50.png
upload_2015-2-1_1-58-3.png
You need to manually change the interaction number each time and save, but apart from that you can then easily call your new global tree from the object in question through the object inspector (Object.../Send Message/) to call the specified interaction. I might make a more general version that you can patch Global.iff with.
 
I can confirm that actions are not working because there is not selected person, as like this cheat message "interests" show:
 

Attachments

  • tsoprealinterest.png
    tsoprealinterest.png
    331.9 KB · Views: 49
I was helping Afr0 search for any pictures of property purchasing in The Sims Online (here is the thread); I was unsuccessful, but I found a "Pre-Alpha" picture:

https://www.flickr.com/photos/simprograms/4283510670/
4283510670_81cba487e8_o_d.jpg


The UI button placement is slightly different in that picture compared to the version of Pre-Alpha that we have. (Ours looks the same as the later versions of TSO.) In fact, this is likely to be a concept image, and not an actual ingame screenshot, due to the lighting visible on the terrain and the use of drop-shadows on the UI bars, neither of which actually appear in Pre-Alpha through EA-Land. There is also another picture similar to this one but which has "AM/PM xx:xx" rather than "xx:xx AM/PM" and where the "xx:xx" appears higher than the "AM/PM", indicating that the text was probably photoshopped (https://www.flickr.com/photos/simprograms/4282762085/).

On the other hand, here is a picture from Gamespot that has the same UI as our version of Pre-Alpha (note the old bookmark icon):

https://www.flickr.com/photos/simprograms/4282794577/
4282794577_ff927e6933_o_d.jpg


There are plenty more of these pictures in this Flickr photostream, starting on page 140 (look for the old bookmark icon)
https://www.flickr.com/photos/simprograms/page140/
 
Last edited:
The big research about defaulthouse.dat start using tso pre alpha, the floor arrays seems to be controlled by one byte!!!

In the image i changed the street floor by this red..
 

Attachments

  • tsoprealphafloor.png
    tsoprealphafloor.png
    232.3 KB · Views: 33
Back
Top