I changed my job, now it is developed purely in C#, and it is a stand-alone game, so I have to study the archive method of C# again. After a period of time to solve various problems, it is now stable, and the veterans who need it can refer to it.
1. Import ProtoBuf
https://github.com/protocolbuffers/protobuf/releases/
Download the required language, unzip it and import it into your own directory.
2. Agreement statement
[ProtoContract]
public class DataRoot
{
[ProtoMember(1)]
public int sA;
[ProtoMember(2)]
public string sB;
[ProtoMember(3)]
public float[] sC;
[ProtoMember(4)]
public DataA sD;
[ProtoMember(5)]
public List<DataA> sE;
}
[ProtoContract]
public class DataA
{
[ProtoMember(1)]
public int sA1;
[ProtoMember(2)]
public int sA2;
}
- ProtoContract declares the class to be serialized
- ProtoMember declares the member to be serialized
3. Archive
data generation
DataRoot data = new DataRoot()
{
sA = 0,
sB = "AAA",
sC = new float[2] {
1, 2 },
sE = null,
};
data.sD = new DataA();
Debug.Log("保存存档完成");
Package and save
//将内容进行封包
File.Delete(Application.persistentDataPath + "/Save.dat");
FileStream stream = File.OpenWrite(Application.persistentDataPath + "/Save.dat");
Serializer.Serialize(stream, data);
stream.Dispose();
- ProtoBuf.Serializer.Serialize The serialization method that comes with protobuf converts data into byte[]
- The writing method of protobuf is to overwrite bytes.length into the file, and finally use ProtoReader.TO_EOF as the terminator to add to the next bit. That is to say, the bytes.length of the file is always kept at the maximum length. In the project, I don’t know why, after the byte[] of the archive becomes shorter, there is always a problem with this terminator, which leads to an error in reading the archive, so I simply delete the original archive file every time I archive.
4. Load file
read and unpack
private void TryDeserialize(out DataRoot data)
{
if (File.Exists(Application.persistentDataPath + "/Save.dat"))
{
//↓↓↓这一部分比较特殊 根据需要判断是否要添加↓↓↓
Support.SetInstanceFunc((Type type) =>
{
if (Type.GetType(type.FullName) == null)
{
return null;
}
return Activator.CreateInstance(type
#if !(CF || SILVERLIGHT || WINRT || PORTABLE || NETSTANDARD1_3 || NETSTANDARD1_4)
, nonPublic: true
#endif
);
});
//↑↑↑这一部分比较特殊 根据需要判断是否要添加↑↑↑
//对内容进行解包
FileStream stream = File.OpenRead(Application.persistentDataPath + "/Save.dat");
data = Serializer.Deserialize<DataRoot>(stream);
stream.Dispose();
}
else
{
data = null;
}
//↓↓↓这一部分比较特殊 根据需要判断是否要添加↓↓↓
Support.SetInstanceFunc(null);
//↑↑↑这一部分比较特殊 根据需要判断是否要添加↑↑↑
}
- ProtoBuf.Serializer.Deserialize<T> The deserialization method that comes with protobuf converts byte[] into data
private static Func<Type, object> instanceFunc;
public static void SetInstanceFunc(Func<Type, object> func)
{
instanceFunc = func;
}
public static object CreateInstance(Type type)
{
if (instanceFunc != null)
{
object obj = instanceFunc(type);
if (obj != null)
{
return obj;
}
}
// ReSharper disable once AssignNullToNotNullAttribute
if (Type.GetType(type.FullName) == null)
{
return _appDomain.Instantiate(type.FullName);
}
return Activator.CreateInstance(type
#if !(CF || SILVERLIGHT || WINRT || PORTABLE || NETSTANDARD1_3 || NETSTANDARD1_4)
, nonPublic: true
#endif
);
- Because our project involves hot update of C# code, the archive manager is in the hot update assembly, and protobuf is placed in a deeper assembly because of other content needs, and this assembly does not refer to hot update assembly, so when unpacking, the class in the hot-updated assembly cannot be found. I had no choice but to add a method to the ProtoBuf.Support class. In the CreateInstance method of Support, check out the instantiated method, and instantiate it from the hot-updated assembly when unpacking.
Data Recovery
public void Load()
{
TryDeserialize(out DataRoot data);
if (data != null)
{
int _sA = data.sA;
string _sB = data.sB;
float[] _sC = data.sC;
DataA _sD = data.sD;
List<DataA> _sE = data.sE;
if (_sE != null)
{
//TODO
}
Debug.Log("加载存档完成");
}
else
{
//TODO 没有存档,可以执行如注册或新手引导之类的方法
}
}
- For List and ArrayList, if the length of the data is 0 when archiving, the unpacked data is null, pay attention to judge this situation.
5. Encryption
AESCryptionUtility
Here is a code that uses AES encryption and decryption, which supports encryption and decryption of string and byte[]. When encrypting and decrypting, you need to pass in a string of length 32 as the key.
/// <summary>
/// AES加解密字符串
/// </summary>
public static class AESCryptionUtility
{
/// <summary>
/// AES加密 String
/// </summary>
/// <param name="str">被加密的明文</param>
/// <param name="key">密钥</param>
/// <returns>密文</returns>
public static string Encrypt(string str, string key)
{
MemoryStream mStream = new MemoryStream();
RijndaelManaged aes = new RijndaelManaged();
byte[] plainbytes = Encoding.UTF8.GetBytes(str);
byte[] bKey = new byte[32];
Array.Copy(Encoding.UTF8.GetBytes(key.PadRight(bKey.Length)), bKey, bKey.Length);
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
aes.KeySize = 128;
//aes.Key = _key;
aes.Key = bKey;
//aes.IV = _iV;
CryptoStream cryptoStream = new CryptoStream(mStream, aes.CreateEncryptor(), CryptoStreamMode.Write);
try
{
cryptoStream.Write(plainbytes, 0, plainbytes.Length);
cryptoStream.FlushFinalBlock();
return Convert.ToBase64String(mStream.ToArray());
}
finally
{
cryptoStream.Close();
mStream.Close();
aes.Clear();
}
}
/// <summary>
/// AES加密 Bytes
/// </summary>
/// <param name="bytes">被加密的明文bytes</param>
/// <param name="key">密钥</param>
/// <returns>密文</returns>
public static byte[] Encrypt(byte[] bytes, string key)
{
MemoryStream mStream = new MemoryStream();
RijndaelManaged aes = new RijndaelManaged();
byte[] bKey = new byte[32];
Array.Copy(Encoding.UTF8.GetBytes(key.PadRight(bKey.Length)), bKey, bKey.Length);
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
aes.KeySize = 128;
//aes.Key = _key;
aes.Key = bKey;
//aes.IV = _iV;
CryptoStream cryptoStream = new CryptoStream(mStream, aes.CreateEncryptor(), CryptoStreamMode.Write);
try
{
cryptoStream.Write(bytes, 0, bytes.Length);
cryptoStream.FlushFinalBlock();
return mStream.ToArray();
}
finally
{
cryptoStream.Close();
mStream.Close();
aes.Clear();
}
}
/// <summary>
/// AES解密 String
/// </summary>
/// <param name="str">被加密的明文</param>
/// <param name="key">密钥</param>
/// <returns>明文</returns>
public static string Decrypt(string str, string key)
{
byte[] encryptedbytes = Convert.FromBase64String(str);
byte[] bKey = new byte[32];
Array.Copy(Encoding.UTF8.GetBytes(key.PadRight(bKey.Length)), bKey, bKey.Length);
MemoryStream mStream = new MemoryStream(encryptedbytes);
//mStream.Write( encryptedbytes, 0, encryptedbytes.Length );
//mStream.Seek( 0, SeekOrigin.Begin );
RijndaelManaged aes = new RijndaelManaged();
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
aes.KeySize = 128;
aes.Key = bKey;
//aes.IV = _iV;
CryptoStream cryptoStream = new CryptoStream(mStream, aes.CreateDecryptor(), CryptoStreamMode.Read);
try
{
byte[] tmp = new byte[encryptedbytes.Length + 32];
int len = cryptoStream.Read(tmp, 0, encryptedbytes.Length + 32);
byte[] ret = new byte[len];
Array.Copy(tmp, 0, ret, 0, len);
return Encoding.UTF8.GetString(ret);
}
finally
{
cryptoStream.Close();
mStream.Close();
aes.Clear();
}
}
/// <summary>
/// AES解密 Bytes
/// </summary>
/// <param name="bytes">被加密的明文bytes</param>
/// <param name="key">密钥</param>
/// <returns>明文</returns>
public static byte[] Decrypt(byte[] bytes, string key)
{
byte[] bKey = new byte[32];
Array.Copy(Encoding.UTF8.GetBytes(key.PadRight(bKey.Length)), bKey, bKey.Length);
MemoryStream mStream = new MemoryStream(bytes);
//mStream.Write( encryptedbytes, 0, encryptedbytes.Length );
//mStream.Seek( 0, SeekOrigin.Begin );
RijndaelManaged aes = new RijndaelManaged();
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
aes.KeySize = 128;
aes.Key = bKey;
//aes.IV = _iV;
CryptoStream cryptoStream = new CryptoStream(mStream, aes.CreateDecryptor(), CryptoStreamMode.Read);
try
{
byte[] tmp = new byte[bytes.Length + 32];
int len = cryptoStream.Read(tmp, 0, bytes.Length + 32);
byte[] ret = new byte[len];
Array.Copy(tmp, 0, ret, 0, len);
return ret;
}
finally
{
cryptoStream.Close();
mStream.Close();
aes.Clear();
}
}
}
Related parameters
private readonly string cryptionKey = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
private bool isCrtption;
- cryptionKey AES key, random characters with a length of 32 bits
- isCrtption Whether to encrypt, used as archive Debug
Archive encryption
//加密
if (isCrtption)
{
//将内容进行封包
FileStream stream = File.OpenWrite(Application.persistentDataPath + "/tempS");
Serializer.Serialize(stream, data);
stream.Dispose();
//加载文件Bytes
FileStream reader = new FileStream(Application.persistentDataPath + "/tempS", FileMode.Open, FileAccess.Read);
byte[] fileBytes = new byte[reader.Length];
reader.Read(fileBytes, 0, fileBytes.Length);
reader.Dispose();
//将内容加密
fileBytes = AESCryptionUtility.Encrypt(fileBytes, cryptionKey);
//保存存档
File.Delete(Application.persistentDataPath + "/Save.dat");
FileStream writer = new FileStream(Application.persistentDataPath + "/Save.dat", FileMode.OpenOrCreate, FileAccess.Write);
writer.Write(fileBytes, 0, fileBytes.Length);
writer.Dispose();
//清理
File.Delete(Application.persistentDataPath + "/tempS");
}
else
{
//将内容进行封包
File.Delete(Application.persistentDataPath + "/Save.dat");
FileStream stream = File.OpenWrite(Application.persistentDataPath + "/Save.dat");
Serializer.Serialize(stream, data);
stream.Dispose();
}
- First serialize and save the data to a temporary file to prevent problems when operating on the same file.
- Read byte[] in temporary file
- Encrypt the content
- Save the encrypted byte[] to the real archive file
- delete temporary files
archive decryption
//解密
if (isCrtption)
{
//加载文件Bytes
FileStream reader = new FileStream(Application.persistentDataPath + "/Save.dat", FileMode.Open, FileAccess.Read);
byte[] fileBytes = new byte[reader.Length];
reader.Read(fileBytes, 0, fileBytes.Length);
reader.Dispose();
//将内容解密
fileBytes = AESCryptionUtility.Decrypt(fileBytes, cryptionKey);
//写道临时文件中
FileStream writer = new FileStream(Application.persistentDataPath + "/tempS", FileMode.OpenOrCreate, FileAccess.Write);
writer.Write(fileBytes, 0, fileBytes.Length);
writer.Dispose();
//对内容进行解包
FileStream stream = File.OpenRead(Application.persistentDataPath + "/tempS");
data = Serializer.Deserialize<DataRoot>(stream);
stream.Dispose();
//清理
File.Delete(Application.persistentDataPath + "/tempS");
}
else
{
//对内容进行解包
FileStream stream = File.OpenRead(Application.persistentDataPath + "/Save.dat");
data = Serializer.Deserialize<DataRoot>(stream);
stream.Dispose();
}
- Read the encrypted byte[] first
- Decrypt the content
- Write the decrypted byte[] to a temporary file. Because if there is no further archive, it is necessary to ensure that the original archive is correct, so the content cannot be overwritten in the original archive.
- Deserialize the decrypted byte[] into real data
- delete temporary files