RunUO Community

This is a sample guest message. Register a free account today to become a member! Once signed in, you'll be able to participate on this site by adding your own topics and posts, as well as connect with other members through your own private inbox!

Serialization

krrios

Administrator
Serialization

To save and load the world, RunUO uses serialization. Every item or mobile you script is required to have three different methods associated with that.

The first is very simple, a special serialization "constructor" called when the object is being loaded from the save files. Usually you can use a simple prototype:

Code:
public MyClassName( Serial serial ) : base( serial )
{
}

All this does is call your parent class' serialization constructor.

The next two methods are for serializing and deserializing (saving and loading). These methods have one parameter each, a GenericWriter and GenericReader. They provide methods for reading and writing any data you have.

Code:
public override void Serialize( GenericWriter writer )
{
base.Serialize( writer );
 
writer.Write( (int) 0 ); // version
}
 
public override void Deserialize( GenericReader reader )
{
base.Deserialize( reader );
 
int version = reader.ReadInt();
}

For the simplest of scripts, where no fields are serialized, that's all you need. You can see a "version" number is serialized; this is for backwards compatibility. If you ever modify the fields in your script, old worldfiles can still work.

Here's an example for two fields, a string and a number.

Code:
public override void Serialize( GenericWriter writer )
{
base.Serialize( writer );
 
writer.Write( (int) 0 ); // version
 
writer.Write( m_StringField );
writer.Write( m_NumberField );
}
 
public override void Deserialize( GenericReader reader )
{
base.Deserialize( reader );
 
int version = reader.ReadInt();
 
switch ( version )
{
case 0:
{
m_StringField = reader.ReadString();
m_NumberField = reader.ReadInt();
 
break;
}
}
}

The "switch ( version )" construct allows us to branch from version to version, allowing you to write less code. Here's how we would add another string field to the same script:

Code:
public override void Serialize( GenericWriter writer )
{
base.Serialize( writer );
 
writer.Write( (int) 1 ); // version
 
writer.Write( m_StringField2 );
 
writer.Write( m_StringField );
writer.Write( m_NumberField );
}
 
public override void Deserialize( GenericReader reader )
{
base.Deserialize( reader );
 
int version = reader.ReadInt();
 
switch ( version )
{
case 1:
{
m_StringField2 = reader.ReadString();
 
goto case 0;
}
case 0:
{
m_StringField = reader.ReadString();
m_NumberField = reader.ReadInt();
 
break;
}
}
}

The version number has been changed from 0 to 1, and the field has been added to the Serialize method. If this script loads an old world file (one with the version number of 0), it does not try to read anything for "m_StringField2".
 
Hmm - how do we do serialization for an entire object? Normally I'd flag the object with the [serialization] attribute rather than decompose it field by field like I see in the scripts.

I'm using structures and usage objects within my mobiles.
 

krrios

Administrator
The standard way to serialize custom classes is through similar Serialize/Deserialize methods.

Code:
public class MyClass
{
   private string m_Field1;
   private int m_Field2;
   ...
   public void Serialize( GenericWriter w )
   {
      w.Write( m_Field1 );
      w.Write( m_Field2 );
   }

   public MyClass( GenericReader r )
   {
      m_Field1 = r.ReadString();
      m_Field2 = r.ReadInt();
   }
}

....

public class MyMobile : Mobile
{
   private MyClass m_Class;
   ...
   public override void Serialize( GenericWriter w )
   {
      ...
      m_Class.Serialize( w );
   }

   public override void Deserialize( GenericReader r )
   {
      ...
      m_Class = new MyClass( r );
   }
}
 

mrkroket

Wanderer
As far as I know serialize & deserialize are used to save/load all objects of the server.

Serialize: Writes the data of an object to the savegame file.
Deserialize: Reads the data of an object from the savegame file.

The data you must save is all new variables you created on that class.

If you programmed incorrectly those, you'll get problems (reading error, incorrect data, etc.) when reinit your server from a savegame.
You must load the variables in the same order you save it, otherwise it will get errors.
Be careful when programming these two methods.

Hope it helps
 

SoulStone

Wanderer
krrios said:
The standard way to serialize custom classes is through similar Serialize/Deserialize methods.

[code:1]public class MyClass
{
private string m_Field1;
private int m_Field2;
...
public void Serialize( GenericWriter w )
{
w.Write( m_Field1 );
w.Write( m_Field2 );
}

public MyClass( GenericReader r )
{
m_Field1 = r.ReadString();
m_Field2 = r.ReadInt();
}
}

....

public class MyMobile : Mobile
{
private MyClass m_Class;
...
public override void Serialize( GenericWriter w )
{
...
m_Class.Serialize( w );
}

public override void Deserialize( GenericReader r )
{
...
m_Class = new MyClass( r );
}
}[/code:1]

What should I do to serialize/deserialize a class that uses MyClass in an ArrayList, like this:
[code:1]ArrayList mylist = new ArrayList();
mylist.Add( new MyClass( par1, par2 ) );[/code:1]
How could I serialize/deserialize mylist?

SoulStone.
 

Ceday

Page
there are some functions:
ReadItemList
ReadMobileList etc.

if there is a function like ReadClassList or something like that, you can use it. (i havent worked on runuo, so i dont know if it exists)

if there isnt any, you can do something like this:

Serialize:
[code:1]
w.Write((int)myList.Items.Count); //w is generic writer

for (int k=0; k<myList.Items.Count; k++)
{
(MyClass)myList[k].Serialize(w) }
[/code:1]

Deserialize:
[code:1]
int length=r.ReadInt(); //r is generic reader
for(int k=0; k<length; k++)
{
MyClass mc=new MyClass(r);
myList.Add(mc);
}
[/code:1]
 

roadmaster

Sorceror
Serialize / Deserialize

I have a Item that once equiped by a Mobile, starts a timer to do a check of the lightlevels.

so, how do i serialize this?

do i have to serialize the mobile, the item, and the timer? or just one or two of them?

and other than string and number fields how would you serialize and deserialize your data?

ie... if i have to serialize the timer how do i do it?

would i just simply add the timer start to serialize and deserialize, like so

Code:
m_DrowLightCheckTimer = new DrowLightCheckTimer( m, this ); 
m_DrowLightCheckTimer.Start();

or is there something more complicated to it?

I tried to serialize my item but it threw a compile error that i "used a class where a variable was expected".

Code:
writer.Write(DrowShadowTunic);

so i instead tried a reference to that item:

Code:
writer.Write(m_DrowShadowTunic);

it compiled fine but crashed and threw an exception Due to Bad Serialize.
Maybe the bad serialize is due to the fact that the variable m_DrowShadowTunic is not in the Deserialize method, my question there would be how would you deserialize a reference to an item? or can you serialize and deserialize a reference to an item.

I tried using:

Code:
m_DrowShadowTunic = reader.ReadItem();

but it shot me the error

Cannot implicitly convert type 'Server.Item' to 'Server.Items.DrowShadowTunic'

I am trying to learn these two methods but i just dont understand them completely yet.

I appreciate any help that anyone may give.

roadmaster :)
 

juani

Wanderer
maybe it would help i you understood what serialize does in low level...
as far as i understand serializing converts something into a series of 0s and 1s, so for example if you serialize the int 3 you would get something like 00000000000000000000000000000011 in memory/hard disk (wich is 3 in binary in 32 bit form).

when u serialize a 3 and a 1 u get:
00000000000000000000000000000011 (3)
00000000000000000000000000000001 (1) etc.

Now, we give the writer the int parameter so he knows in how many bytes it should write the info (integers have 32 bits i believe, thats why theres so many 0s), if instead of writer.write((int) 3). we put writer.write((byte) 3) (bytes have 8 bits instead of 32) you would get something like:
00000011 (3 in binary format in 8 bits)

thats for the writer.

the problem for the reader is that it doesnt really knows if with:
00000000000000000000000000000011
you meant 1 32 bit integer value of 3,
or 4 8 bit "byte" values (0,0,0,3).

Thats why we specify the type when we read.


trying to write or read directly a deathshroud for example is a problem because the writer doent knows how to convert it to binary, and the reader doenst knows how to read it.
you need to serialize each property 1 at a time making sure u read the same type of info in the same order, after all for the server your worldfile looks like: 10010100100010101101001001011101001001001000100001001100100100
:D

theres another way that involves letting c# convert the WHOLE object to memory in binary format and retrieve it and put it in memory "as it is" when deserialized... thats how its done mostly on normal programming, but for some reason its not used a lot for scripts.


Hope it helps!
if any of that is wrong i apologize, thats how i understand it.
 

i_am_neo

Wanderer
Good to know...

Since I haven't found it explained thoroughly in here and it might be really useful for new scripters...

If you just add some variables in the serialize and deserialize methods, the server still might not see the variables' values on existing mobiles / items / whatever and might want to delete them.

There's a simple solution to that.

If you have added new variables in the Serialize and Deserialize methods
eg.
Code:
(...)
[SIZE=2][COLOR=#0000ff]public [/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]override [/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]void[/COLOR][/SIZE][SIZE=2] Deserialize( GenericReader reader )[/SIZE]
[SIZE=2]{[/SIZE]
[SIZE=2][COLOR=#0000ff]base[/COLOR][/SIZE][SIZE=2].Deserialize( reader );[/SIZE]
[SIZE=2][COLOR=#0000ff]int[/COLOR][/SIZE][SIZE=2] version = reader.ReadInt();[/SIZE]
 
 
[SIZE=2][COLOR=#0000ff]switch[/COLOR][/SIZE][SIZE=2] ( version )[/SIZE]
[SIZE=2]{[/SIZE]
 
[SIZE=2][COLOR=#0000ff][SIZE=2][COLOR=#0000ff]#region[/COLOR][/SIZE][SIZE=2][COLOR=#000000] MyRegion[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]case[/COLOR][/SIZE][SIZE=2] 26:[/SIZE]
[SIZE=2]{[/SIZE]
[SIZE=2]var1 = reader.ReadLong();[/SIZE]
[SIZE=2]var2 = reader.ReadInt();[/SIZE]
var3 = reader.ReadString();
[SIZE=2][COLOR=#0000ff]goto[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]case[/COLOR][/SIZE][SIZE=2] 25;[/SIZE]
[SIZE=2]}[/SIZE]
[SIZE=2][COLOR=#0000ff]#endregion[/COLOR][/SIZE]
 
[SIZE=2][COLOR=#0000ff][COLOR=black](...)[/COLOR][/COLOR][/SIZE]
 
[COLOR=#0000ff][SIZE=2][SIZE=2][COLOR=#0000ff]public [/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]override [/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]void[/COLOR][/SIZE][SIZE=2] [COLOR=black]Serialize[/COLOR]( GenericWriter [COLOR=black]writer[/COLOR] )[/SIZE]
[SIZE=2]{[/SIZE]
 
[COLOR=black](...)[/COLOR]
 
[SIZE=2][COLOR=black]writer[/COLOR].Write( ([/SIZE][SIZE=2][COLOR=#0000ff]int[/COLOR][/SIZE][SIZE=2]) 26 ); [/SIZE][SIZE=2][COLOR=#008000]// VERSION![/COLOR][/SIZE]
 
 
 
[SIZE=2][COLOR=#0000ff]#region[/COLOR][/SIZE][SIZE=2][COLOR=#000000] MyRegion[/COLOR][/SIZE]
[SIZE=2][COLOR=black]writer[/COLOR].Write[COLOR=black]( ([/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]long[/COLOR][/SIZE][SIZE=2][COLOR=black])var1 );[/COLOR][/SIZE]
[SIZE=2][COLOR=black]writer[/COLOR].Write[COLOR=black]( ([/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]int[/COLOR][/SIZE][SIZE=2][COLOR=black])var2 );[/COLOR][/SIZE]
[SIZE=2][COLOR=black]writer[/COLOR].Write[COLOR=black]( ([/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]string[/COLOR][/SIZE][SIZE=2][COLOR=black])var3 );[/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]#endregion[/COLOR][/SIZE]
 
[COLOR=black](...)[/COLOR][/SIZE][/COLOR]

You should make sure the script doesn't lead to an error when an object without those properties (old object) is loaded.
An easy way to do that supposing you increased the version number when adding the new properties would be to those lines:
Code:
[SIZE=2][COLOR=#0000ff]#region [COLOR=black]MyRegion[/COLOR][/COLOR][/SIZE]
[SIZE=2][COLOR=#0000ff]if[/COLOR][/SIZE][SIZE=2] (version < 26)[/SIZE]
[SIZE=2]{[/SIZE]
[SIZE=2]var1 = [value 1];[/SIZE]
[SIZE=2]var2 = [value 2];[/SIZE]
[SIZE=2]var3 = "[value3]";[/SIZE]
[SIZE=2]}[/SIZE]
[SIZE=2][COLOR=#0000ff]#endregion[/COLOR][/SIZE]
in the Deserialize method right after the end of the switch function:
Code:
[SIZE=2][COLOR=#0000ff]case[/COLOR][/SIZE][SIZE=2] 0:[/SIZE]
[SIZE=2]{[/SIZE]
[SIZE=2][COLOR=#0000ff]break[/COLOR][/SIZE][SIZE=2];[/SIZE]
[SIZE=2]}[/SIZE]
[SIZE=2]}[/SIZE]

That should prevent the server from throwing any errors and deleting existing objects.

[Post inspired by Ki Frost]
 

Moroth

Wanderer
Patching scripts without versions to add new variables.

I've seen this issue time and time again. The solution that most people respond with is, "You have to wipe your server or old items". Well here is a very simple method to update items, that never included a "version" variable.

This will work anytime you need to update a script that does not already have a "Version Variable".
In this example, we will also demonstrate adding a "Version Variable" for easier future updates.


So, lets take a look at the code.
Serialize = "Writes the data"
Deserialize = "Reads the data"

Code:
		public override void Serialize( GenericWriter writer )
		{
			base.Serialize( writer );
		}

		public override void Deserialize( GenericReader reader )
		{
			base.Deserialize( reader );
		}

The reason we get an error and have to delete our objests is because, its trying to "Load" data that doesn't exist.

So How do we fix this?
Simple, we update the data it's trying to "load" before it loads the new format.

How is this done?
- Create the Serialize method that you want to "upgrade to".

Code:
		public override void Serialize( GenericWriter writer )
		{
			base.Serialize( writer );

			writer.Write( (int)1 ); // Version

			writer.Write( ContainerName );
		}

- Leave the Deserialize method the same, so it can load our exsiting data.
- Launch/Restart your server.
- It should load fine, once it's up - Shutdown w/save
- Make the changes to the Deserialize method, to support your new data structure.
Code:
public override void Deserialize( GenericReader reader )
		{
			base.Deserialize( reader );

			int version = reader.ReadInt();

			switch (version)
			{
				case 1: { ContainerName = reader.ReadString(); } goto case 0;
				case 0: {  } break;
			}
		}

- Launch your server again
- Done :) You now have a working upgrade to your script.
 

Vorspire

Knight
TIP: Where-ever possible, you should use the 'switch' statement to control your versions.
Code:
public override void Deserialize( GenericReader reader )
		{
			base.Deserialize( reader );

			int version = reader.ReadInt();

			switch (version)
			{
				case 1: { ContainerName = reader.ReadString(); } goto case 0;
				case 0: {  } break;
			}
		}
 

MarciXs

Sorceror
Thought id share.

Basically, i've seen scripts that use external files.. like some config files etc. While all of that could be done within the class itself.Using MemoryStream for example, or even XmlDocument object ...
Or by using even more complicated class. And to serialize it all using bytes and ints,bools is well possible but you lose time by working on the method. Like to go through every member of your DataSet for example...although you could use WriteXml ... and then just save as a string...
but anyways,

Complete script:
Do not use this method if you have types that are not marked as serializable.
Most of Runuo items,Mobiles etc. aren't,therefore. You should not use it to Serialize a List<Mobile>. It can be used for things like
DataSet or MemoryStream. And so on. Otherwise you'll crash the server and mess up your saves.

Code:
using System;
using Server;

namespace Server
{
    public class SerializeObject
    {
        public static void SerializeAsByteArray(object obj, GenericWriter writer)
        {
            if (obj == null)
                throw new ArgumentException("Serializing nothing?");

            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            // Create an memory stream
            byte[] data;
            using (System.IO.MemoryStream memStream = new System.IO.MemoryStream())
            {
                // serialize in it  Shame we don't have access to the GenericWrite base stream...
                serializer.Serialize(memStream, obj);
                // reset for loading.
                memStream.Position = 0;
                // now we'got the data serialized,
                data = new byte[memStream.Length];
                // load it back ..
                memStream.Read(data, 0, data.Length);
            }
                writer.Write(data.Length);  // length will say the data length to get back 
                for (int b0 = 0; b0 < data.Length; b0++)
                {
                    writer.Write(data[b0]); // that's the data...
                }

            
        }

        public static void SerializeAsString(object obj, GenericWriter writer)
        {
            if (obj == null)
                throw new ArgumentException("Serializing nothing?");

            // Create the serializer
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            // Create an memory stream
            string strData = String.Empty;
            using (System.IO.MemoryStream memStream = new System.IO.MemoryStream())
            {
                // serialize in it
                // Shame we don't have access to the GenericWrite base stream...
                serializer.Serialize(memStream, obj);
                // reset for loading.
                memStream.Position = 0;
                // now we'got the data serialized,
                byte[] data = new byte[memStream.Length];
                // load it back ..
                memStream.Read(data, 0, data.Length); 
                strData = BitConverter.ToString(data);
            }
            //convert to {byte}-{byte}-... string
            
            writer.Write(strData); // done
        }
    }

    public class DeserializeObject
    {
        public static object DeserializeFromByteArray(GenericReader reader)
        {
            // Read the data length that was written
            int dataLength = reader.ReadInt();
            // Create an byte[] that will contain it.
            byte[] data = new byte[dataLength];
            // for the data length that was written read all it in the byte[]
            for (int d0 = 0; d0 < data.Length; d0++)
                data[d0] = reader.ReadByte();
            // memoStream that will be used in deserialization
            object yourObject = null;
            using (System.IO.MemoryStream memoStream = new System.IO.MemoryStream())
            {
                memoStream.Write(data, 0, data.Length);
                // set the position back
                memoStream.Position = 0;
                // deserialize
                System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserialize = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                // get what we need.
                yourObject = deserialize.Deserialize(memoStream);
            }
            // send it off.
            return yourObject;
        }
        public static object DeserializeFromString(GenericReader reader)
        {
            string readData = reader.ReadString(); //<-- data as a string.
            // string was saved as {byte}-{byte}- , get rid of '-' , then turn them into bytes...
            string[] bytes = readData.Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
            // stream for deserialization
            object yourObject = null;
            using (System.IO.MemoryStream memoStream = new System.IO.MemoryStream())
            {
                // data that will contain what we need..
                byte[] data = new byte[bytes.Length];
                //  foreach string that would be byte now,convert it to one.
                for (int xd = 0; xd < bytes.Length; xd++)
                    data[xd] = Byte.Parse(bytes[xd],System.Globalization.NumberStyles.AllowHexSpecifier);

                // write to the memostream
                memoStream.Write(data, 0, data.Length);
                // set position back to 0
                memoStream.Position = 0;
                // deserialize 
                System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserialize = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                // get what we need.
                 yourObject = deserialize.Deserialize(memoStream);
            }
            // and return it... surely you have to convert it back. Do avoid serializing Value types please
            // cause you'll just get boxing/unboxing.. object->value
            return yourObject;
        }

    }
}


Drop it at anywhere you want. Like create a script Something.cs , put inside some Server namespace maybe for easier access...
anyways How to use it?

inside your Scripts where you normally write the code for loading.
Deserialize(GenericReader reader){
//you had a DataSet _someData;
// you just call
_someData = (/*yourtype*/DataSet) Server.Serialization./*or whatever*/DeserializeFromString(reader);
or
_someData = (/*yourtype*/DataSet) Server.Serialization./*or whatever*/DeserializeFromByteArray(reader);


}

and the
Serialize(GenericWriter writer){
// you had DataSet _someData that you want to save
_someData =Server.Serialization./*or whatever*/SerializeAsString(_someData,writer);
or
_someData =Server.Serialization./*or whatever*/SerializeAsByteArray(_someData,writer);

}


Note: I haven't used it right now, cause i've got to rewrite few system. Why am I posting this? Well once, someone asked for help with serialzing some class from System.ComponentModel.And I figured out Id share the complete version I wrote.

If you do use it, mind giving feedback? Thanks, as I said haven't tested it myself...nor compiled. But should be okay.
Remember to include right namespaces
 

Soteric

Knight
You can take a look on m_ChampionTitles serialization in PlayerMobile class. It's not an item or mobile and not a primitive type so it has its own custom serialization method. You just call it when serializing a player character (since m_ChampionTitles is a part of PlayerMobile). I think it's more simple than saving to xml.
 

Vorspire

Knight
Custom serialization is pretty simple, all you need are file paths, a file handle and the Binary Reader/Writer instances.

You don't even need the paths and handles if you're hooking into the PlayerMobile serialization methods, just create a new pair of serialization methods that are called from the originals.

Check out this script, it uses completely custom serialization methods: http://www.runuo.com/forums/custom-script-releases/90466-ruo-1-0-final-region-tracker.html
 

daat99

Moderator
Staff member
This is a post I made in the script support forum in order to help someone.
It was probably covered already in this thread but just in case it may help someone in the future I'll post it here as well:

All you need to remember is that things must be read at the same order they were saved the last time.

That means that if you change the ser\deser you need to make sure that 2 things will happen:
1. It can deserialize the objects that were saved before you made the changes (load the save from before you restarted).
2. It can deserialize the objects that will be saved in the future after your changes (you'll save with the new changes and it'll load them at the next restart).

In order to accomplish these two things most scripts serialize a version number (like you did without knowing).

People use this number to check what version of the script was last saved and based on that version they need to load it in a different order or load different variables.

For example if I have a monster with "level" property I will save it like this (just pseudo code):
Code:
//we ALWAYS save the version number first!!!
writer.write(1); //first version
writer.write(level);

And I'll load it like this:
Code:
version =reader.readInt(); //we ALWAYS read the version number first
level = reader.readInt(); //read the level, for now we don't care  about the version because it's the first one and we don't need to load  "older" monsters

Some time goes by and I updated the monster with another property called "reachedMaxLevel" and I want to save that too.

In order to save it I'll simply do:
Code:
//we ALWAYS save the version number first!!!
writer.write(2); //we modified the save so now we save version 2 instead of 1
//note that I added the new property before the old one! this will be important for the deserialization method
writer.write(reachedMaxLevel);
writer.write(level);

Now loading it requires more thinking:
Code:
version = reader.readInt(); //just like before, we ALWAYS read the version number first
//now we need to check what version we have:
switch(version)
{
    case 2: //we saved version 2 so we need to load both properties
        reachedMaxLevel = reader.readBool(); //read the new property we added in version 2
        goto case 1; //read what version 1 saved which is the level
    case 1: //we either saved version 1 or we already loaded version 2 properties
        level = reader.readInt();
        break; //we're done loading
    default: //we read an unknown version number and have no idea  what to do so we do nothing... at this point the save is most likely  corrupted
        break
}

Finally we decided that we don't want a level system anymore but we want to save the serial number of the last player our monster saw.

We save it like this:
Code:
//we ALWAYS save the version number first!!!
writer.write(3); //we modified the save so now we save version 3 instead of 2
writer.write(lastPlayerSerial); //save the last player serial

Now we need to make sure we can still load objects saved with both older versions so we can't simply write something similar to what we had at the start:
Code:
version = reader.readInt();
lastPlayerSerial = reader.readInt();

Instead we'll do something like this:
Code:
version = reader.readInt(); //just like before, we ALWAYS read the version number first
//now we need to check what version we have:
switch(version)
{
    case 3: //we saved version 3 so we read only the serial number
        lastPlayerSerial = reader.readInt();
        break; //don't continue reading!!! we didn't save anything else!!!
    //the rest is just like we had in version 2 because we still need to load both version 1 and version 2 objects like before:
    case 2: //we saved version 2 so we need to load both properties
        reachedMaxLevel = reader.readBool(); //read the new property we added in version 2
        goto case 1; //read what version 1 saved which is the level
    case 1: //we either saved version 1 or we already loaded version 2 properties
        level = reader.readInt();
        break; //we're done loading
     default: //we read an unknown version number and have no idea  what to  do so we do nothing... at this point the save is most likely  corrupted
        break
}

I hope this detailed explanation relieves some of your fears from the serialization\deserialization procedure (save it in a file for later reference, I bet you'll eventually need it and you'll have hard time finding it in the forum when you do!).
 
Top