public void Write() { // Set City Folk Bed first if (_saveData.SaveGeneration == SaveGeneration.Wii && House != null && Data.Bed != null) { House.Data.Bed = Data.Bed; Data.Bed = null; } var playerSaveInfoType = typeof(PlayerSaveInfo); var playerDataType = typeof(PlayerData); foreach (var field in playerSaveInfoType.GetFields(BindingFlags.Public | BindingFlags.Instance)) { if (field.GetValue(Offsets) == null || field.Name.Contains("Count") || field.Name.Contains("Size")) { continue; } if (playerDataType.GetField(field.Name) == null) { continue; } if (field.FieldType != typeof(int) || (int)field.GetValue(Offsets) == -1) { continue; } var fieldType = typeof(PlayerData).GetField(field.Name).FieldType; var dataOffset = Offset + (int)field.GetValue(Offsets); switch (field.Name) { case "TownPassCardImage" when _saveData.SaveGeneration == SaveGeneration.N3DS: _saveData.Write(dataOffset, Data.TownPassCardData); break; case "Reset" when _saveData.SaveGeneration == SaveGeneration.GCN: _saveData.Write(dataOffset, Data.Reset ? (uint)0x250C : (uint)0, _saveData.IsBigEndian); break; default: if (fieldType == typeof(string)) { _saveData.Write(dataOffset, AcString.GetBytes((string)playerDataType.GetField(field.Name).GetValue(Data), (int)playerSaveInfoType.GetField(field.Name + "Size").GetValue(Offsets))); } else if (fieldType == typeof(byte)) { if (_saveData.SaveType == SaveType.WildWorld) { switch (field.Name) { case "HairColor": { var condensedData = (byte)(Data.HairColor & 0x0F); //Remove upper nibble just incase condensedData += (byte)((Data.Tan & 0x0F) << 4); //Add in tan to the byte _saveData.Write(dataOffset, condensedData); break; } case "HairType": { var condensedData = (byte)(Data.FaceType & 0x0F); condensedData += (byte)((Data.HairType & 0x0F) << 4); _saveData.Write(dataOffset, condensedData); break; } default: _saveData.Write(dataOffset, (byte)playerDataType.GetField(field.Name).GetValue(Data)); break; } } else if (_saveData.SaveType == SaveType.CityFolk) { switch (field.Name) { case "Tan": var shiftedData = (byte)(Data.Tan << 1); //ACToolkit does this _saveData.Write(dataOffset, shiftedData); break; case "FaceType": _saveData.Write(dataOffset, (byte)(Data.EyeColor + Data.FaceType)); break; default: _saveData.Write(dataOffset, (byte)playerDataType.GetField(field.Name).GetValue(Data)); break; } } else { _saveData.Write(dataOffset, (byte)playerDataType.GetField(field.Name).GetValue(Data)); } } else if (fieldType == typeof(ushort)) { _saveData.Write(dataOffset, (ushort)playerDataType.GetField(field.Name).GetValue(Data), _saveData.IsBigEndian); } else if (fieldType == typeof(uint)) { _saveData.Write(dataOffset, (uint)playerDataType.GetField(field.Name).GetValue(Data), _saveData.IsBigEndian); } else if (fieldType == typeof(Inventory)) { if (_saveData.SaveGeneration == SaveGeneration.N3DS) { var items = new uint[Offsets.PocketsCount]; for (var i = 0; i < items.Length; i++) { items[i] = ItemData.EncodeItem(Data.Pockets.Items[i]); } _saveData.Write(dataOffset, items); } else { var items = new ushort[Offsets.PocketsCount]; for (var i = 0; i < items.Length; i++) { items[i] = Data.Pockets.Items[i].ItemId; } _saveData.Write(dataOffset, items, _saveData.IsBigEndian); } } else if (fieldType == typeof(Item)) { var item = (Item)playerDataType.GetField(field.Name).GetValue(Data); if (_saveData.SaveGeneration == SaveGeneration.N3DS) { _saveData.Write(dataOffset, item.ToUInt32()); } else { if (field.Name.Equals("Shirt") && (_saveData.SaveGeneration == SaveGeneration.N64 || _saveData.SaveGeneration == SaveGeneration.GCN || _saveData.SaveGeneration == SaveGeneration.iQue)) // For some reason, the shirt lower byte is also stored before the actual item id. { _saveData.Write(dataOffset - 1, new[] { (byte)item.ItemId, (byte)(item.ItemId >> 8), (byte)item.ItemId }, _saveData.IsBigEndian); } else { _saveData.Write(dataOffset, item.ItemId, _saveData.IsBigEndian); } } } else if (fieldType == typeof(Item[])) { var itemArray = (Item[])playerDataType.GetField(field.Name).GetValue(Data); if (field.Name.Equals("Dressers")) { switch (_saveData.SaveGeneration) { case SaveGeneration.NDS: dataOffset = _saveData.SaveDataStartOffset + 0x15430 + 0xB4 * Index; // Terrible hack break; case SaveGeneration.Wii: dataOffset = _saveData.SaveDataStartOffset + 0x1F3038 + 0x140 * Index; break; } } for (var i = 0; i < itemArray.Length; i++) { if (_saveData.SaveGeneration == SaveGeneration.N3DS) { _saveData.Write(dataOffset + i * 4, itemArray[i].ToUInt32()); } else { _saveData.Write(dataOffset + i * 2, itemArray[i].ItemId, _saveData.IsBigEndian); } } } else if (fieldType == typeof(NewLeafInt32)) { if (_saveData.SaveGeneration == SaveGeneration.NDS) { var encryptedInt = (NewLeafInt32)playerDataType.GetField(field.Name).GetValue(Data); _saveData.Write(dataOffset, encryptedInt.Int1); _saveData.Write(dataOffset + 4, encryptedInt.Int2); } } else if (fieldType == typeof(AcDate) && (_saveData.SaveGeneration == SaveGeneration.GCN || _saveData.SaveGeneration == SaveGeneration.NDS || _saveData.SaveGeneration == SaveGeneration.Wii)) { if (field.Name.Equals("Birthday")) { _saveData.Write(dataOffset, ((AcDate)playerDataType.GetField(field.Name).GetValue(Data)).ToMonthDayDateData()); } } break; } } }
public virtual void Write() { var saveData = Save.SaveInstance; var offsets = HouseInfo.GetHouseOffsets(saveData.SaveType); // Set House TownID & Name if (Owner != null) { if (offsets.OwningPlayerName != -1 && offsets.TownId != -1) { Data.TownId = saveData.ReadUInt16(saveData.SaveDataStartOffset + Save.SaveInstance.SaveInfo.SaveOffsets.TownId, saveData.IsBigEndian); // Might not be UInt16 in all games } if (offsets.OwningPlayerName != -1 && offsets.TownName != -1) { Data.TownName = saveData.ReadString( saveData.SaveDataStartOffset + Save.SaveInstance.SaveInfo.SaveOffsets.TownName, Save.SaveInstance.SaveInfo.SaveOffsets.TownNameSize); } if (offsets.OwningPlayerName != -1) { Data.OwningPlayerName = Owner.Data.Name; } if (offsets.OwningPlayerId != -1) { Data.OwningPlayerId = Owner.Data.Identifier; } } var houseOffsetData = typeof(HouseOffsets); var houseDataType = typeof(HouseData); foreach (var field in houseOffsetData.GetFields(BindingFlags.Public | BindingFlags.Instance)) { if (field.GetValue(offsets) == null || field.Name.Contains("Count") || field.Name.Contains("Size")) { continue; } if (houseDataType.GetField(field.Name) == null) { continue; } if (field.FieldType != typeof(int) || (int)field.GetValue(offsets) == -1) { continue; } var fieldType = houseDataType.GetField(field.Name).FieldType; var dataOffset = Offset + (int)field.GetValue(offsets); //MessageBox.Show("Field Name: " + Field.Name + " | Data Offset: " + DataOffset.ToString("X")); if (fieldType == typeof(string)) { saveData.Write(dataOffset, AcString.GetBytes((string)houseDataType.GetField(field.Name).GetValue(Data), (int)houseOffsetData.GetField(field.Name + "Size").GetValue(offsets))); } else if (fieldType == typeof(byte)) { saveData.Write(dataOffset, (byte)houseDataType.GetField(field.Name).GetValue(Data)); } else if (fieldType == typeof(byte[])) { saveData.Write(dataOffset, (byte[])houseDataType.GetField(field.Name).GetValue(Data)); } else if (fieldType == typeof(ushort)) { saveData.Write(dataOffset, (ushort)houseDataType.GetField(field.Name).GetValue(Data), saveData.IsBigEndian); } else if (fieldType == typeof(Item)) { if (saveData.SaveGeneration == SaveGeneration.N3DS) { saveData.Write(dataOffset, ItemData.EncodeItem((Item)houseDataType.GetField(field.Name).GetValue(Data)), saveData.IsBigEndian); } else { saveData.Write(dataOffset, ((Item)houseDataType.GetField(field.Name).GetValue(Data)).ItemId, saveData.IsBigEndian); } } } foreach (var r in Data.Rooms) { r.Write(); } }
public void Write(string townName) { // Set Villager TownID & Name when villager exists. if (Exists && _saveData.SaveGeneration != SaveGeneration.iQue) // TODO: Once iQue text is implemented, remove this. { if (Offsets.TownId != -1) { Data.TownId = _saveData.ReadUInt16( _saveData.SaveDataStartOffset + Save.SaveInstance.SaveInfo.SaveOffsets.TownId, _saveData.IsBigEndian); // Might not be UInt16 in all games } if (!string.IsNullOrWhiteSpace(townName) && Offsets.TownName != -1) { Data.TownName = townName; } } if (!Exists) { return; // TODO: Do I need this? it overwrites important stuff in Animal Forest iQue if I don't have it. } var villagerOffsetData = typeof(VillagerOffsets); var villagerDataType = typeof(VillagerDataStruct); foreach (var field in villagerOffsetData.GetFields(BindingFlags.Public | BindingFlags.Instance)) { if (field.GetValue(Offsets) == null || field.Name.Contains("Count") || field.Name.Contains("Size")) { continue; } if (villagerDataType.GetField(field.Name) == null) { continue; } if (field.FieldType != typeof(int) || (int)field.GetValue(Offsets) == -1) { continue; } var fieldType = villagerDataType.GetField(field.Name).FieldType; var dataOffset = Offset + (int)field.GetValue(Offsets); dynamic dataObject = villagerDataType.GetField(field.Name).GetValue(Data); switch (field.Name) { case "VillagerId" when _saveData.SaveType == SaveType.WildWorld: _saveData.Write(dataOffset, Convert.ToByte(dataObject)); break; //Might not encompass City Folk case "VillagerId": _saveData.Write(dataOffset, dataObject, _saveData.IsBigEndian); break; default: if (fieldType == typeof(string)) { _saveData.Write(dataOffset, AcString.GetBytes(dataObject, (int)villagerOffsetData.GetField(field.Name + "Size").GetValue(Offsets))); } else if (fieldType == typeof(byte)) { _saveData.Write(dataOffset, (byte)dataObject); } else if (fieldType == typeof(ushort)) { _saveData.Write(dataOffset, (ushort)dataObject, _saveData.IsBigEndian); } else if (fieldType == typeof(uint)) { _saveData.Write(dataOffset, (uint)dataObject, _saveData.IsBigEndian); } else if (fieldType == typeof(byte[])) { _saveData.Write(dataOffset, dataObject, false); } else if (fieldType == typeof(Item)) { switch (_saveData.SaveGeneration) { case SaveGeneration.N3DS: _saveData.Write(dataOffset, ItemData.EncodeItem((Item)dataObject)); break; default: _saveData.Write(dataOffset, ((Item)dataObject).ItemId, _saveData.IsBigEndian); break; } } else if (fieldType == typeof(Item[])) { if (dataObject is Item[] collection) { for (var i = 0; i < collection.Length; i++) { switch (_saveData.SaveGeneration) { case SaveGeneration.N3DS: _saveData.Write(dataOffset + i * 4, ItemData.EncodeItem(collection[i])); break; default: _saveData.Write(dataOffset + i * 2, collection[i].ItemId, _saveData.IsBigEndian); break; } } } } else if (!fieldType.IsClass) // Don't write classes. If we're here, the that means that we haven't handled saving the class. { _saveData.Write(dataOffset, dataObject, _saveData.IsBigEndian); } break; } } // Write AnimalMemories if (Exists && AnimalMemories != null) { foreach (var relation in AnimalMemories) { if (relation.Exists) { relation.Write(); } } } // Write the NameId for N64/GC/iQue games, & name for e+. TODO: Check if Wild World also has this. if (_saveData.SaveGeneration != SaveGeneration.N64 && _saveData.SaveGeneration != SaveGeneration.GCN && _saveData.SaveGeneration != SaveGeneration.iQue) { return; } switch (_saveData.SaveType) { case SaveType.DoubutsuNoMoriPlus: case SaveType.AnimalCrossing: _saveData.Write(Offset + Offsets.NameId, Index == 15 ? (byte)0xFF : (byte)Data.VillagerId); break; case SaveType.DoubutsuNoMoriEPlus: // e+ doesn't set this byte, as they changed the SetNameID function _saveData.Write(Offset + 0xC, AcString.GetBytes(Name, 6), false); break; case SaveType.AnimalForestEPlus: _saveData.Write(Offset + 0xC, AcString.GetBytes(Name, 8), false); break; default: _saveData.Write(Offset + Offsets.NameId, (byte)Data.VillagerId); break; } }