Terraria ModLoader  0.11.7.8
A mod to make and play Terraria mods
PlayerIO.cs
Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.IO;
4 using System.Linq;
5 using System.Security.Cryptography;
6 using Terraria.Graphics.Shaders;
7 using Terraria.ModLoader.Default;
8 using Terraria.ModLoader.Engine;
10 using Terraria.Social;
11 using Terraria.Utilities;
12 
13 namespace Terraria.ModLoader.IO
14 {
15  internal static class PlayerIO
16  {
17  internal static void WriteVanillaHairDye(short hairDye, BinaryWriter writer) {
18  writer.Write((byte)(hairDye > EffectsTracker.vanillaHairShaderCount ? 0 : hairDye));
19  }
20 
21  //make Terraria.Player.ENCRYPTION_KEY internal
22  //add to end of Terraria.Player.SavePlayer
23  internal static void Save(Player player, string path, bool isCloudSave) {
24  path = Path.ChangeExtension(path, ".tplr");
25  if (FileUtilities.Exists(path, isCloudSave))
26  FileUtilities.Copy(path, path + ".bak", isCloudSave);
27 
28  var tag = new TagCompound {
29  ["armor"] = SaveInventory(player.armor),
30  ["dye"] = SaveInventory(player.dye),
31  ["inventory"] = SaveInventory(player.inventory),
32  ["miscEquips"] = SaveInventory(player.miscEquips),
33  ["miscDyes"] = SaveInventory(player.miscDyes),
34  ["bank"] = SaveInventory(player.bank.item),
35  ["bank2"] = SaveInventory(player.bank2.item),
36  ["bank3"] = SaveInventory(player.bank3.item),
37  ["hairDye"] = SaveHairDye(player.hairDye),
38  ["modData"] = SaveModData(player),
39  ["modBuffs"] = SaveModBuffs(player),
40  ["usedMods"] = SaveUsedMods(player)
41  };
42 
43  using (Stream stream = isCloudSave ? (Stream)new MemoryStream() : (Stream)new FileStream(path, FileMode.Create)) {
44  TagIO.ToStream(tag, stream);
45  if (isCloudSave && SocialAPI.Cloud != null)
46  SocialAPI.Cloud.Write(path, ((MemoryStream)stream).ToArray());
47  }
48  }
49  //add near end of Terraria.Player.LoadPlayer before accessory check
50  internal static void Load(Player player, string path, bool isCloudSave) {
51  path = Path.ChangeExtension(path, ".tplr");
52  if (!FileUtilities.Exists(path, isCloudSave))
53  return;
54 
55  var buf = FileUtilities.ReadAllBytes(path, isCloudSave);
56  if (buf[0] != 0x1F || buf[1] != 0x8B) {
57  LoadLegacy(player, buf);
58  return;
59  }
60 
61  var tag = TagIO.FromStream(new MemoryStream(buf));
62  LoadInventory(player.armor, tag.GetList<TagCompound>("armor"));
63  LoadInventory(player.dye, tag.GetList<TagCompound>("dye"));
64  LoadInventory(player.inventory, tag.GetList<TagCompound>("inventory"));
65  LoadInventory(player.miscEquips, tag.GetList<TagCompound>("miscEquips"));
66  LoadInventory(player.miscDyes, tag.GetList<TagCompound>("miscDyes"));
67  LoadInventory(player.bank.item, tag.GetList<TagCompound>("bank"));
68  LoadInventory(player.bank2.item, tag.GetList<TagCompound>("bank2"));
69  LoadInventory(player.bank3.item, tag.GetList<TagCompound>("bank3"));
70  LoadHairDye(player, tag.GetString("hairDye"));
71  LoadModData(player, tag.GetList<TagCompound>("modData"));
72  LoadModBuffs(player, tag.GetList<TagCompound>("modBuffs"));
73  LoadUsedMods(player, tag.GetList<string>("usedMods"));
74  }
75 
76  public static List<TagCompound> SaveInventory(Item[] inv) {
77  var list = new List<TagCompound>();
78  for (int k = 0; k < inv.Length; k++) {
79  if (ItemLoader.NeedsModSaving(inv[k])) {
80  var tag = ItemIO.Save(inv[k]);
81  tag.Set("slot", (short)k);
82  list.Add(tag);
83  }
84  }
85  return list.Count > 0 ? list : null;
86  }
87 
88  public static void LoadInventory(Item[] inv, IList<TagCompound> list) {
89  foreach (var tag in list)
90  inv[tag.GetShort("slot")] = ItemIO.Load(tag);
91  }
92 
93  public static string SaveHairDye(short hairDye) {
94  if (hairDye <= EffectsTracker.vanillaHairShaderCount)
95  return "";
96 
97  int itemId = GameShaders.Hair._reverseShaderLookupDictionary[hairDye];
98  var modItem = ItemLoader.GetItem(itemId);
99  return modItem.mod.Name + '/' + modItem.Name;
100  }
101 
102  public static void LoadHairDye(Player player, string hairDyeItemName) {
103  if (hairDyeItemName == "")
104  return;
105 
106  // no mystery hair dye at this stage
107  ModContent.SplitName(hairDyeItemName, out string modName, out string itemName);
108  var modItem = ModLoader.GetMod(modName)?.GetItem(itemName);
109  if (modItem != null)
110  player.hairDye = (byte)GameShaders.Hair.GetShaderIdFromItemId(modItem.item.type);
111  }
112 
113  internal static List<TagCompound> SaveModData(Player player) {
114  var list = new List<TagCompound>();
115  foreach (var modPlayer in player.modPlayers) {
116  var data = modPlayer.Save();
117  if (data == null)
118  continue;
119 
120  list.Add(new TagCompound {
121  ["mod"] = modPlayer.mod.Name,
122  ["name"] = modPlayer.Name,
123  ["data"] = data
124  });
125  }
126  return list;
127  }
128 
129  internal static void LoadModData(Player player, IList<TagCompound> list) {
130  foreach (var tag in list) {
131  var mod = ModLoader.GetMod(tag.GetString("mod"));
132  var modPlayer = mod == null ? null : player.GetModPlayer(mod, tag.GetString("name"));
133  if (modPlayer != null) {
134  try {
135  if (tag.ContainsKey("legacyData"))
136  modPlayer.LoadLegacy(new BinaryReader(new MemoryStream(tag.GetByteArray("legacyData"))));
137  else
138  modPlayer.Load(tag.GetCompound("data"));
139  }
140  catch (Exception e) {
141  throw new CustomModDataException(mod,
142  "Error in reading custom player data for " + mod.Name, e);
143  }
144  }
145  else {
146  player.GetModPlayer<MysteryPlayer>().data.Add(tag);
147  }
148  }
149  }
150 
151  internal static List<TagCompound> SaveModBuffs(Player player) {
152  var list = new List<TagCompound>();
153  for (int k = 0; k < Player.MaxBuffs; k++) {
154  int buff = player.buffType[k];
155  if (buff == 0 || Main.buffNoSave[buff])
156  continue;
157 
158  if (BuffLoader.IsModBuff(buff)) {
159  var modBuff = BuffLoader.GetBuff(buff);
160  list.Add(new TagCompound {
161  ["mod"] = modBuff.mod.Name,
162  ["name"] = modBuff.Name,
163  ["time"] = player.buffTime[k]
164  });
165  }
166  else {
167  list.Add(new TagCompound {
168  ["mod"] = "Terraria",
169  ["id"] = buff,
170  ["time"] = player.buffTime[k]
171  });
172  }
173  }
174  return list;
175  }
176 
177  internal static void LoadModBuffs(Player player, IList<TagCompound> list) {
178  //buffs list is guaranteed to be compacted
179  int buffCount = Player.MaxBuffs;
180  while (buffCount > 0 && player.buffType[buffCount - 1] == 0)
181  buffCount--;
182 
183  if (buffCount == 0) {
184  //always the case since vanilla buff saving was disabled, when extra buff slots were added
185  foreach (var tag in list) {
186  if (buffCount == Player.MaxBuffs)
187  return;
188 
189  var modName = tag.GetString("mod");
190  int type = modName == "Terraria" ? tag.GetInt("id") : ModLoader.GetMod(modName)?.BuffType(tag.GetString("name")) ?? 0;
191  if (type > 0) {
192  player.buffType[buffCount] = type;
193  player.buffTime[buffCount] = tag.GetInt("time");
194  buffCount++;
195  }
196  }
197  return;
198  }
199 
200  //legacy code path
201  //iterate the list in reverse, insert each buff at its index and push the buffs after it up a slot
202  foreach (var tag in list.Reverse()) {
203  var mod = ModLoader.GetMod(tag.GetString("mod"));
204  int type = mod?.BuffType(tag.GetString("name")) ?? 0;
205  if (type == 0)
206  continue;
207 
208  int index = Math.Min(tag.GetByte("index"), buffCount);
209  Array.Copy(player.buffType, index, player.buffType, index + 1, Player.MaxBuffs - index - 1);
210  Array.Copy(player.buffTime, index, player.buffTime, index + 1, Player.MaxBuffs - index - 1);
211  player.buffType[index] = type;
212  player.buffTime[index] = tag.GetInt("time");
213  }
214  }
215 
216  private static void LoadLegacy(Player player, byte[] buffer) {
217  const int numFlagBytes = 2;
218  RijndaelManaged rijndaelManaged = new RijndaelManaged();
219  rijndaelManaged.Padding = PaddingMode.None;
220  using (MemoryStream stream = new MemoryStream(buffer)) {
221  using (CryptoStream cryptoStream = new CryptoStream(stream, rijndaelManaged.CreateDecryptor(Player.ENCRYPTION_KEY, Player.ENCRYPTION_KEY), CryptoStreamMode.Read)) {
222  using (BinaryReader reader = new BinaryReader(cryptoStream)) {
223  byte limit = reader.ReadByte();
224  if (limit == 0) {
225  return;
226  }
227  byte[] flags = reader.ReadBytes(limit);
228  if (flags.Length < numFlagBytes) {
229  Array.Resize(ref flags, numFlagBytes);
230  }
231  LoadLegacyModPlayer(player, flags, reader);
232  }
233  }
234  }
235  }
236 
237  private static void LoadLegacyModPlayer(Player player, byte[] flags, BinaryReader reader) {
238  if ((flags[0] & 1) == 1) {
239  ItemIO.LoadLegacyInventory(player.armor, reader);
240  }
241  if ((flags[0] & 2) == 2) {
242  ItemIO.LoadLegacyInventory(player.dye, reader);
243  }
244  if ((flags[0] & 4) == 4) {
245  ItemIO.LoadLegacyInventory(player.inventory, reader, true, true);
246  }
247  if ((flags[0] & 8) == 8) {
248  ItemIO.LoadLegacyInventory(player.miscEquips, reader);
249  }
250  if ((flags[0] & 16) == 16) {
251  ItemIO.LoadLegacyInventory(player.miscDyes, reader);
252  }
253  if ((flags[0] & 32) == 32) {
254  ItemIO.LoadLegacyInventory(player.bank.item, reader, true);
255  }
256  if ((flags[0] & 64) == 64) {
257  ItemIO.LoadLegacyInventory(player.bank2.item, reader, true);
258  }
259  if ((flags[0] & 128) == 128) {
260  LoadLegacyModData(player, reader);
261  }
262  if ((flags[1] & 1) == 1) {
263  LoadLegacyModBuffs(player, reader);
264  }
265  }
266 
267  private static void LoadLegacyModData(Player player, BinaryReader reader) {
268  int count = reader.ReadUInt16();
269  for (int k = 0; k < count; k++) {
270  string modName = reader.ReadString();
271  string name = reader.ReadString();
272  byte[] data = reader.ReadBytes(reader.ReadUInt16());
273  Mod mod = ModLoader.GetMod(modName);
274  ModPlayer modPlayer = mod == null ? null : player.GetModPlayer(mod, name);
275  if (modPlayer != null) {
276  using (MemoryStream stream = new MemoryStream(data)) {
277  using (BinaryReader customReader = new BinaryReader(stream)) {
278  try {
279  modPlayer.LoadLegacy(customReader);
280  }
281  catch (Exception e) {
282  throw new CustomModDataException(mod,
283  "Error in reading custom player data for " + mod.Name, e);
284  }
285  }
286  }
287  }
288  else {
289  var tag = new TagCompound {
290  ["mod"] = modName,
291  ["name"] = name,
292  ["legacyData"] = data
293  };
294  player.GetModPlayer<MysteryPlayer>().data.Add(tag);
295  }
296  }
297  }
298 
299  private static void LoadLegacyModBuffs(Player player, BinaryReader reader) {
300  int num = reader.ReadByte();
301  int minusIndex = 0;
302  for (int k = 0; k < num; k++) {
303  int index = reader.ReadByte() - minusIndex;
304  string modName = reader.ReadString();
305  string name = reader.ReadString();
306  int time = reader.ReadInt32();
307  Mod mod = ModLoader.GetMod(modName);
308  int type = mod == null ? 0 : mod.BuffType(name);
309  if (type > 0) {
310  for (int j = Player.MaxBuffs - 1; j > index; j--) {
311  player.buffType[j] = player.buffType[j - 1];
312  player.buffTime[j] = player.buffTime[j - 1];
313  }
314  player.buffType[index] = type;
315  player.buffTime[index] = time;
316  }
317  else {
318  minusIndex++;
319  }
320  }
321  for (int k = 1; k < Player.MaxBuffs; k++) {
322  if (player.buffType[k] > 0) {
323  int j = k - 1;
324  while (player.buffType[j] == 0) {
325  player.buffType[j] = player.buffType[j + 1];
326  player.buffTime[j] = player.buffTime[j + 1];
327  player.buffType[j + 1] = 0;
328  player.buffTime[j + 1] = 0;
329  j--;
330  }
331  }
332  }
333  }
334 
335  internal static void LoadUsedMods(Player player, IList<string> usedMods) {
336  player.usedMods = usedMods;
337  }
338 
339  internal static List<string> SaveUsedMods(Player player) {
340  return ModLoader.Mods.Select(m => m.Name).Except(new[] { "ModLoader" }).ToList();
341  }
342 
343  //add to end of Terraria.IO.PlayerFileData.MoveToCloud
344  internal static void MoveToCloud(string localPath, string cloudPath) {
345  localPath = Path.ChangeExtension(localPath, ".tplr");
346  cloudPath = Path.ChangeExtension(cloudPath, ".tplr");
347  if (File.Exists(localPath)) {
348  FileUtilities.MoveToCloud(localPath, cloudPath);
349  }
350  }
351  //add to end of Terraria.IO.PlayerFileData.MoveToLocal
352  //in Terraria.IO.PlayerFileData.MoveToLocal before iterating through map files add
353  // matchPattern = Regex.Escape(Main.CloudPlayerPath) + "/" + Regex.Escape(fileName) + "/.+\\.tmap";
354  // files.AddRange(SocialAPI.Cloud.GetFiles(matchPattern));
355  internal static void MoveToLocal(string cloudPath, string localPath) {
356  cloudPath = Path.ChangeExtension(cloudPath, ".tplr");
357  localPath = Path.ChangeExtension(localPath, ".tplr");
358  if (FileUtilities.Exists(cloudPath, true)) {
359  FileUtilities.MoveToLocal(cloudPath, localPath);
360  }
361  }
362  //add to Terraria.Player.GetFileData after moving vanilla .bak file
363  internal static void LoadBackup(string path, bool cloudSave) {
364  path = Path.ChangeExtension(path, ".tplr");
365  if (FileUtilities.Exists(path + ".bak", cloudSave)) {
366  FileUtilities.Move(path + ".bak", path, cloudSave, true);
367  }
368  }
369  //in Terraria.Main.ErasePlayer between the two try catches add
370  // PlayerIO.ErasePlayer(Main.PlayerList[i].Path, Main.PlayerList[i].IsCloudSave);
371  internal static void ErasePlayer(string path, bool cloudSave) {
372  path = Path.ChangeExtension(path, ".tplr");
373  try {
374  FileUtilities.Delete(path, cloudSave);
375  FileUtilities.Delete(path + ".bak", cloudSave);
376  }
377  catch {
378  //just copying the Terraria code which also has an empty catch
379  }
380  }
381  }
382 }