tModLoader v0.11.8.9
A mod to make and play Terraria mods
PlayerIO.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Linq;
5using System.Security.Cryptography;
6using Terraria.Graphics.Shaders;
7using Terraria.ModLoader.Default;
8using Terraria.ModLoader.Engine;
10using Terraria.Social;
11using Terraria.Utilities;
12
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}
This serves as the central class from which buff-related functions are supported and carried out.
Definition: BuffLoader.cs:15
static ModBuff GetBuff(int type)
Gets the ModBuff instance with the given type. If no ModBuff with the given type exists,...
Definition: BuffLoader.cs:77
This serves as the central class from which item-related functions are carried out....
Definition: ItemLoader.cs:22
static ModItem GetItem(int type)
Gets the ModItem instance corresponding to the specified type. Returns null if no modded item has the...
Definition: ItemLoader.cs:76
static bool NeedsModSaving(Item item)
Definition: ItemLoader.cs:1754
Manages content added by mods. Liasons between mod content and Terraria's arrays and oversees the Loa...
Definition: ModContent.cs:27
static void SplitName(string name, out string domain, out string subName)
Definition: ModContent.cs:30
Mod is an abstract class that you will override. It serves as a central place from which the mod's co...
Definition: Mod.cs:25
int BuffType(string name)
Gets the type of the ModBuff of this mod corresponding to the given name. Returns 0 if no ModBuff wit...
ModItem GetItem(string name)
Gets the ModItem instance corresponding to the name. Because this method is in the Mod class,...
virtual string Name
Stores the name of the mod. This name serves as the mod's identification, and also helps with saving ...
Definition: Mod.cs:42
virtual void Load()
Override this method to add most of your content to your mod. Here you will call other methods such a...
Definition: Mod.cs:71
string Name
The internal name of this ModItem.
Definition: ModItem.cs:45
This serves as the central class which loads mods. It contains many static fields and methods related...
Definition: ModLoader.cs:29
static Mod GetMod(string name)
Gets the instance of the Mod with the specified name.
Definition: ModLoader.cs:90
A ModPlayer instance represents an extension of a Player instance. You can store fields in the ModPla...
Definition: ModPlayer.cs:16
virtual void LoadLegacy(BinaryReader reader)
Allows you to load pre-v0.9 custom data you have saved for this player.
Definition: ModPlayer.cs:114