I don't feel like typing up a long post, so, here are our notes:
Code:
prealpha -> naitrial
Member functions:
cTSOSimulator::cTSOSimulator: TSOSimulatorClientD_base+0x1cce5 -> TSOSimulatorClientD_base+0x6c9a4
cTSOSimulator::SetSpeed: TSOSimulatorClientD_base+0x1d722 -> TSOSimulatorClientD_base+0x6d387
cTSOSimulator::AssembleAndSendChecksum: TSOSimulatorClientD_base+0x1de93 -> TSOSimulatorClientD_base+0x6dbbf
cTSOSimulator::AssembleAndSendChecksync: TSOSimulatorClientD_base+0x1df41 -> TSOSimulatorClientD_base+0x6dc1c
cTSOSimulator::ScheduleLocalEvent: TSOSimulatorClientD_base+0x1e10a -> ?
cTSOSimulator::LocalPause: TSOSimulatorClientD_base+0x1e26b -> TSOSimulatorClientD_base+0x6de4c
cTSOSimulator::LocalUnpause: TSOSimulatorClientD_base+0x1e35a -> TSOSimulatorClientD_base+0x6debb
cTSOSimulator::ClientIdle: cTSOSimulatorClient_base+0x1ecfc -> TSOSimulatorClientD_base+0x6ec79
cTSOSimulator::HandleEvent: TSOSimulatorClientD_base+0x1f192 -> ?
Member variables:
cTSOSimulator::sim_speed: +0x68 -> ?
cTSOSimulator::tick_number: +0xcc -> 0xd4
cTSOSimulator::is_paused: +0xf4 -> 0x100
cTSOSimulator::simulate_until: +0xfc -> 0x108
Do a regex search for "DWORD PTR \[.*\+0x108\]," in TSOSimulatorClientD.dll,
and the result is:
10067963: 89 83 08 01 00 00 mov DWORD PTR [ebx+0x108],eax
1006ca7b: 89 9e 08 01 00 00 mov DWORD PTR [esi+0x108],ebx <- being set to 0
1006f33a: 89 86 08 01 00 00 mov DWORD PTR [esi+0x108],eax
1009c07c: 89 8e 08 01 00 00 mov DWORD PTR [esi+0x108],ecx
That leaves us with 10067963, 1006f33a, and 1009c07c as potentially the code we
want to reach to increment the simulate_until variable.
Of these three, 1006f33a is perhaps the most interesting (at least in my
opinion) because it appears to retrieve its value from an object that accepts
IID=0x65297976 (kGZIID_cIGZMessage2Standard). The function starts at 0x1006f2ea
and is called by this code:
1006e62a: ff 50 10 call DWORD PTR [eax+0x10]
1006e62d: 6a 01 push 0x1
1006e62f: 2d 03 3d 7c 2c sub eax,0x2c7c3d03
1006e634: 5b pop ebx
1006e635: 0f 84 28 01 00 00 je 0x1006e763
1006e63b: 83 e8 09 sub eax,0x9
1006e63e: 0f 84 b4 00 00 00 je 0x1006e6f8
1006e644: 2d 86 fa dc 11 sub eax,0x11dcfa86
1006e649: 0f 84 90 00 00 00 je 0x1006e6df
1006e64f: 83 e8 0c sub eax,0xc
1006e652: 74 19 je 0x1006e66d
1006e654: 2d 03 ec 15 2d sub eax,0x2d15ec03
1006e659: 0f 85 b5 01 00 00 jne 0x1006e814
1006e65f: 57 push edi
1006e660: 8d 4e dc lea ecx,[esi-0x24]
1006e663: e8 82 0c 00 00 call 0x1006f2ea
0x2c7c3d03 = 0x2C7C3D03 -> ?
0x2c7c3d03+0x9 = 0x2C7C3D0C -> ?
0x2c7c3d03+0x9+0x11dcfa86 = 0x3E593792 -> ?
0x2c7c3d03+0x9+0x11dcfa86+0xc = 0x3E59379E -> ?
0x2c7c3d03+0x9+0x11dcfa86+0xc+0x2d15ec03 = 0x6B6F23A1 -> kServerTickConfirmationMsg (!!!!!)
This is very interesting because the debugging string in naitrial
"TicksPerServerConfirmation" (at TSOSimulatorClientD_base+0x6e311) tells us that
a "server confirmation" provides a number of ticks to the client.
By placing a breakpoint on the ClientIdle function, you can see that when the house is first loaded, both current_tick and simulate_until are set to 0.
If you send a kServerTickConfirmationMsg cTSONetMessageStandard, it turns out that 0x1006e663 is reached, and subsequently, 0x1006f33a is reached with eax corresponding to the value of the "String ID" field (gosh, we need to rename that). Since there are 5 real world seconds per game minute and 25 SimAntics ticks per real world second, I set this value to 0xaf for 175 ticks or 7 real world seconds, and sure enough, the game time changed from 12:01 to 12:02.
So kServerTickConfirmationMsg updates the value of simulate_until. But is this seriously all we need to do: send the packet several times per second and the simulation works? What about the server "confirming" that the client is in sync etc.? How does the client receive actions from other players? And what do we need to do to ensure that the simulation is smooth for the player?
It seems that, to ensure that the client is still in sync with the server, the client will periodically call cTSOSimulator::AssembleAndSendChecksum and cTSOSimulator::AssembleAndSendChecksync, at least when the server asks the client to do so; but so far this hasn't happened yet.
It seems that the client should receive player actions from another packet, and that the server should send these packets before sending the kServerTickConfirmationMsg to the client for the tick when those actions are scheduled to occur.
It turns out that, if you set the simulate_until value to a really high value (many game hours in the future), to catch up to the current time, the game does not simulate as fast as it can, but instead, it
gradually increases the speed of the simulation and then gradually decreases the speed when it begins approaching the current time.
As for whether or not the game buffers the kServerTickConfirmationMsg packets it receives and only responds to them after a certain scheduled time in the future (e.g. an 0.5 second latency would protect against dropouts up to 0.5 seconds long), I'm not sure yet.
Here is the packet:
http://niotso.org/files/kservertickconfirmationmsg.dat
and a screenshot: