Use savefiles in newer builds

brandos

Veteran
Veteran
Joined
May 25, 2013
Messages
147
Reaction score
31
First Language
German
Primarily Uses
Is there a way to accomplish this? When I use an older savefile for a new build the game just freezes when I try to load it.
 

TheoAllen

Self-proclaimed jack of all trades
Veteran
Joined
Mar 16, 2012
Messages
5,592
Reaction score
6,522
First Language
Indonesian
Primarily Uses
RMVXA
What do you mean by freeze?

is the game.exe just not responding?

or display blank screen?

or poped up an error notice?
 

Hudell

Dog Lord
Veteran
Joined
Oct 2, 2014
Messages
3,545
Reaction score
3,715
First Language
Java's Crypt
Primarily Uses
RMMZ
What did you change in the game since the build that save file was generated?
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,268
Reaction score
1,422
First Language
English
Primarily Uses
RMXP
In general? No.

In your specific case? Possibly.

Depending on your delta there may be missing knowledge you cannot determine in a deterministic fashion. This sort of problem should be fixed case-by-case.

*hugs*

 - Zeriab
 

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,268
Reaction score
1,422
First Language
English
Primarily Uses
RMXP
Take a look at what Zalerinian writes:

This script attempts to fix many issues that were caused by loading an old save when new scripts were installed. This script is NOT foolproof, but it will prevent a large number of errors from occurring.
"This script is NOT foolproof," <-- I.e. does not work in general.

On one hands help prevent crashing, on the other hand may simply hide the symptoms of data errors that could end up messing later.

In some ways it is a bit like fixing the NoMethodError for nil:NilClass by using the following code:

class NilClass  def method_missing(*args)    nil  endendIt might mean that for some cases the game would continue run just fine where as normally the game would crash. Other cases it leads to weird and subtle errors. Debugging such errors are way harder.

*hugs*

 - Zeriab
 
Last edited by a moderator:

TheoAllen

Self-proclaimed jack of all trades
Veteran
Joined
Mar 16, 2012
Messages
5,592
Reaction score
6,522
First Language
Indonesian
Primarily Uses
RMVXA
Not sure what's the foolproof means

But the script fixes most of case where the general cause of error is because of undefined variable / method for nilClass since most of the scripts added new variables during the object initalization. I think, that counts as 'general'
 

Zalerinian

Jack of all Errors
Veteran
Joined
Dec 17, 2012
Messages
4,696
Reaction score
935
First Language
English
Primarily Uses
N/A
Now, you're correct Theo, it does add in any default variables that were missing, however I have to also agree with Zeriab.

When I first released the script, it worked seemingly perfectly. New variables started popping up in the old files. It was great for what it was designed for, a general fix. However, issues soon started to arise. Turns out that the original script destroys any equips the player has, probably resets their skills and inventory. To be honest, I still haven't fixed these issues, they STILL happen. Its clear to me that the original method I was using will jot do the job we need.

I have prototyped a new version, but it needs to get cleaned up. I suppose I may be able to work some on it recently since I'm off work for the holidays, but to truly get the issue fixed, every object needs to be recursively rebuilt. Without doing that, we cannot be guaranteed that ALL the old data was preserved.

I'll see if I can get a good, working RC for version 2. If so, it will certainly be a good addition to any project. I'll keep you posted.
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,846
First Language
English
When I first released the script, it worked seemingly perfectly. New variables started popping up in the old files. It was great for what it was designed for, a general fix. However, issues soon started to arise. Turns out that the original script destroys any equips the player has, probably resets their skills and inventory. To be honest, I still haven't fixed these issues, they STILL happen. Its clear to me that the original method I was using will jot do the job we need.
I'm under the impression that you're just copying over values that exist in a new object, but do not exist in the save file. If it already exists in the save file, then there is no need to copy it over.


Unless that's the purpose of the script: re-initializing the object rather than just initializing missing variables.
 
Last edited by a moderator:

Zalerinian

Jack of all Errors
Veteran
Joined
Dec 17, 2012
Messages
4,696
Reaction score
935
First Language
English
Primarily Uses
N/A
I'm under the impression that you're just copying over values that exist in a new object, but do not exist in the save file. If it already exists in the save file, then there is no need to copy it over.

Unless that's the purpose of the script: re-initializing the object rather than just initializing missing variables.
It creates a new object and copies values from the old one to the new one. It has some issues, especially with things like followers. They're stored, essentially, in an array in the game player class. When we copy the old followers over, they're still trying to follow the old Game_Player object that exists in that array, breaking them. They'll only move onto the spot that the player was on when the game was saved since we no longer control that player, but it is still referenced in that array.
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,846
First Language
English
It creates a new object and copies values from the old one to the new one. It has some issues, especially with things like followers. They're stored, essentially, in an array in the game player class. When we copy the old followers over, they're still trying to follow the old Game_Player object that exists in that array, breaking them. They'll only move onto the spot that the player was on when the game was saved since we no longer control that player, but it is still referenced in that array.
That's something new to me.


When the follower object is created, a reference to the game player is passed in and stored in the array as you have mentioned.


I would have expected ruby to properly serialize that as a reference instead of a full dump of the object, essentially resulting in two separate player objects.
 

♥SOURCE♥

Too sexy for your party.
Veteran
Joined
Mar 14, 2012
Messages
693
Reaction score
411
Primarily Uses
I had this lying around (paste above Main and below all other custom scripts):

#==============================================================================# ** MakerSystems#------------------------------------------------------------------------------# #==============================================================================module MakerSystems #============================================================================ # ** SaveFileDoctor #---------------------------------------------------------------------------- # #============================================================================ module SaveFileDoctor #---------------------------------------------------------------------- # * Heal. [NEW] #---------------------------------------------------------------------- def self.heal(contents) # Will keep track of repaired objects to avoid a stack overflow. @ms_save_file_doctor_ids = [] # Goes through each object in contents hash. contents.each_value do |patient| # Updated version of this Object. current = patient.class.new # Fixes this (loaded) Object with the help of its updated version. fix_object(patient, current) # Starts the healing process for each instance variable. child_health(object_data(patient), object_data(current)) end # No need to keep the objects ids in memory. @ms_save_file_doctor_ids = nil end #---------------------------------------------------------------------- # * Enumerable Child Processing. [NEW] #---------------------------------------------------------------------- def self.enum_child(patient, current) # Iterates loaded version and updated version as a sequence. patient.zip(current).each do |p_value, c_value| # If Enumerable, there might be instanceable Objects inside it. enum_child(p_value.to_a, c_value.to_a) if Enumerable === p_value # Ignore if it can't be instanced. next unless p_value.class.respond_to?:)new) # Stop if this Object was already healed to avoid a stack overflow. return if @ms_save_file_doctor_ids.include?(p_value.__id__) # Saves current Object id. @ms_save_file_doctor_ids << p_value.__id__ # Fixes this Object with the help of its updated version. fix_object(p_value, c_value) # Healing process for each instance variable of this Object. child_health(object_data(p_value), object_data(c_value)) end end #---------------------------------------------------------------------- # * Object Data. [NEW] #---------------------------------------------------------------------- def self.object_data(object) # Returns a hash with each instance variable name and value. Hash[ object.instance_variables.map do |name| [name, object.instance_variable_get(name)] end ] end #---------------------------------------------------------------------- # * Fix Object. [NEW] #---------------------------------------------------------------------- def self.fix_object(patient, current) # Adds new data to loaded Object's data target. data = object_data(current).merge(object_data(patient)) # Uses the correct data target to set each instance variable. data.each { |name, value| patient.instance_variable_set(name, value) } end #---------------------------------------------------------------------- # * Child Health. [NEW] #---------------------------------------------------------------------- def self.child_health(patient, current) # Goes through each instance variable. patient.each do |name, value| # If Enumerable, there might be instanceable Objects inside it. enum_child(value.to_a, current[name].to_a) if Enumerable === value # Ignore if it can't be instanced. next unless value.class.respond_to?:)new) # Stop if this Object was already healed to avoid a stack overflow. return if @ms_save_file_doctor_ids.include?(value.__id__) # Saves current Object id. @ms_save_file_doctor_ids << value.__id__ # Fixes this Object with the help of its updated version. fix_object(value, current[name]) # Healing process for each instance variable of this Object. child_health(object_data(value), object_data(current[name])) end end end end#==============================================================================# ** DataManager#------------------------------------------------------------------------------# This module manages the database and game objects. Almost all of the # global variables used by the game are initialized by this module.#==============================================================================module DataManager class << self #------------------------------------------------------------------------ # * Alias Extract Save Contents. [NEW] #------------------------------------------------------------------------ alias_method:)ms_save_file_doctor_original_extract_save_contents, :extract_save_contents) #------------------------------------------------------------------------ # * Extract Save Contents. [MOD] #------------------------------------------------------------------------ def extract_save_contents(contents) # Healthcare. Object repairing is handled at a local level. MakerSystems::SaveFileDoctor.heal(contents) # Proceeds with the original method, using repaired contents. ms_save_file_doctor_original_extract_save_contents(contents) end end endIt should fix the vast majority of errors caused by adding a script and attempting to load a savefile created before such addition.

Save File Doctor has no known bugs.

Will make a proper topic later.[/MOD]
 

brandos

Veteran
Veteran
Joined
May 25, 2013
Messages
147
Reaction score
31
First Language
German
Primarily Uses
I had this lying around (paste above Main and below all other custom scripts):

#==============================================================================# ** MakerSystems#------------------------------------------------------------------------------# #==============================================================================module MakerSystems #============================================================================ # ** SaveFileDoctor #---------------------------------------------------------------------------- # #============================================================================ module SaveFileDoctor #---------------------------------------------------------------------- # * Heal. [NEW] #---------------------------------------------------------------------- def self.heal(contents) # Will keep track of repaired objects to avoid a stack overflow. @ms_save_file_doctor_ids = [] # Goes through each object in contents hash. contents.each_value do |patient| # Updated version of this Object. current = patient.class.new # Fixes this (loaded) Object with the help of its updated version. fix_object(patient, current) # Starts the healing process for each instance variable. child_health(object_data(patient), object_data(current)) end # No need to keep the objects ids in memory. @ms_save_file_doctor_ids = nil end #---------------------------------------------------------------------- # * Enumerable Child Processing. [NEW] #---------------------------------------------------------------------- def self.enum_child(patient, current) # Iterates loaded version and updated version as a sequence. patient.zip(current).each do |p_value, c_value| # If Enumerable, there might be instanceable Objects inside it. enum_child(p_value.to_a, c_value.to_a) if Enumerable === p_value # Ignore if it can't be instanced. next unless p_value.class.respond_to?:)new) # Stop if this Object was already healed to avoid a stack overflow. return if @ms_save_file_doctor_ids.include?(p_value.__id__) # Saves current Object id. @ms_save_file_doctor_ids << p_value.__id__ # Fixes this Object with the help of its updated version. fix_object(p_value, c_value) # Healing process for each instance variable of this Object. child_health(object_data(p_value), object_data(c_value)) end end #---------------------------------------------------------------------- # * Object Data. [NEW] #---------------------------------------------------------------------- def self.object_data(object) # Returns a hash with each instance variable name and value. Hash[ object.instance_variables.map do |name| [name, object.instance_variable_get(name)] end ] end #---------------------------------------------------------------------- # * Fix Object. [NEW] #---------------------------------------------------------------------- def self.fix_object(patient, current) # Adds new data to loaded Object's data target. data = object_data(current).merge(object_data(patient)) # Uses the correct data target to set each instance variable. data.each { |name, value| patient.instance_variable_set(name, value) } end #---------------------------------------------------------------------- # * Child Health. [NEW] #---------------------------------------------------------------------- def self.child_health(patient, current) # Goes through each instance variable. patient.each do |name, value| # If Enumerable, there might be instanceable Objects inside it. enum_child(value.to_a, current[name].to_a) if Enumerable === value # Ignore if it can't be instanced. next unless value.class.respond_to?:)new) # Stop if this Object was already healed to avoid a stack overflow. return if @ms_save_file_doctor_ids.include?(value.__id__) # Saves current Object id. @ms_save_file_doctor_ids << value.__id__ # Fixes this Object with the help of its updated version. fix_object(value, current[name]) # Healing process for each instance variable of this Object. child_health(object_data(value), object_data(current[name])) end end end end#==============================================================================# ** DataManager#------------------------------------------------------------------------------# This module manages the database and game objects. Almost all of the # global variables used by the game are initialized by this module.#==============================================================================module DataManager class << self #------------------------------------------------------------------------ # * Alias Extract Save Contents. [NEW] #------------------------------------------------------------------------ alias_method:)ms_save_file_doctor_original_extract_save_contents, :extract_save_contents) #------------------------------------------------------------------------ # * Extract Save Contents. [MOD] #------------------------------------------------------------------------ def extract_save_contents(contents) # Healthcare. Object repairing is handled at a local level. MakerSystems::SaveFileDoctor.heal(contents) # Proceeds with the original method, using repaired contents. ms_save_file_doctor_original_extract_save_contents(contents) end end endIt should fix the vast majority of errors caused by adding a script and attempting to load a savefile created before such addition.

Save File Doctor has no known bugs.

Will make a proper topic later.

[/MOD]
[MOD]
Thank you very much. I'll test it and post the results later next week.[/mod]
 

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,098
Reaction score
13,704
First Language
English
Primarily Uses
RMMV
Since this discussion is all about scripting, I'm going to move this into RGSSx Script Support.
 

Galenmereth

Retired
Veteran
Joined
May 15, 2013
Messages
2,248
Reaction score
2,158
First Language
English
Primarily Uses
N/A
Isn't the problem usually that people save a serialized Hash directly, instead of making a wrapper / dedicated object for it instead? For example, even though the list of creatures in my game is stored in a Hash, I use a Creature object to access the data for each creature. I then access the creatures Hash only by using the methods on the Creature object, and thus if I change things later in a patch (like the data structure of the creatures Hash), I can build in backwards support into the Creature object's access methods.
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,846
First Language
English
Isn't the problem usually that people save a serialized Hash directly, instead of making a wrapper / dedicated object for it instead? For example, even though the list of creatures in my game is stored in a Hash, I use a Creature object to access the data for each creature. I then access the creatures Hash only by using the methods on the Creature object, and thus if I change things later in a patch (like the data structure of the creatures Hash), I can build in backwards support into the Creature object's access methods.
The problem usually stems from a lot of things, but in general it's the assumption that it is safe for required data to be initialized at certain points in time (eg: on start up)


Doesn't matter if it's a hash or some other object: if you assume that data will always be there when you need it, then you probably better have code available to make sure that assumption is true.


The spectrum basically extends from "load on-demand" to "load on-init". On one end, you assume old data is likely obsolete or non-existent and always force the engine to work hard and get you fresh data. On the other end you just let the engine load it at the very beginning and then you trust that the engine holds its end of the bargain.


Of course, enter save files and that contract is already pretty hard to maintain, especially if the save file was created before a contract was even made (can't blame someone for not doing something they were never around for).


This is why I typically go with lazy-loading which is a somewhat good compromise in the middle.


Given the nature of RPG Maker (introduction of new scripts, dynamic objects, how the database works with regards to initialization, etc) I think it's better to either stick with lazy-loading or just loading whenever data is needed unless you absolutely can't (eg: expensive processes that will just destroy perfromance).


Assuming data will not be there (whether it's out-dated or not) when you need it is probably one of the only fool-proof ways to avoid breaking compatibility with old saves without forcing the end user to do lots of work like providing their own save file patching solutions


However, solutions such as the copying data over from old data to new data would cover all cases. The question then becomes whether the copy is done correctly (and completely).


If some objects have not been updated properly, then the copy likely wasn't done correctly.


Got a hash? You need to examine every key-value pair.


Got an array? You need to examine every element.


Custom objects? You'll have to figure out how to examine them in the specified way. Those tend to be composed of a bunch of standard objects, so a little recursion does the trick. Most of the time. Try to copy a Color or a Tone or a Table. Instance variables are not going to help you. Solution? Please don't save them if you don't have to, or save them in such a way that you don't rely on other scripts knowing how to load them.


It is possible to write a script to cover standard objects and objects provided in the library, but if someone comes up with some perverse object that simply doesn't follow any of the rules...well, need to add a new case for that. Hopefully the copy script is written flexibly enough for that.
 
Last edited by a moderator:

Zeriab

Huggins!
Veteran
Joined
Mar 20, 2012
Messages
1,268
Reaction score
1,422
First Language
English
Primarily Uses
RMXP
So much effort spent on making the game humping along rather than simply crashing.

For all we know simply initializing data structures in the middle of a game could lead to a soft lock a couple of hours later.

Do the smart thing. Version your save games. Makes it so much easier to manage when you have to create a new save file version.

Feel free to base your code on something I created 5 years ago: (RMXP)

### Save_Info is a header of a save file describing properties of the save and# contains the data necessary for presentation in the save/load menu.# The idea is to load as little as possible of the save file for presentation.# The header should therefore be as little as possible.#class Save_Info  # Info about the game which created the save  attr_accessor :game  class Game    attr_accessor :game         # Shows which game it is    attr_accessor :version      # The version of the game used to save the file    attr_accessor :magic_number # Magic number of the save    attr_accessor :misc         # Miscellaneous data  end   # A summary of the save for presentation in the save menu  attr_accessor :summary  class Summary    attr_accessor :playing_time # Playing time    attr_accessor :characters   # Characters in party    attr_accessor :switches     # Game switches    attr_accessor :variables    # Game variables    attr_accessor :misc         # Miscellaneous data  end   # A miscellaneous accessor, just in case  attr_accessor :miscend*hugs*

 - Zeriab
 
Last edited by a moderator:

Zalerinian

Jack of all Errors
Veteran
Joined
Dec 17, 2012
Messages
4,696
Reaction score
935
First Language
English
Primarily Uses
N/A
However, solutions such as the copying data over from old data to new data would cover all cases. The question then becomes whether the copy is done correctly (and completely).

If some objects have not been updated properly, then the copy likely wasn't done correctly.

Got a hash? You need to examine every key-value pair.

Got an array? You need to examine every element.

Custom objects? You'll have to figure out how to examine them in the specified way. Those tend to be composed of a bunch of standard objects, so a little recursion does the trick. Most of the time. Try to copy a Color or a Tone or a Table. Instance variables are not going to help you. Solution? Please don't save them if you don't have to, or save them in such a way that you don't rely on other scripts knowing how to load them.

It is possible to write a script to cover standard objects and objects provided in the library, but if someone comes up with some perverse object that simply doesn't follow any of the rules...well, need to add a new case for that. Hopefully the copy script is written flexibly enough for that.
This is exactly why my script currently does not work. It didn't go into hashes and arrays and such to rebuild everything, it rebuilt the $game_blarg objects that were stored in the save file hash. If we look at what Galenmereth is saying,

Isn't the problem usually that people save a serialized Hash directly, instead of making a wrapper / dedicated object for it instead? For example, even though the list of creatures in my game is stored in a Hash, I use a Creature object to access the data for each creature. I then access the creatures Hash only by using the methods on the Creature object, and thus if I change things later in a patch (like the data structure of the creatures Hash), I can build in backwards support into the Creature object's access methods.
my script would completely break if there were mandatory changes to the Creature class. Sure, the hash would be brought over with all the creature objects, but the creature objects were not remade, therefore they don't have the new data they need. A proper fix needs to recursively check, which Save File Doctor seems to do properly. Granted I didn't do in-depth testing, it did fix the lack of a required instance variable, and it did fix followers properly.

At this point, it seems Source's solution is the best with Save File Doctor.
 

Galenmereth

Retired
Veteran
Joined
May 15, 2013
Messages
2,248
Reaction score
2,158
First Language
English
Primarily Uses
N/A
All you need is actually just checking whether a variable exists or not in your code, and have fallbacks for nil cases. How you implement that is the question. But it's usually bad to let a nil value break your code; there should always be fallbacks. If you have this all throughout your classes and modules, you can usually alleviate most of these issues. But like Zeriab mentioned, there might be a time where you do need to update old data from a specific save version, so keeping track of that will certainly make your job a lot easier.
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,846
First Language
English
All you need is actually just checking whether a variable exists or not in your code, and have fallbacks for nil cases. How you implement that is the question. But it's usually bad to let a nil value break your code; there should always be fallbacks. If you have this all throughout your classes and modules, you can usually alleviate most of these issues. But like Zeriab mentioned, there might be a time where you do need to update old data from a specific save version, so keeping track of that will certainly make your job a lot easier.
Data from a specific save version MAY become an issue, but the only reason I can think of is that patching methods have changed between different versions.


Maybe you had to add 10 to version 1, but the script changed in version 2 so you only had to add 5.


I can't think of any practical reasons for this though.
 

Users Who Are Viewing This Thread (Users: 0, Guests: 1)

Latest Threads

Latest Posts

Latest Profile Posts

Couple hours of work. Might use in my game as a secret find or something. Not sure. Fancy though no? :D
Holy stink, where have I been? Well, I started my temporary job this week. So less time to spend on game design... :(
Cartoonier cloud cover that better fits the art style, as well as (slightly) improved blending/fading... fading clouds when there are larger patterns is still somewhat abrupt for some reason.
Do you Find Tilesetting or Looking for Tilesets/Plugins more fun? Personally I like making my tileset for my Game (Cretaceous Park TM) xD
How many parameters is 'too many'??

Forum statistics

Threads
105,862
Messages
1,017,049
Members
137,569
Latest member
Shtelsky
Top