From 772da26a04eb4443bd241865b6ee20e3969f1fff Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 30 Mar 2020 13:59:41 -0700 Subject: [PATCH] Add design pattern read/write Looks like they might be indexing into the palette differently... --- NHSE.Core/Save/Files/MainSave.cs | 2 + NHSE.Core/Save/Offsets/MainSaveOffsets.cs | 30 ++++- NHSE.Core/Save/Offsets/MainSaveOffsets10.cs | 7 +- NHSE.Core/Save/Offsets/MainSaveOffsets11.cs | 7 +- NHSE.Core/Structures/Designs/DesignPattern.cs | 118 ++++++++++++++++++ NHSE.Core/Structures/GameFileDumper.cs | 17 +++ 6 files changed, 168 insertions(+), 13 deletions(-) create mode 100644 NHSE.Core/Structures/Designs/DesignPattern.cs diff --git a/NHSE.Core/Save/Files/MainSave.cs b/NHSE.Core/Save/Files/MainSave.cs index 7666315..3ff7401 100644 --- a/NHSE.Core/Save/Files/MainSave.cs +++ b/NHSE.Core/Save/Files/MainSave.cs @@ -12,6 +12,8 @@ public sealed class MainSave : EncryptedFilePair public Villager GetVillager(int index) => Offsets.ReadVillager(Data, index); public void SetVillager(Villager value, int index) => Offsets.WriteVillager(value, Data, index); + public DesignPattern GetDesign(int index) => Offsets.ReadPattern(Data, index); + public void SetDesign(DesignPattern value, int index) => Offsets.WritePattern(value, Data, index); public IReadOnlyList RecycleBin { diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets.cs index d0b84a9..7054c4f 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets.cs @@ -9,6 +9,10 @@ public abstract class MainSaveOffsets { public abstract int Villager { get; } public const int VillagerSize = 0x12AB0; + public const int VillagerCount = 10; + + public abstract int Patterns { get; } + public const int PatternCount = 50; public abstract int RecycleBin { get; } public const int RecycleBinCount = 40; @@ -25,9 +29,33 @@ public static MainSaveOffsets GetOffsets(FileHeaderInfo Info) }; } - public abstract Villager ReadVillager(byte[] data, int index); + public DesignPattern ReadPattern(byte[] data, int index) + { + if ((uint)index >= PatternCount) + throw new ArgumentOutOfRangeException(nameof(index)); + var v = data.Slice(Patterns + (index * DesignPattern.SIZE), DesignPattern.SIZE); + return new DesignPattern(v); + } + + public void WritePattern(DesignPattern p, byte[] data, int index) + { + if ((uint)index >= PatternCount) + throw new ArgumentOutOfRangeException(nameof(index)); + p.Data.CopyTo(data, Patterns + (index * DesignPattern.SIZE)); + } + + public Villager ReadVillager(byte[] data, int index) + { + if ((uint)index >= VillagerCount) + throw new ArgumentOutOfRangeException(nameof(index)); + var v = data.Slice(Villager + (index * VillagerSize), VillagerSize); + return new Villager(v); + } + public void WriteVillager(Villager v, byte[] data, int index) { + if ((uint)index >= VillagerCount) + throw new ArgumentOutOfRangeException(nameof(index)); v.Data.CopyTo(data, Villager + (index * VillagerSize)); } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets10.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets10.cs index d662339..808e9ee 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets10.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets10.cs @@ -6,12 +6,7 @@ public class MainSaveOffsets10 : MainSaveOffsets { public override int Villager => 0x110; + public override int Patterns => 0x1D72F0; public override int RecycleBin => 0xABC000; // yep. - - public override Villager ReadVillager(byte[] data, int index) - { - var v = data.Slice(Villager + (index * VillagerSize), VillagerSize); - return new Villager(v); - } } } \ No newline at end of file diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets11.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets11.cs index 0dec39a..a7e9773 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets11.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets11.cs @@ -6,12 +6,7 @@ public class MainSaveOffsets11 : MainSaveOffsets { public override int Villager => 0x120; + public override int Patterns => 0x1D7310; // +0x20 from v1.0 public override int RecycleBin => 0xABDE70; // +0x1E70 from v1.0 - - public override Villager ReadVillager(byte[] data, int index) - { - var v = data.Slice(Villager + (index * VillagerSize), VillagerSize); - return new Villager(v); - } } } \ No newline at end of file diff --git a/NHSE.Core/Structures/Designs/DesignPattern.cs b/NHSE.Core/Structures/Designs/DesignPattern.cs new file mode 100644 index 0000000..196711e --- /dev/null +++ b/NHSE.Core/Structures/Designs/DesignPattern.cs @@ -0,0 +1,118 @@ +using System; + +namespace NHSE.Core +{ + public class DesignPattern : IVillagerOrigin + { + public const int Width = 32; + public const int Height = 32; + + public const int SIZE = 0x2A8; // 3 bytes unused at end + private const int PersonalOffset = 0x38; + private const int PaletteDataStart = 0x78; + public const int PaletteColorCount = 15; // y not 16??? + private const int PaletteColorSize = 3; // R, G, B + private const int PixelDataOffset = PaletteDataStart + (PaletteColorCount * PaletteColorSize); // 0xA5 + private const int PixelCount = 0x400; // Width * Height + //private const int PixelDataSize = PixelCount / 2; // 4bit|4bit pixel packing + + public readonly byte[] Data; + + public DesignPattern(byte[] data) => Data = data; + + public string DesignName + { + get => StringUtil.GetString(Data, 0x10, 20); + set => StringUtil.GetBytes(value, 20).CopyTo(Data, 0x10); + } + + public uint TownID + { + get => BitConverter.ToUInt32(Data, PersonalOffset); + set => BitConverter.GetBytes(value).CopyTo(Data, PersonalOffset); + } + + public string TownName + { + get => StringUtil.GetString(Data, PersonalOffset + 0x04, 10); + set => StringUtil.GetBytes(value, 10).CopyTo(Data, PersonalOffset + 0x04); + } + + public byte[] GetTownIdentity() => Data.Slice(PersonalOffset + 0x00, 4 + 20); + + public uint PlayerID + { + get => BitConverter.ToUInt32(Data, PersonalOffset + 0x1C); + set => BitConverter.GetBytes(value).CopyTo(Data, PersonalOffset + 0x1C); + } + + public string PlayerName + { + get => StringUtil.GetString(Data, PersonalOffset + 0x20, 10); + set => StringUtil.GetBytes(value, 10).CopyTo(Data, PersonalOffset + 0x20); + } + + public byte[] GetPlayerIdentity() => Data.Slice(PersonalOffset + 0x1C, 4 + 20); + + /// + /// Gets/Sets the color choice (1-15) for the pixel at the given . + /// + /// Pixel index + public int this[int index] + { + get + { + var ofs = PixelDataOffset + (index / 2); + var val = Data[ofs]; + return (index & 1) == 0 ? (val & 0x0F) : (val >> 4); + } + set + { + var ofs = PixelDataOffset + (index / 2); + var val = Data[ofs]; + var update = ((index & 1) == 0) + ? (val & 0xF0) | (value & 0xF) + : (value & 0xF) << 4 | (val & 0xF); + Data[ofs] = (byte)update; + } + } + + public static int GetPixelIndex(int x, int y) => (y * Height) + x; + + public int GetPixel(int x, int y) + { + if ((uint)x >= Width) + throw new ArgumentException($"Argument out of range (0-{Width})", nameof(x)); + if ((uint)y >= Height) + throw new ArgumentException($"Argument out of range (0-{Height})", nameof(y)); + + var index = GetPixelIndex(x, y); + return this[index]; + } + + public static int GetColorOffset(int index) + { + if ((uint)index >= PaletteColorCount) + throw new ArgumentException($"Argument out of range (0-{PaletteColorCount})", nameof(index)); + return PaletteDataStart + (index * PaletteColorSize); + } + + public byte[] GetBitmap() + { + byte[] data = new byte[4 * Width * Height]; + for (int i = 0; i < PixelCount; i++) + { + var choice = this[i]; + if (choice == PaletteColorCount) + continue; // transparent? + var palette = GetColorOffset(choice); + var ofs = i * 4; + Array.Copy(Data, palette, data, ofs, 4); + data[ofs + 3] = 0xFF; // opaque + } + return data; + } + + public byte[] GetPaletteBitmap() => Data.Slice(PaletteDataStart, PaletteColorCount * PaletteColorSize); + } +} diff --git a/NHSE.Core/Structures/GameFileDumper.cs b/NHSE.Core/Structures/GameFileDumper.cs index 8459244..e1c414c 100644 --- a/NHSE.Core/Structures/GameFileDumper.cs +++ b/NHSE.Core/Structures/GameFileDumper.cs @@ -66,5 +66,22 @@ public static void DumpVillagers(this MainSave sav, string path) File.WriteAllBytes(dest, v.Data); } } + + /// + /// Dumps all villagers in their decrypted state to the requested . + /// + /// Save Data to dump from + /// Path to dump to + public static void DumpDesigns(this MainSave sav, string path) + { + for (int i = 0; i < 10; i++) + { + var dp = sav.GetDesign(i); + var name = dp.DesignName; + var fn = StringUtil.CleanFileName($"{name}.nhd"); + var dest = Path.Combine(path, fn); + File.WriteAllBytes(dest, dp.Data); + } + } } }