Post subject: A progress report of sorts: Hourglass and save-states
Warepire
He/Him
Editor
Joined: 3/2/2010
Posts: 2178
Location: A little to the left of nowhere (Sweden)
... OR How I learned to stop worrying and studied the principles of memory management, C++ std::allocator, and the horrors of non-fundamental data types in the global scope of DLL code. The background: I placed a _MARKER_ at the end of the background, to skip it, find the marker. As many have experienced save-states in Hourglass work less than great when Hourglass is run on Windows 7+ (technically also on Vista, but ... why?). While this was a problem it was not seen as a major issue until Microsoft decided to end XP support due to the very few number of games that took the save-state issues to the extreme and caused BSoDs. The quest to improve this began, so that TASers updating to Windows 7+ wouldn't by default be thrown under the bus while other features were being worked on. Some of the issues with save-states were identified: • Save-states turning stale. • Loading save-states crashing the game. • Loading save-states crashing Windows with various BSoDs. A couple of reasons are related to the Load save-state code, which does zero error checking and proceeds even if an error value was returned in the process. On XP these operations would probably "never" fail as the processes have a freer reign than on 7+. Other reasons turn a bit more scary, and may be related to a few posts I have seen regarding memory leaks. So, one might now think "But, why go through all of this and not just fix the loading of save-states?" and that is a very valid question. There are 2 reasons for this, the first being, it's like putting a band-aid on a wound that needs stitches. For the second reason, let's have a look at this memory leak thing, and how save-states are stored. For reason one, the major issue with the load save-state code is that on Windows 7+ it may fail due to access rights and whatnot. Failing better with loading save-states leaves you with stale states, meaning re-watching the movie until the same state is reached again. That is just annoying if you want to load a state 15 minutes in and need to re-watch your progress EVERY TIME. The second reason is the fun one, the second half is easier to explain: The save-states in Hourglass are stored in RAM, and RAM alone. This doesn't matter so much due to the fact that save-states do not work cross-session. But it does matter because any game using a lot of RAM will produce big save-states. Before I attack the first part of reason two, this is going to get a bit techy and a bit trippy, so jump to the _MARKER_ further down if you want to skip it. I'll need to cover the save-state process first, that is easy: When we make a save-state we just dump the entire memory map (incl. DLLs) and the thread contexts into a binary blob and slap some headers on it. Now loading this, this is where it gets hard, we need to suspend the process, and overwrite it's current memory state with the old (saved) one. This was referred to as an instant brain transplant by nitsuja, and it's a great way to think about it. What is done is a forced overwrite of everything, this is rather simple, except on Vista+ you can get Invalid Memory Read blue screens. Now, this problem on it's own is not too bad, we can just capture the exceptions and fail better at loading the state, and use the stale state logic... but we're also royally screwing with Windows internal state in many cases while loading states. To understand this part, let's consider how the DLL works. The DLL is injected into the game process space, which means that the DLL state is also captured in the save state, and brutally restored when states are loaded. The DLL is therefore no better at managing memory than the game, and since the game is royally screwing with Windows because of us, so is the DLL. This would not be so bad if the DLL was a simple middle-man that just adjusted values following a fix set of rules. But, the DLL contains quite a lot of logic, that needs to allocate memory, in order to give us control of time. Now, how are we royally screwing with Windows? Riddle me this, what happens if we save a state, the game progresses a bit and allocates new memory (because a level-transition or whatever), and then we load the state? There is now no known state of the game that knows about this particular memory allocation. The same goes if the DLL allocated memory. We have now told Windows to reserve this memory space for us, it's just that we can never use it, because we have no reference to it, as we erased the knowledge of this allocation from the "brain" entirely. This is basically Alzheimer's disease represented as a Windows process at this point. So, we're leaking memory, and it's almost entirely invisible. We must therefore know what we allocate, and when, and where. We cannot entrust the DLL to keep track of this information as outlined above. What to do? Let the Hourglass executable instruct the DLL how to behave by encapsulating everything we can into memory mapped files, we can then easily keep track of their state, and we can easily move them backwards and forwards in time. This leaves the Contexts and stacks unaffected however, but these parts should be easier to handle as a special case. Since we create all the threads from the DLL though, we can set the security level for them as well, so the point of failure will be smaller. This may very well cause all sorts of problems with loading save-states that we just cannot see explicitly. _MARKER_ Now with the background out of the way, what has been done so far? So far the implementation has been solely in the DLL part, and it covers the memory managing logic itself and the std::allocator part. This part was a major battle featuring many re-writes, and will probably feature a few more, currently it's a bit rough around the edges but it should be stable enough for testing purposes. std::list may for example not be the most clever way to keep track of blocks, and the block-searching can surely be done much much better. The current code of the Memory Manager can be viewed here, some comments may not be fully accurate anymore as I have not reviewed them entirely since the last re-write. https://github.com/Warepire/Hourglass-Resurrection/blob/memory_manager/wintasee/MemoryManager/MemoryManager.h https://github.com/Warepire/Hourglass-Resurrection/blob/memory_manager/wintasee/MemoryManager/MemoryManager.cpp The current code for the hooks into the Windows API can be viewed here: https://github.com/Warepire/Hourglass-Resurrection/blob/memory_manager/wintasee/hooks/memoryhooks.cpp Every internal std:: container-type was extended to use the Memory Manager implementation of the allocator, every usage new, malloc(), realloc(), free() and delete were replaced as well. Everything compiles fine, Hourglass-Resurrection launches properly. Loading a game seems to work... then nothing happens, no Window shows up. I didn't change any code that runs before DllMain(), because no code runs before DllMain(), but no DLL log messages show up and the game process seems stuck in an endless loop. Where did it all go wrong? Where it all went wrong: Recall when I said that no code runs before DllMain()? That was a good joke, was it not? Code DOES execute before DllMain(), the code needed to initialize the globals. Did I mention that Hourglass have global std:: containers? No? Well, Hourglass does. Code that is not ready to execute before a certain point has been reached in the DllMain() is executing before DllMain(), all because of these globals. Aren't globals awesome? Next steps: Re-structure a lot of the DLL to get these globals out of the global scope, preferably using singleton classes or a similar approach. And finally, some credit where credit is due. I would not have reached this far without the invaluable help of: Nach, Ilari, feos, Invariel, ais523, Henke37 and zid` (who might have left the #tasvideos IRC permanently, but in case he reads this, thank you!) Also credits to nitsuja for starting the Hourglass project!
Zarmakuizz
He/Him
Joined: 10/12/2013
Posts: 279
Location: France
Glad to see you still on Hourglass! I enjoyed reading those kind of technical battles (so I liked this thread), and you have some though ones with Hourglass. Keep up!
Active player (476)
Joined: 2/1/2014
Posts: 928
I Love You.
Post subject: Update to the update report.
Warepire
He/Him
Editor
Joined: 3/2/2010
Posts: 2178
Location: A little to the left of nowhere (Sweden)
Yesterday night I managed to resolve the DllMain() not executing problem. The non-fundamental type globals were taken out of the global scope. Unfortunately my hooking of the allocation-related functions of the WinAPI causes the hooking logic to crash. This is the next thing to figure out.
Patashu
He/Him
Joined: 10/2/2005
Posts: 4043
Good luck! This looks like a tough nut to crack.
My Chiptune music, made in Famitracker: http://soundcloud.com/patashu My twitch. I stream mostly shmups & rhythm games http://twitch.tv/patashu My youtube, again shmups and rhythm games and misc stuff: http://youtube.com/user/patashu
Editor, Experienced player (570)
Joined: 11/8/2010
Posts: 4036
Yes, good luck! And thank you for explaining the current state of the project to us. Your efforts are appreciated!
Editor, Expert player (2478)
Joined: 4/8/2005
Posts: 1573
Location: Gone for a year, just for varietyyyyyyyyy!!
Thank you and good luck!
Sonia
She/Her
Joined: 12/6/2013
Posts: 435
Location: Brazil
The whole thing sounds rather complicated. I wish you good luck and thank you! It's exciting to see Hourglass isn't dead yet.
Post subject: Update: When the storm struck the house of cards
Warepire
He/Him
Editor
Joined: 3/2/2010
Posts: 2178
Location: A little to the left of nowhere (Sweden)
After a few rewrites and bug corrections, I can finally hook (most of) the allocation related WinAPI calls. In a typical movie when everything is going well, the cliche storm that throws everything upside down hits. This tale is no different. So, it turns out that one of the very few DLLs that are loaded before the DLLs in the IAT (a table inside the executable telling Windows which dependencies the application has), creates a heap. So, there are heaps already before I start hooking, that's not good... Because in order to properly handle the hooks, I reject every unknown heap-handle, as it SHOULD be invalid. That is however not the case, and these DLLs do not handle AT ALL that an allocation may fail, resulting in every hook after HeapAlloc failing because we keep telling some DLL their heap-handle is bad. And there the house of cards fell apart... Now: How does one handle this...?
Player (16)
Joined: 7/3/2012
Posts: 35
Not sure how relevant to the topic this is, but I would like to point out that a Japanese TASer has made a version of Hourglass which saves savestates to disk.
Warepire
He/Him
Editor
Joined: 3/2/2010
Posts: 2178
Location: A little to the left of nowhere (Sweden)
The biggest problem right now is that save-states aren't re-useable between sessions, meaning that once Hourglass is shut down, the save-states are just junk. The only gain is that Hourglass uses less RAM. This is however a planned improvement for the future, when save-states hopefully have been made cross-session. Making save-states cross-session is not a small task.
Skilled player (1741)
Joined: 9/17/2009
Posts: 4981
Location: ̶C̶a̶n̶a̶d̶a̶ "Kanatah"
I'm not sure where else to post this, but since it's relevant to savestates: 1. For games like Undertale, it constantly updates files regarding player choices, so if a savestate that doesn't revert those, it would cause desync. 2. Said game (and probably others. maybe) also forcibly closes the game after a certain point (and required to reopen to progress). In other words, the savestates go stale. Also the game would update said files from above, so simply reopening it would cause desyncs. Not sure how would these problems be tackled, but I hope you find a way.
Experienced player (588)
Joined: 2/5/2011
Posts: 1417
Location: France
Noice! Good luck with this, I'd like to see a TAS of Undertale
Current: Rayman 3 maybe? idk xD Paused: N64 Rayman 2 (with Funnyhair) GBA SMA 4 : E Reader (With TehSeven) TASVideos is like a quicksand, you get in, but you cannot quit the sand