Replace player/town name on edit

Replaces a byte sequence with the updated sequence of data, using the player/town ID as a prefix to ensure that short names can be changed correctly without affecting other game data (hopefully).
This commit is contained in:
Kurt 2020-03-29 12:41:27 -07:00
parent 786aad69f0
commit 65fca3cbba
6 changed files with 126 additions and 54 deletions

View File

@ -10,7 +10,7 @@ public sealed class Personal : EncryptedFilePair
{
public readonly PersonalOffsets Offsets;
public Personal(string folder) : base(folder, "personal") => Offsets = PersonalOffsets.GetOffsets(Info);
public override string ToString() => Name;
public override string ToString() => PlayerName;
public uint TownID
{
@ -24,18 +24,22 @@ public string TownName
set => GetBytes(value, 10).CopyTo(Data, Offsets.PersonalId + 0x04);
}
public byte[] GetTownIdentity() => Data.Slice(Offsets.PersonalId + 0x00, 4 + 20);
public uint PlayerID
{
get => BitConverter.ToUInt32(Data, Offsets.PersonalId + 0x1C);
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.PersonalId + 0x1C);
}
public string Name
public string PlayerName
{
get => GetString(Offsets.PersonalId + 0x20, 10);
set => GetBytes(value, 10).CopyTo(Data, Offsets.PersonalId + 0x20);
}
public byte[] GetPlayerIdentity() => Data.Slice(Offsets.PersonalId + 0x1C, 4 + 20);
public EncryptedInt32 Wallet
{
get => EncryptedInt32.Read(Data, Offsets.Wallet);

View File

@ -28,8 +28,11 @@ public void Save(uint seed)
Main.Save(seed);
foreach (var player in Players)
{
player.Hash();
player.Save(seed);
foreach (var pair in player)
{
pair.Hash();
pair.Save(seed);
}
}
}
@ -42,7 +45,17 @@ public void Save(uint seed)
/// </remarks>
public IEnumerable<FileHashRegion> GetInvalidHashes()
{
return Main.InvalidHashes().Concat(Players.SelectMany(z => z.InvalidHashes()));
foreach (var hash in Main.InvalidHashes())
yield return hash;
foreach (var hash in Players.SelectMany(z => z).SelectMany(z => z.InvalidHashes()))
yield return hash;
}
public void ChangeIdentity(byte[] original, byte[] updated)
{
Main.Data.ReplaceOccurrences(original, updated);
foreach (var pair in Players.SelectMany(z => z))
pair.Data.ReplaceOccurrences(original, updated);
}
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -7,7 +8,7 @@ namespace NHSE.Core
/// <summary>
/// Stores references for all files in the Villager (<see cref="DirectoryName"/>) folder.
/// </summary>
public sealed class Player
public sealed class Player : IEnumerable<EncryptedFilePair>
{
public readonly Personal Personal;
public readonly PhotoStudioIsland Photo;
@ -15,7 +16,10 @@ public sealed class Player
public readonly Profile Profile;
public readonly string DirectoryName;
public override string ToString() => Personal.Name;
public IEnumerator<EncryptedFilePair> GetEnumerator() => new EncryptedFilePair[] {Personal, Photo, PostBox, Profile}.AsEnumerable().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public override string ToString() => Personal.PlayerName;
public static Player[] ReadMany(string folder)
{
@ -35,43 +39,5 @@ private Player(string folder)
PostBox = new PostBox(folder);
Profile = new Profile(folder);
}
/// <summary>
/// Saves the data using the provided crypto <see cref="seed"/>.
/// </summary>
/// <param name="seed">Seed to initialize the RNG with when encrypting the files.</param>
public void Save(uint seed)
{
Personal.Save(seed);
Photo.Save(seed);
PostBox.Save(seed);
Profile.Save(seed);
}
/// <summary>
/// Updates all hashes of the Player's files.
/// </summary>
public void Hash()
{
Personal.Hash();
Photo.Hash();
PostBox.Hash();
Profile.Hash();
}
/// <summary>
/// Gets every <see cref="FileHashRegion"/> that is deemed invalid.
/// </summary>
/// <remarks>
/// Doesn't return any metadata about which file the hashes were bad for.
/// Just check what's returned with what's implemented; the offsets are unique enough.
/// </remarks>
public IEnumerable<FileHashRegion> InvalidHashes()
{
return Personal.InvalidHashes()
.Concat(Photo.InvalidHashes())
.Concat(PostBox.InvalidHashes())
.Concat(Profile.InvalidHashes());
}
}
}

View File

@ -1,4 +1,5 @@
using System.Text;
using System;
using System.Text;
namespace NHSE.Core
{
@ -25,6 +26,36 @@ public VillagerPersonality Personality
set => Data[2] = (byte)value;
}
public uint TownID
{
get => BitConverter.ToUInt32(Data, 0x04);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x04);
}
public string TownName
{
get => GetString(0x08, 10);
set => GetBytes(value, 10).CopyTo(Data, 0x08);
}
public uint PlayerID
{
get => BitConverter.ToUInt32(Data, 0x20);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x20);
}
public string PlayerName
{
get => GetString(0x24, 10);
set => GetBytes(value, 10).CopyTo(Data, 0x24);
}
public string TownName2
{
get => GetString(0x5CC, 10);
set => GetBytes(value, 10).CopyTo(Data, 0x5CC);
}
public string CatchPhrase
{
get => GetString(0x10014, 2 * 12);

View File

@ -178,5 +178,52 @@ internal static T[] ConcatAll<T>(params T[][] arr)
return result;
}
/// <summary>
/// Finds a provided <see cref="pattern"/> within the supplied <see cref="array"/>.
/// </summary>
/// <param name="array">Array to look in</param>
/// <param name="pattern">Pattern to look for</param>
/// <param name="startIndex">Starting offset to look from</param>
/// <param name="length">Amount of entries to look through</param>
/// <returns>Index the pattern occurrs at; if not found, returns -1.</returns>
public static int IndexOfBytes(byte[] array, byte[] pattern, int startIndex = 0, int length = -1)
{
int len = pattern.Length;
int endIndex = length > 0
? startIndex + length
: array.Length - len - startIndex;
endIndex = Math.Min(array.Length - pattern.Length, endIndex);
int i = startIndex;
int j = 0;
while (true)
{
if (pattern[j] != array[i + j])
{
if (++i == endIndex)
return -1;
j = 0;
}
else if (++j == len)
{
return i;
}
}
}
public static int ReplaceOccurrences(this byte[] array, byte[] pattern, byte[] swap)
{
int count = 0;
while (true)
{
int ofs = IndexOfBytes(array, pattern);
if (ofs == -1)
return count;
swap.CopyTo(array, ofs);
++count;
}
}
}
}

View File

@ -150,7 +150,7 @@ private void LoadPlayer(int index)
var player = SAV.Players[index];
var pers = player.Personal;
TB_Name.Text = pers.Name;
TB_Name.Text = pers.PlayerName;
TB_TownName.Text = pers.TownName;
NUD_BankBells.Value = pers.Bank.Value;
NUD_NookMiles.Value = pers.NookMiles.Value;
@ -170,8 +170,21 @@ private void SavePlayer(int index)
var player = SAV.Players[index];
var pers = player.Personal;
pers.Name = TB_Name.Text;
pers.TownName = TB_TownName.Text;
if (pers.PlayerName != TB_Name.Text)
{
var orig = pers.GetPlayerIdentity();
pers.PlayerName = TB_Name.Text;
var updated = pers.GetPlayerIdentity();
SAV.ChangeIdentity(orig, updated);
}
if (pers.TownName != TB_Name.Text)
{
var orig = pers.GetTownIdentity();
pers.TownName = TB_TownName.Text;
var updated = pers.GetTownIdentity();
SAV.ChangeIdentity(orig, updated);
}
var bank = pers.Bank;
bank.Value = (uint)NUD_BankBells.Value;
@ -189,7 +202,6 @@ private void SavePlayer(int index)
#region Villager Editing
private Villager? villager;
private int VillagerIndex = -1;
private void LoadVillager(int index)
@ -208,12 +220,11 @@ private void LoadVillager(int index)
TB_Catchphrase.Text = v.CatchPhrase;
VillagerIndex = index;
villager = v;
}
private void SaveVillager(int index)
{
var v = villager;
var v = SAV.Main.GetVillager(index);
if (v is null)
return;
@ -248,7 +259,7 @@ private void Menu_SavePNG_Click(object sender, EventArgs e)
string name;
if (pb == PB_Player)
name = SAV.Players[PlayerIndex].Personal.Name;
name = SAV.Players[PlayerIndex].Personal.PlayerName;
else if (pb == PB_Villager)
name = L_ExternalName.Text;
else