Add design pattern read/write

Looks like they might be indexing into the palette differently...
This commit is contained in:
Kurt
2020-03-30 13:59:41 -07:00
parent 447916e120
commit 772da26a04
6 changed files with 168 additions and 13 deletions

View File

@@ -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<Item> RecycleBin
{

View File

@@ -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));
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
/// <summary>
/// Gets/Sets the color choice (1-15) for the pixel at the given <see cref="index"/>.
/// </summary>
/// <param name="index">Pixel index</param>
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);
}
}

View File

@@ -66,5 +66,22 @@ public static void DumpVillagers(this MainSave sav, string path)
File.WriteAllBytes(dest, v.Data);
}
}
/// <summary>
/// Dumps all villagers in their decrypted state to the requested <see cref="path"/>.
/// </summary>
/// <param name="sav">Save Data to dump from</param>
/// <param name="path">Path to dump to</param>
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);
}
}
}
}