16099 shaares
https://twitter.com/Enichan/status/1190745186705514503
@Enichan :
« This is a big topic so this'll probably be a bit of a thread
Save games are hard primarily because modern programming doesn't just let you take chunks of raw bytes from RAM, write them to disk, then later put them back and have the same state you had before
In fact modern languages/OSes mostly don't let you access memory in this way at all. This approach is how emulator savestates work: they just dump all the working memory to disk, and then reload it, and it basically restores the state of the entire machine.
So instead you get what's called "serialization". Serialization roughly means taking an object that exists in memory and manually (or semi-automatically) converting that into a set of primitive values like numbers, true/false, and text.
Primitive values are unique in that they're all stand-alone, i.e. they do not refer to other objects. A number is just a number, a string is just a string. They don't rely on some other object to have a complete and usable state.
The problem is that in a game you always have references between objects. A monster might have an aggro target, that's a reference to another object. Sometimes these object references create complex interlocking graphs, and those become super difficult to save and load. For example, MidBoss works around this by giving every entity in the game a unique numeric identifier. References between game entities only hold this number, and every time you want the actual object, it has to look it up in a table of number->object values. That way when you save a reference to another object, you only have to save the number, and after load things will mostly just work automatically after the lookup table is repopulated
The problem is that if you want to have control over your serialization (which is required for it to be secure!) the simplest way is to write code so that every value on every object that has to be saved is converted into a primitive type on save and converted back on load. Needless to say this is a TON of work! And it can lead to human error, especially in systems with lots of complex interlocking references, which could mean savegames become corrupt and unloadable entirely
There are serialization schemes built on top of the primitive values that I mentioned earlier, like the COM interop system Untitled Goose Game uses, which try to automate as much of this process as possible, but results can be unpredictable and not secure
And a lot of these systems aren't foolproof either. Imagine objects A and B. If A only references B, everything is cool. But if B also references A, that becomes a problem. You can't naively store a copy of B's data in A, because that system would then put a copy of A into B, and then another copy of B into the copy of A, ad infinitum until this circular reference crashes everything. That's just one example of things that can go wrong.
In Untitled Goose Game the system they used will for the most part create ANY object in the .NET framework
regardless of whether it has anything to do with the game or not. That's the downside of making serialization easier, it becomes more general and so has less constraints on what it can do.
The attacker exploited this to create an ad-hoc function that pointed to multiple other ad-hoc functions and exploited a quirk of .NET where if you do this, these functions can return a different value than expected, which you can then use.
The way around this is to confine what types can be created by the deserialization process, which was the fix, but this again means more work for the developer.
The only secure way to use untrusted user data (files, data from over the network) is to do it the hard way.
The hard way means writing custom code for everything in your game to turn complex object graphs into collections of primitive values when you save, and then rebuilding the complex object graph when you load. And it's a pain.
There's loads of other issues with savegames too like making sure if you get a power cut or a crash mid-save you don't corrupt the user's previous saved state on disk which I could do a whole thread about (if you're interested google ACID) »
@TheDStecks
« Honestly this is why implementing saves as checkpoint-based in closed-off rooms is just so much less of a headache. Also something that can make roguelikes appealing to make.
Yeah, I bet you'd like to have a quicksave, but I'd also like to be able to release this fucking game »
@Enichan :
« This is correct. If all you have to save is the player's stats and inventory and what position on what map they're on things become super easy. This is why you see games like Shadowrun Returns and Underworld Ascendant launch with only checkpoint saves
Adding save-anywhere to a game that wasn't built for it means retrofitting EVERYTHING with serialization and it's usually a giant pain in the ass, like multiplayer support it's really something you wanna plan for from the very start of the project
Interesting additional note: some old languages like QBasic are designed in a way that everything is a value type*, and objects can never have references to other objects. The lack of complex object graphs allowed them to implement automatic memory management on weak hardware.
(*QBasic has strings which are technically references but they are immutable, which effectively makes them a primitive, and arrays, but all arrays are arrays of primitives. Also custom value types are always made up of primitives, or fixed length arrays and strings)
(Fixed length arrays and strings are simply collections of N primitives and so are also not reference types) »
@JifMOD :
« Oh, is *that* why so many modern games seem to have an awful "save the game state but not really" checkpoint system? I thought it was the in-vogue design choice or a console cert thing, not a technical limitation! »
@Enichan :
« totally a tech limitation! :D »
@Enichan :
« This is a big topic so this'll probably be a bit of a thread
Save games are hard primarily because modern programming doesn't just let you take chunks of raw bytes from RAM, write them to disk, then later put them back and have the same state you had before
In fact modern languages/OSes mostly don't let you access memory in this way at all. This approach is how emulator savestates work: they just dump all the working memory to disk, and then reload it, and it basically restores the state of the entire machine.
So instead you get what's called "serialization". Serialization roughly means taking an object that exists in memory and manually (or semi-automatically) converting that into a set of primitive values like numbers, true/false, and text.
Primitive values are unique in that they're all stand-alone, i.e. they do not refer to other objects. A number is just a number, a string is just a string. They don't rely on some other object to have a complete and usable state.
The problem is that in a game you always have references between objects. A monster might have an aggro target, that's a reference to another object. Sometimes these object references create complex interlocking graphs, and those become super difficult to save and load. For example, MidBoss works around this by giving every entity in the game a unique numeric identifier. References between game entities only hold this number, and every time you want the actual object, it has to look it up in a table of number->object values. That way when you save a reference to another object, you only have to save the number, and after load things will mostly just work automatically after the lookup table is repopulated
The problem is that if you want to have control over your serialization (which is required for it to be secure!) the simplest way is to write code so that every value on every object that has to be saved is converted into a primitive type on save and converted back on load. Needless to say this is a TON of work! And it can lead to human error, especially in systems with lots of complex interlocking references, which could mean savegames become corrupt and unloadable entirely
There are serialization schemes built on top of the primitive values that I mentioned earlier, like the COM interop system Untitled Goose Game uses, which try to automate as much of this process as possible, but results can be unpredictable and not secure
And a lot of these systems aren't foolproof either. Imagine objects A and B. If A only references B, everything is cool. But if B also references A, that becomes a problem. You can't naively store a copy of B's data in A, because that system would then put a copy of A into B, and then another copy of B into the copy of A, ad infinitum until this circular reference crashes everything. That's just one example of things that can go wrong.
In Untitled Goose Game the system they used will for the most part create ANY object in the .NET framework
regardless of whether it has anything to do with the game or not. That's the downside of making serialization easier, it becomes more general and so has less constraints on what it can do.
The attacker exploited this to create an ad-hoc function that pointed to multiple other ad-hoc functions and exploited a quirk of .NET where if you do this, these functions can return a different value than expected, which you can then use.
The way around this is to confine what types can be created by the deserialization process, which was the fix, but this again means more work for the developer.
The only secure way to use untrusted user data (files, data from over the network) is to do it the hard way.
The hard way means writing custom code for everything in your game to turn complex object graphs into collections of primitive values when you save, and then rebuilding the complex object graph when you load. And it's a pain.
There's loads of other issues with savegames too like making sure if you get a power cut or a crash mid-save you don't corrupt the user's previous saved state on disk which I could do a whole thread about (if you're interested google ACID) »
@TheDStecks
« Honestly this is why implementing saves as checkpoint-based in closed-off rooms is just so much less of a headache. Also something that can make roguelikes appealing to make.
Yeah, I bet you'd like to have a quicksave, but I'd also like to be able to release this fucking game »
@Enichan :
« This is correct. If all you have to save is the player's stats and inventory and what position on what map they're on things become super easy. This is why you see games like Shadowrun Returns and Underworld Ascendant launch with only checkpoint saves
Adding save-anywhere to a game that wasn't built for it means retrofitting EVERYTHING with serialization and it's usually a giant pain in the ass, like multiplayer support it's really something you wanna plan for from the very start of the project
Interesting additional note: some old languages like QBasic are designed in a way that everything is a value type*, and objects can never have references to other objects. The lack of complex object graphs allowed them to implement automatic memory management on weak hardware.
(*QBasic has strings which are technically references but they are immutable, which effectively makes them a primitive, and arrays, but all arrays are arrays of primitives. Also custom value types are always made up of primitives, or fixed length arrays and strings)
(Fixed length arrays and strings are simply collections of N primitives and so are also not reference types) »
@JifMOD :
« Oh, is *that* why so many modern games seem to have an awful "save the game state but not really" checkpoint system? I thought it was the in-vogue design choice or a console cert thing, not a technical limitation! »
@Enichan :
« totally a tech limitation! :D »
^^
https://twitter.com/AshAgony/status/1190386683327451136
(NB : FTP = Fuck The Police)
(NB : FTP = Fuck The Police)
Je viens de retrouver ça dans mes marque-pages (nov 2016).
J'ai pas trouvé le jeu sur Steam ou Itch, mais ça a pas l'air mort, ya de l'activité sur leur compte twitter (https://twitter.com/TavernKeeper) et :
« Tavern Keeper is currently at the internal Alpha stage and, while it’s too early to talk about a release date, we’re aiming to get to a solid Beta stage sometime in 2019. » (https://tavernkeeper.com/)
J'ai pas trouvé le jeu sur Steam ou Itch, mais ça a pas l'air mort, ya de l'activité sur leur compte twitter (https://twitter.com/TavernKeeper) et :
« Tavern Keeper is currently at the internal Alpha stage and, while it’s too early to talk about a release date, we’re aiming to get to a solid Beta stage sometime in 2019. » (https://tavernkeeper.com/)
« This is the last part of the Studio Ghibli miniseries, and I decided to reinterpret a moment from Princess Mononoke. »
https://twitter.com/snatti/status/1190283144110338048
https://twitter.com/snatti/status/1190283144110338048