Skip to content

Commit 3963b27

Browse files
author
Jannify
authored
Added save file upgradability (SubnauticaNitrox#1370)
* Added save file upgradability * Changed config value setting with commands * Moved waiting to server * Moved from string to JObject in SaveDataUpgrade
1 parent 97188cb commit 3963b27

18 files changed

+317
-105
lines changed

NitroxModel/Core/NitroxEnvironment.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
using System;
2+
using System.Reflection;
23

34
namespace NitroxModel.Helper
45
{
56
public static class NitroxEnvironment
67
{
8+
public static readonly Version Version = Assembly.GetAssembly(typeof(NitroxEnvironment)).GetName().Version;
9+
710
public enum Types
811
{
912
NORMAL,

NitroxServer/ConsoleCommands/AutosaveCommand.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using NitroxModel.DataStructures.GameLogic;
2+
using NitroxModel.Serialization;
23
using NitroxModel.Server;
34
using NitroxServer.ConsoleCommands.Abstract;
45
using NitroxServer.ConsoleCommands.Abstract.Type;
@@ -8,18 +9,15 @@ namespace NitroxServer.ConsoleCommands
89
{
910
internal class AutoSaveCommand : Command
1011
{
11-
private readonly ServerConfig serverConfig;
12-
13-
public AutoSaveCommand(ServerConfig serverConfig) : base("autosave", Perms.ADMIN, "Toggles the map autosave")
12+
public AutoSaveCommand() : base("autosave", Perms.ADMIN, "Toggles the map autosave")
1413
{
15-
this.serverConfig = serverConfig;
1614
AddParameter(new TypeBoolean("on/off", true));
1715
}
1816

1917
protected override void Execute(CallArgs args)
2018
{
2119
bool toggle = args.Get<bool>(0);
22-
20+
ServerConfig serverConfig = NitroxConfig.Deserialize<ServerConfig>();
2321
if (toggle)
2422
{
2523
serverConfig.DisableAutoSave = false;
@@ -32,6 +30,7 @@ protected override void Execute(CallArgs args)
3230
Server.Instance.DisablePeriodicSaving();
3331
SendMessage(args.Sender, "Disabled periodical saving");
3432
}
33+
NitroxConfig.Serialize(serverConfig);
3534
}
3635
}
3736
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using NitroxModel.DataStructures.GameLogic;
22
using NitroxModel.Logger;
3+
using NitroxModel.Serialization;
34
using NitroxModel.Server;
45
using NitroxServer.ConsoleCommands.Abstract;
56
using NitroxServer.ConsoleCommands.Abstract.Type;
@@ -9,22 +10,21 @@ namespace NitroxServer.ConsoleCommands
910
{
1011
internal class ChangeAdminPasswordCommand : Command
1112
{
12-
private readonly ServerConfig serverConfig;
13-
14-
public ChangeAdminPasswordCommand(ServerConfig serverConfig) : base("changeadminpassword", Perms.ADMIN, "Changes admin password")
13+
public ChangeAdminPasswordCommand() : base("changeadminpassword", Perms.ADMIN, "Changes admin password")
1514
{
16-
this.serverConfig = serverConfig;
1715
AddParameter(new TypeString("password", true));
1816
}
1917

2018
protected override void Execute(CallArgs args)
2119
{
2220
string newPassword = args.Get(0);
2321

22+
ServerConfig serverConfig = NitroxConfig.Deserialize<ServerConfig>();
2423
serverConfig.AdminPassword = newPassword;
24+
NitroxConfig.Serialize(serverConfig);
2525

2626
Log.InfoSensitive("Admin password changed to {password} by {playername}", newPassword, args.SenderName);
27-
SendMessageToPlayer(args.Sender, "Admin password changed");
27+
SendMessageToPlayer(args.Sender, "Admin password changed. In order to take effect pls restart the server.");
2828
}
2929
}
3030
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using NitroxModel.DataStructures.GameLogic;
22
using NitroxModel.Logger;
3+
using NitroxModel.Serialization;
34
using NitroxModel.Server;
45
using NitroxServer.ConsoleCommands.Abstract;
56
using NitroxServer.ConsoleCommands.Abstract.Type;
@@ -9,22 +10,21 @@ namespace NitroxServer.ConsoleCommands
910
{
1011
internal class ChangeServerPasswordCommand : Command
1112
{
12-
private readonly ServerConfig serverConfig;
13-
14-
public ChangeServerPasswordCommand(ServerConfig serverConfig) : base("changeserverpassword", Perms.ADMIN, "Changes server password. Clear it without argument")
13+
public ChangeServerPasswordCommand() : base("changeserverpassword", Perms.ADMIN, "Changes server password. Clear it without argument")
1514
{
16-
this.serverConfig = serverConfig;
1715
AddParameter(new TypeString("password", false));
1816
}
1917

2018
protected override void Execute(CallArgs args)
2119
{
2220
string password = args.Get(0) ?? string.Empty;
2321

22+
ServerConfig serverConfig = NitroxConfig.Deserialize<ServerConfig>();
2423
serverConfig.ServerPassword = password;
24+
NitroxConfig.Serialize(serverConfig);
2525

2626
Log.InfoSensitive("Server password changed to {password} by {playername}", password, args.SenderName);
27-
SendMessageToPlayer(args.Sender, "Server password changed");
27+
SendMessageToPlayer(args.Sender, "Server password changed. In order to take effect pls restart the server.");
2828
}
2929
}
3030
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using NitroxModel.DataStructures.GameLogic;
2+
using NitroxModel.Serialization;
3+
using NitroxModel.Server;
4+
using NitroxServer.ConsoleCommands.Abstract;
5+
using NitroxServer.Serialization;
6+
using NitroxServer.Serialization.World;
7+
8+
namespace NitroxServer.ConsoleCommands
9+
{
10+
internal sealed class SwapSerializerCommand : Command
11+
{
12+
private readonly WorldPersistence worldPersistence;
13+
private readonly ServerProtoBufSerializer protoBufSerializer;
14+
private readonly ServerJsonSerializer jsonSerializer;
15+
16+
public SwapSerializerCommand(WorldPersistence worldPersistence, ServerProtoBufSerializer protoBufSerializer, ServerJsonSerializer jsonSerializer) : base("swapSerializer", Perms.CONSOLE, "Swaps the world data serializer")
17+
{
18+
this.worldPersistence = worldPersistence;
19+
this.protoBufSerializer = protoBufSerializer;
20+
this.jsonSerializer = jsonSerializer;
21+
}
22+
23+
protected override void Execute(CallArgs args)
24+
{
25+
ServerConfig serverConfig = NitroxConfig.Deserialize<ServerConfig>();
26+
serverConfig.SerializerMode = serverConfig.SerializerMode == ServerSerializerMode.PROTOBUF ? ServerSerializerMode.JSON : ServerSerializerMode.PROTOBUF;
27+
NitroxConfig.Serialize(serverConfig);
28+
29+
worldPersistence.UpdateSerializer(serverConfig.SerializerMode == ServerSerializerMode.PROTOBUF ? (IServerSerializer)protoBufSerializer : jsonSerializer);
30+
SendMessage(args.Sender, $"Swapped to {serverConfig.SerializerMode}");
31+
}
32+
}
33+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System.IO;
2+
using System.Threading;
3+
using NitroxModel.DataStructures.GameLogic;
4+
using NitroxModel.Helper;
5+
using NitroxModel.Server;
6+
using NitroxServer.ConsoleCommands.Abstract;
7+
using NitroxServer.Serialization;
8+
using NitroxServer.Serialization.Upgrade;
9+
using NitroxServer.Serialization.World;
10+
11+
namespace NitroxServer.ConsoleCommands
12+
{
13+
internal sealed class UpgradeCommand : Command
14+
{
15+
private readonly WorldPersistence worldPersistence;
16+
private readonly ServerConfig serverConfig;
17+
private readonly SaveDataUpgrade[] upgrades;
18+
private readonly Server server;
19+
20+
public UpgradeCommand(WorldPersistence worldPersistence, ServerConfig serverConfig, SaveDataUpgrade[] upgrades, Server server) : base("upgrade", Perms.CONSOLE, "Upgrades the save file to the next version")
21+
{
22+
this.worldPersistence = worldPersistence;
23+
this.serverConfig = serverConfig;
24+
this.upgrades = upgrades;
25+
this.server = server;
26+
}
27+
28+
protected override void Execute(CallArgs args)
29+
{
30+
server.DisablePeriodicSaving();
31+
string saveDir = serverConfig.SaveName;
32+
string fileEnding = worldPersistence.SaveDataSerializer.GetFileEnding();
33+
SaveFileVersion saveFileVersion = worldPersistence.SaveDataSerializer.Deserialize<SaveFileVersion>(Path.Combine(saveDir, "Version" + fileEnding));
34+
35+
36+
if (saveFileVersion.Version == NitroxEnvironment.Version)
37+
{
38+
SendMessage(args.Sender, "Save files are already at the newest version");
39+
}
40+
else if (serverConfig.SerializerMode == ServerSerializerMode.PROTOBUF)
41+
{
42+
SendMessage(args.Sender, "Can't upgrade while using ProtoBuf as serializer");
43+
}
44+
else
45+
{
46+
foreach (SaveDataUpgrade upgrade in upgrades)
47+
{
48+
if (upgrade.TargetVersion > saveFileVersion.Version)
49+
{
50+
upgrade.UpgradeData(saveDir, fileEnding);
51+
}
52+
}
53+
worldPersistence.SaveDataSerializer.Serialize(Path.Combine(saveDir, "Version" + fileEnding), new SaveFileVersion());
54+
SendMessage(args.Sender, $"Save file was upgraded to {NitroxEnvironment.Version}");
55+
server.StopAndWait(false);
56+
}
57+
}
58+
}
59+
}

NitroxServer/GameLogic/Bases/BaseData.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ namespace NitroxServer.GameLogic.Bases
88
[ProtoContract, JsonObject(MemberSerialization.OptIn)]
99
public class BaseData
1010
{
11-
public const short VERSION = 2;
12-
1311
[JsonProperty, ProtoMember(1)]
1412
public List<BasePiece> PartiallyConstructedPieces = new List<BasePiece>();
1513

NitroxServer/GameLogic/Players/PlayerData.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ namespace NitroxServer.GameLogic.Players
88
[ProtoContract, JsonObject(MemberSerialization.OptIn)]
99
public class PlayerData
1010
{
11-
public const short VERSION = 2;
12-
1311
[JsonProperty, ProtoMember(1)]
1412
public List<PersistedPlayerData> Players = new List<PersistedPlayerData>();
1513

NitroxServer/NitroxServer.csproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
44
<PropertyGroup>
@@ -147,7 +147,9 @@
147147
<Compile Include="ConsoleCommands\DirectoryCommand.cs" />
148148
<Compile Include="ConsoleCommands\HelpCommand.cs" />
149149
<Compile Include="ConsoleCommands\RestartCommand.cs" />
150+
<Compile Include="ConsoleCommands\SwapSerializerCommand.cs" />
150151
<Compile Include="ConsoleCommands\TeleportCommand.cs" />
152+
<Compile Include="ConsoleCommands\UpgradeCommand.cs" />
151153
<Compile Include="ConsoleCommands\WarpCommand.cs" />
152154
<Compile Include="ConsoleCommands\WhoisCommand.cs" />
153155
<Compile Include="ConsoleCommands\KickCommand.cs" />
@@ -172,6 +174,8 @@
172174
<Compile Include="Serialization\JsonConverter\AttributeContractResolver.cs" />
173175
<Compile Include="Serialization\JsonConverter\NitroxIdConverter.cs" />
174176
<Compile Include="Serialization\JsonConverter\TechTypeConverter.cs" />
177+
<Compile Include="Serialization\SaveDataUpgrades\NewtonsoftExtensions.cs" />
178+
<Compile Include="Serialization\SaveDataUpgrades\SaveDataUpgrade.cs" />
175179
<Compile Include="Serialization\ServerConfig.cs" />
176180
<Compile Include="Serialization\Resources\Datastructures\AssetIdentifier.cs" />
177181
<Compile Include="Serialization\Resources\Datastructures\GameObjectAsset.cs" />
@@ -214,7 +218,7 @@
214218
<Compile Include="Serialization\World\World.cs" />
215219
<Compile Include="Serialization\World\WorldData.cs" />
216220
<Compile Include="Serialization\World\WorldPersistence.cs" />
217-
<Compile Include="Serialization\World\SaveFileVersions.cs" />
221+
<Compile Include="Serialization\World\SaveFileVersion.cs" />
218222
<Compile Include="Serialization\World\VersionMismatchException.cs" />
219223
<Compile Include="Server.cs" />
220224
<Compile Include="ServerAutoFacRegistrar.cs" />
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#nullable enable
2+
using System;
3+
using Newtonsoft.Json.Linq;
4+
5+
namespace NitroxServer.Serialization.SaveDataUpgrades
6+
{
7+
public static class NewtonsoftExtensions
8+
{
9+
public static void Rename(this JToken token, string newName)
10+
{
11+
if (token == null)
12+
{
13+
throw new ArgumentNullException("token", "Cannot rename a null token");
14+
}
15+
16+
JProperty property;
17+
18+
if (token.Type == JTokenType.Property)
19+
{
20+
if (token.Parent == null)
21+
{
22+
throw new InvalidOperationException("Cannot rename a property with no parent");
23+
}
24+
25+
property = (JProperty)token;
26+
}
27+
else
28+
{
29+
if (token.Parent == null || token.Parent.Type != JTokenType.Property)
30+
{
31+
throw new InvalidOperationException("This token's parent is not a JProperty; cannot rename");
32+
}
33+
34+
property = (JProperty)token.Parent;
35+
}
36+
37+
// Note: to avoid triggering a clone of the existing property's value,
38+
// we need to save a reference to it and then null out property.Value
39+
// before adding the value to the new JProperty.
40+
// Thanks to @dbc for the suggestion.
41+
42+
JToken? existingValue = property.Value;
43+
property.Value = null!;
44+
JProperty? newProperty = new JProperty(newName, existingValue);
45+
property.Replace(newProperty);
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)