Add more event block repo types

Allow bigger window
This commit is contained in:
Kurt
2025-10-29 16:34:10 -05:00
parent 4c2cc2f45f
commit 702829bb20
5 changed files with 915 additions and 241 deletions

View File

@@ -34,6 +34,14 @@ public sealed class SaveBlockAccessor9ZA(SAV9ZA sav) : SCBlockAccessor
public EventWorkValueStorage WorkSpawn { get; } = new(sav, Block(sav, KEventWorkSpawn));
public EventWorkFlagStorage Flags { get; } = new(sav, Block(sav, KEventFlagsOther));
public EventWorkValueStorageKey128 Report { get; } = new(sav, Block(sav, KEventReport));
public EventWorkValueStorageKey128 Obstruction { get; } = new(sav, Block(sav, KObstruction));
public EventWorkValueStorageKey192 FieldObjectInteractable { get; } = new(sav, Block(sav, KFieldObjectInteractable));
public EventWorkValueStorageKey128 Spawner2 { get; } = new(sav, Block(sav, KEventSpawner2)); // (u64-key, u64-bool, u64-struct)
public EventWorkValueStorage InfiniteRank { get; } = new(sav, Block(sav, KEventInfiniteRank)); // (u64-key, u64-struct)
public EventWorkValueStorageKey128 Spawner4 { get; } = new(sav, Block(sav, KEventSpawner4)); // (u64-key, u64-hash, u64-struct)
public MableStatus9a Mable { get; } = new(sav, Block(sav, KStatusMable));
private const uint KBox = 0x0d66012c; // Box Data
@@ -64,12 +72,15 @@ public sealed class SaveBlockAccessor9ZA(SAV9ZA sav) : SCBlockAccessor
private const uint KEventCountTitle = 0x2C2C6964; // title_count (u64,u64)[64] - Player earned display titles
private const uint KEventWorkSpawn = 0x53FD0223; // Overworld Spawner 0x46500 small values (u64,u64)[18000]
// 7C896A83 0x2000 unused
// B25E7EE5 0x400 unused
// AF2165F0 0x3000 (u64,u64,value)
private const uint KEventSpawner2 = 0x79ABCB0B; // (u64-key, u64-bool, u64-struct)
private const uint KEventInfiniteRank = 0x7C896A83; // (u64-key, u64-struct)
private const uint KEventSpawner4 = 0xD1A3FF7B; // (u64-key, u64-hash, u64-struct)
// B25E7EE5 0x400 unused
private const uint KEventReport = 0xAF2165F0; // 0x3000 (u64,(s64,u64) value)
private const uint KObstruction = 0x4C26C29B; // (u64, u64-state, u64-unused)[2000]
private const uint KFieldObjectInteractable = 0x7147C953; // (u64,u64,u64,value)[5000] (mega crystal, prize medals)
private const uint KObstruction = 0x4C26C29B; // (u64, u64-state, u64-unused)[2000]
public const uint KTicketPointsZARoyale = 0x9A730DE1; // u32
public const uint KTicketPointsZARoyaleInfinite = 0x1D7EE369; // u32

View File

@@ -5,29 +5,41 @@
namespace PKHeX.Core;
/// <summary>
/// Storage for event work values keyed by a 64-bit FNV-1a hash.
/// Generic storage for event work values keyed by a fixed-size key and backed by a 64-bit value field.
/// </summary>
/// <typeparam name="T">Unmanaged value type stored alongside the hash. Backed by a 64-bit field.</typeparam>
/// <typeparam name="TKey">Key type. Implementations serialize/deserialize to a fixed-size number of 64-bit chunks.</typeparam>
/// <typeparam name="TValue">Unmanaged value type stored alongside the key. Backed by a 64-bit field.</typeparam>
/// <param name="sav">Owning save file.</param>
/// <param name="block">Underlying block providing the backing bytes.</param>
public abstract class EventWorkStorage64<T>(SaveFile sav, SCBlock block) : SaveBlock<SaveFile>(sav, block.Raw),
IEventValueStorage<T> where T : struct, IEquatable<T>
public abstract class EventWorkStorage<TKey, TValue>(SaveFile sav, SCBlock block) : SaveBlock<SaveFile>(sav, block.Raw)
where TKey : struct, IEquatable<TKey>
where TValue : struct, IEquatable<TValue>
{
// Structure:
// entry[] of u64 hash, T value
// T is backed by a separate u64
// Essentially, it is
// (u64, u64)[Count];
// Empty entries use an empty hash value instead of 0.
// entry[] of key(u64 * N), value(u64)
// Empty entries use an implementation-defined empty key value.
// Default state is all entries initialized to "empty".
/// <summary>
/// Size in bytes of a single entry: 8 bytes for the key hash + 8 bytes for the value.
/// </summary>
public const int ENTRY_SIZE = 16;
private const ulong HashEmpty = FnvHash.HashEmpty;
/// <summary> Number of 64-bit chunks used to serialize a key. </summary>
protected abstract int KeyU64Count { get; }
/// <summary> Gets the empty key sentinel. </summary>
protected abstract TKey EmptyKey { get; }
/// <summary> Reads the key at the specified index. </summary>
protected abstract TKey ReadKey(int index);
/// <summary> Writes the key at the specified index. </summary>
protected abstract void WriteKey(int index, TKey key);
/// <summary> Compares two keys for sorting. Return same semantics as <see cref="IComparer{T}.Compare(T, T)"/>. </summary>
protected abstract int CompareKeys(TKey a, TKey b);
/// <summary> Size in bytes of a single entry: key bytes (8 * KeyU64Count) + 8 bytes for the value. </summary>
protected int EntrySize => (KeyU64Count * sizeof(ulong)) + sizeof(ulong);
public int Count => Data.Length / EntrySize;
public int Count => Data.Length / ENTRY_SIZE;
public int CountUsed => ScanCountUsed(Data);
private int ScanCountUsed(ReadOnlySpan<byte> data)
@@ -35,61 +47,44 @@ private int ScanCountUsed(ReadOnlySpan<byte> data)
int count = 0;
for (int i = 0; i < Count; i++)
{
var hash = ReadUInt64LittleEndian(data[GetOffset(i)..]);
if (hash == 0)
var key = ReadKey(i);
if (IsEmpty(key))
break;
count++;
}
return count;
}
private static ulong Hash(ReadOnlySpan<char> name) => FnvHash.HashFnv1a_64(name);
public bool HasSpace(out int index) => (index = CountUsed) < Count;
public bool HasSpace() => HasSpace(out _);
public bool TryGetValue(ReadOnlySpan<char> name, out T value) => TryGetValue(Hash(name), out value);
public bool TryGetValue(ulong hash, out T value)
{
var index = GetIndex(hash);
if (index == -1)
{
value = default;
return false;
}
value = GetValue(index);
return true;
}
protected int GetOffset(int index)
{
if ((uint)index > Count)
throw new ArgumentOutOfRangeException(nameof(index), index, null);
return index * ENTRY_SIZE;
return index * EntrySize;
}
public int GetIndex(ulong hash)
protected bool IsEmpty(TKey key) => EqualityComparer<TKey>.Default.Equals(key, EmptyKey);
public bool HasSpace(out int index) => (index = CountUsed) < Count;
public bool HasSpace() => HasSpace(out _);
public int GetIndex(TKey key)
{
for (int i = 0; i < Count; i++)
{
var ofs = i * ENTRY_SIZE;
var entryHash = ReadUInt64LittleEndian(Data.Slice(ofs, 8));
if (entryHash == hash)
var entryKey = ReadKey(i);
if (entryKey.Equals(key))
return i;
if (entryHash == HashEmpty)
if (IsEmpty(entryKey))
break;
}
return -1;
}
public void Clear(int index) => WriteEntry(Data[GetOffset(index)..], HashEmpty, 0);
private void WriteEntry(int index, ulong hash, ulong value) => WriteEntry(Data[GetOffset(index)..], hash, value);
private static void WriteEntry(Span<byte> data, ulong hash, ulong value)
public void Clear(int index)
{
WriteUInt64LittleEndian(data, hash);
WriteUInt64LittleEndian(data[8..], value);
// Write empty key and zero value
WriteKey(index, EmptyKey);
WriteUInt64LittleEndian(Data.Slice(GetOffset(index) + (KeyU64Count * sizeof(ulong)), sizeof(ulong)), 0);
}
public void Compress()
@@ -98,16 +93,16 @@ public void Compress()
int writePtr = 0;
for (int readPtr = 0; readPtr < count; readPtr++)
{
var readOfs = GetOffset(readPtr);
var entryHash = ReadUInt64LittleEndian(Data.Slice(readOfs, 8));
if (entryHash == HashEmpty)
var entryKey = ReadKey(readPtr);
if (IsEmpty(entryKey))
continue; // skip empty
if (writePtr != readPtr)
{
var writeOfs = GetOffset(writePtr);
var readOfs = GetOffset(readPtr);
// Move entry
var dest = Data.Slice(writeOfs, ENTRY_SIZE);
var src = Data.Slice(readOfs, ENTRY_SIZE);
var dest = Data.Slice(writeOfs, EntrySize);
var src = Data.Slice(readOfs, EntrySize);
src.CopyTo(dest);
// Clear old entry
Clear(readPtr);
@@ -119,50 +114,203 @@ public void Compress()
public void Sort()
{
Compress();
var entries = new (ulong Hash, ulong Value)[CountUsed];
var used = CountUsed;
var entries = new (TKey Key, ulong Value)[used];
// Gather used entries.
for (int i = 0; i < entries.Length; i++)
{
var data = Data[GetOffset(i)..];
var entryHash = ReadUInt64LittleEndian(data);
var entryValue = ReadUInt64LittleEndian(data[8..]);
entries[i] = (entryHash, entryValue);
var key = ReadKey(i);
var value = ReadUInt64LittleEndian(data.Slice(KeyU64Count * sizeof(ulong), sizeof(ulong)));
entries[i] = (key, value);
}
Array.Sort(entries, Comparer<(ulong Hash, ulong Value)>.Create((a, b) => a.Hash.CompareTo(b.Hash)));
Array.Sort(entries, Comparer<(TKey Key, ulong Value)>.Create((a, b) => CompareKeys(a.Key, b.Key)));
// Write back
for (int i = 0; i < entries.Length; i++)
WriteEntry(i, entries[i].Hash, entries[i].Value);
{
WriteKey(i, entries[i].Key);
WriteUInt64LittleEndian(Data.Slice(GetOffset(i) + (KeyU64Count * sizeof(ulong)), sizeof(ulong)), entries[i].Value);
}
}
public ulong GetKey(int index) => ReadUInt64LittleEndian(Data.Slice(GetOffset(index), 8));
public void SetKey(int index, ulong key) => WriteUInt64LittleEndian(Data.Slice(GetOffset(index), 8), key);
public abstract T GetValue(int index);
public abstract void SetValue(int index, T value);
public abstract TValue GetValue(int index);
public abstract void SetValue(int index, TValue value);
public T this[ReadOnlySpan<char> name]
public bool TryGetValue(TKey key, out TValue value)
{
var index = GetIndex(key);
if (index == -1)
{
value = default;
return false;
}
value = GetValue(index);
return true;
}
public void SetValue(TKey key, TValue value)
{
var index = GetIndex(key);
if (index == -1 && !HasSpace(out index))
throw new InvalidOperationException("No space available to add new entry.");
// Only ensure the key exists; value writing is done via SetValue(index, ...)
WriteKey(index, key);
}
}
/// <summary>
/// Storage for event work values keyed by a 64-bit FNV-1a hash.
/// </summary>
/// <typeparam name="TValue">Unmanaged value type stored alongside the hash. Backed by a 64-bit field.</typeparam>
/// <param name="sav">Owning save file.</param>
/// <param name="block">Underlying block providing the backing bytes.</param>
public abstract class EventWorkStorage64<TValue>(SaveFile sav, SCBlock block) : EventWorkStorage<ulong, TValue>(sav, block),
IEventValueStorage<TValue> where TValue : struct, IEquatable<TValue>
{
/// <summary>
/// Size in bytes of a single entry: 8 bytes for the key hash + 8 bytes for the value.
/// </summary>
public const int ENTRY_SIZE = 16;
private const ulong HashEmpty = FnvHash.HashEmpty;
protected override int KeyU64Count => 1;
protected override ulong EmptyKey => HashEmpty;
protected override int CompareKeys(ulong a, ulong b) => a.CompareTo(b);
protected override ulong ReadKey(int index)
{
var ofs = GetOffset(index);
return ReadUInt64LittleEndian(Data.Slice(ofs, sizeof(ulong)));
}
protected override void WriteKey(int index, ulong key)
{
var ofs = GetOffset(index);
WriteUInt64LittleEndian(Data.Slice(ofs, sizeof(ulong)), key);
}
private static ulong Hash(ReadOnlySpan<char> name) => FnvHash.HashFnv1a_64(name);
public bool TryGetValue(ReadOnlySpan<char> name, out TValue value) => TryGetValue(Hash(name), out value);
public ulong GetKey(int index) => ReadKey(index);
public void SetKey(int index, ulong key) => WriteKey(index, key);
public TValue this[ReadOnlySpan<char> name]
{
get => TryGetValue(name, out var val) ? val : throw new KeyNotFoundException($"No entry found for name '{name.ToString()}'");
set => SetValue(name, value);
}
public T this[ulong hash]
public TValue this[ulong hash]
{
get => TryGetValue(hash, out var val) ? val : throw new KeyNotFoundException($"No entry found for hash '{hash:X16}'");
set => SetValue(hash, value);
}
public void SetValue(ulong hash, T value)
public void SetValue(ReadOnlySpan<char> name, TValue value) => SetValue(Hash(name), value);
}
/// <summary>
/// Storage for event work values keyed by a 128-bit composite FNV-1a hash (two 64-bit parts).
/// </summary>
/// <typeparam name="TValue">Unmanaged value type stored alongside the key. Backed by a 64-bit field.</typeparam>
/// <param name="sav">Owning save file.</param>
/// <param name="block">Underlying block providing the backing bytes.</param>
public abstract class EventWorkStorageKey128<TValue>(SaveFile sav, SCBlock block) : EventWorkStorage<(ulong A, ulong B), TValue>(sav, block)
where TValue : struct, IEquatable<TValue>
{
protected override int KeyU64Count => 2;
protected override (ulong A, ulong B) EmptyKey => (FnvHash.HashEmpty, FnvHash.HashEmpty);
protected override int CompareKeys((ulong A, ulong B) a, (ulong A, ulong B) b)
{
var index = GetIndex(hash);
if (index == -1 && !HasSpace(out index))
throw new InvalidOperationException("No space available to add new entry.");
WriteEntry(index, hash, 0);
int cmp = a.A.CompareTo(b.A);
return cmp != 0 ? cmp : a.B.CompareTo(b.B);
}
public void SetValue(ReadOnlySpan<char> name, T value) => SetValue(Hash(name), value);
protected override (ulong A, ulong B) ReadKey(int index)
{
var ofs = GetOffset(index);
var a = ReadUInt64LittleEndian(Data.Slice(ofs + (0 * sizeof(ulong)), sizeof(ulong)));
var b = ReadUInt64LittleEndian(Data.Slice(ofs + (1 * sizeof(ulong)), sizeof(ulong)));
return (a, b);
}
protected override void WriteKey(int index, (ulong A, ulong B) key)
{
var ofs = GetOffset(index);
WriteUInt64LittleEndian(Data.Slice(ofs + (0 * sizeof(ulong)), sizeof(ulong)), key.A);
WriteUInt64LittleEndian(Data.Slice(ofs + (1 * sizeof(ulong)), sizeof(ulong)), key.B);
}
private static ulong Hash(ReadOnlySpan<char> name) => FnvHash.HashFnv1a_64(name);
// Helper methods for convenience
public bool TryGetValue(ReadOnlySpan<char> a, ReadOnlySpan<char> b, out TValue value)
=> TryGetValue((Hash(a), Hash(b)), out value);
public void SetValue(ReadOnlySpan<char> a, ReadOnlySpan<char> b, TValue value)
=> SetValue((Hash(a), Hash(b)), value);
public (ulong A, ulong B) GetKey(int index) => ReadKey(index);
public void SetKey(int index, (ulong A, ulong B) key) => WriteKey(index, key);
public void SetKey(int index, ulong a, ulong b) => WriteKey(index, (a, b));
}
/// <summary>
/// Storage for event work values keyed by a 192-bit composite FNV-1a hash (three 64-bit parts).
/// </summary>
/// <typeparam name="TValue">Unmanaged value type stored alongside the key. Backed by a 64-bit field.</typeparam>
/// <param name="sav">Owning save file.</param>
/// <param name="block">Underlying block providing the backing bytes.</param>
public abstract class EventWorkStorageKey192<TValue>(SaveFile sav, SCBlock block) : EventWorkStorage<(ulong A, ulong B, ulong C), TValue>(sav, block)
where TValue : struct, IEquatable<TValue>
{
protected override int KeyU64Count => 3;
protected override (ulong A, ulong B, ulong C) EmptyKey => (FnvHash.HashEmpty, FnvHash.HashEmpty, FnvHash.HashEmpty);
protected override int CompareKeys((ulong A, ulong B, ulong C) a, (ulong A, ulong B, ulong C) b)
{
int cmp = a.A.CompareTo(b.A);
if (cmp != 0) return cmp;
cmp = a.B.CompareTo(b.B);
return cmp != 0 ? cmp : a.C.CompareTo(b.C);
}
protected override (ulong A, ulong B, ulong C) ReadKey(int index)
{
var ofs = GetOffset(index);
var a = ReadUInt64LittleEndian(Data.Slice(ofs + (0 * sizeof(ulong)), sizeof(ulong)));
var b = ReadUInt64LittleEndian(Data.Slice(ofs + (1 * sizeof(ulong)), sizeof(ulong)));
var c = ReadUInt64LittleEndian(Data.Slice(ofs + (2 * sizeof(ulong)), sizeof(ulong)));
return (a, b, c);
}
protected override void WriteKey(int index, (ulong A, ulong B, ulong C) key)
{
var ofs = GetOffset(index);
WriteUInt64LittleEndian(Data.Slice(ofs + (0 * sizeof(ulong)), sizeof(ulong)), key.A);
WriteUInt64LittleEndian(Data.Slice(ofs + (1 * sizeof(ulong)), sizeof(ulong)), key.B);
WriteUInt64LittleEndian(Data.Slice(ofs + (2 * sizeof(ulong)), sizeof(ulong)), key.C);
}
private static ulong Hash(ReadOnlySpan<char> name) => FnvHash.HashFnv1a_64(name);
// Helper methods for convenience
public bool TryGetValue(ReadOnlySpan<char> a, ReadOnlySpan<char> b, ReadOnlySpan<char> c, out TValue value)
=> TryGetValue((Hash(a), Hash(b), Hash(c)), out value);
public void SetValue(ReadOnlySpan<char> a, ReadOnlySpan<char> b, ReadOnlySpan<char> c, TValue value)
=> SetValue((Hash(a), Hash(b), Hash(c)), value);
public (ulong A, ulong B, ulong C) GetKey(int index) => ReadKey(index);
public void SetKey(int index, (ulong A, ulong B, ulong C) key) => WriteKey(index, key);
public void SetKey(int index, ulong a, ulong b, ulong c) => WriteKey(index, (a, b, c));
}
/// <summary>
@@ -205,3 +353,53 @@ public override void SetValue(int index, ulong value)
}
}
/// <summary>
/// Event work storage specialized for 64-bit integer values.
/// </summary>
/// <param name="sav">Owning save file.</param>
/// <param name="block">Underlying block providing the backing bytes.</param>
public abstract class EventWorkTupleStorage<T1, T2>(SAV9ZA sav, SCBlock block) : EventWorkStorageKey128<(T1, T2)>(sav, block)
{
}
/// <summary>
/// Event work storage specialized for 64-bit integer values with 128-bit keys.
/// </summary>
/// <param name="sav">Owning save file.</param>
/// <param name="block">Underlying block providing the backing bytes.</param>
public sealed class EventWorkValueStorageKey128(SAV9ZA sav, SCBlock block) : EventWorkStorageKey128<ulong>(sav, block)
{
public override ulong GetValue(int index)
{
var ofs = GetOffset(index);
return ReadUInt64LittleEndian(Data.Slice(ofs + (KeyU64Count * sizeof(ulong)), sizeof(ulong)));
}
public override void SetValue(int index, ulong value)
{
var ofs = GetOffset(index);
WriteUInt64LittleEndian(Data.Slice(ofs + (KeyU64Count * sizeof(ulong)), sizeof(ulong)), value);
}
}
/// <summary>
/// Event work storage specialized for 64-bit integer values with 192-bit keys.
/// </summary>
/// <param name="sav">Owning save file.</param>
/// <param name="block">Underlying block providing the backing bytes.</param>
public sealed class EventWorkValueStorageKey192(SAV9ZA sav, SCBlock block) : EventWorkStorageKey192<ulong>(sav, block)
{
public override ulong GetValue(int index)
{
var ofs = GetOffset(index);
return ReadUInt64LittleEndian(Data.Slice(ofs + (KeyU64Count * sizeof(ulong)), sizeof(ulong)));
}
public override void SetValue(int index, ulong value)
{
var ofs = GetOffset(index);
WriteUInt64LittleEndian(Data.Slice(ofs + (KeyU64Count * sizeof(ulong)), sizeof(ulong)), value);
}
}

View File

@@ -15,34 +15,132 @@ public interface IEventWorkGrid
void Save();
}
/// <summary>
/// Immutable helper for name/hash lookup and reverse lookup.
/// </summary>
public sealed class EventWorkLookup(Dictionary<ulong, string> forward)
{
private readonly Dictionary<string, ulong> _reverse = forward.ToDictionary(z => z.Value, z => z.Key);
public string GetName(ulong hash)
{
if (forward.TryGetValue(hash, out var name))
return name;
return hash.ToString("X16");
}
public ulong GetHash(string nameOrHex)
{
if (_reverse.TryGetValue(nameOrHex, out var h))
return h;
if (ulong.TryParse(nameOrHex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed))
return parsed;
return FnvHash.HashFnv1a_64(nameOrHex);
}
}
/// <summary>
/// Base class with shared UI helpers and wiring for EventWork grids.
/// </summary>
public abstract record EventWorkGridBase(SplitContainer Container, DataGridView Grid, TextBox Search)
: IEventWorkGrid
{
protected static (SplitContainer Container, TextBox Search) CreateContainerCommon()
{
var container = new SplitContainer
{
Dock = DockStyle.Fill,
Orientation = Orientation.Horizontal,
FixedPanel = FixedPanel.Panel1,
IsSplitterFixed = true,
Margin = new Padding(0),
};
var search = new TextBox
{
Dock = DockStyle.Fill,
Margin = new Padding(0),
PlaceholderText = "Search...",
};
container.Panel1.Controls.Add(search);
var topHeight = Math.Max(search.PreferredHeight + 4, 24);
container.Panel1MinSize = topHeight;
container.SplitterDistance = topHeight;
return (container, search);
}
protected static DataGridViewTextBoxColumn MakeIndexColumn(Font font) => new()
{
Name = "Index",
HeaderText = "Index",
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
Width = 40,
ReadOnly = true,
};
protected static DataGridViewTextBoxColumn MakeKeyColumn(Font font, string title = "Name") => new()
{
Name = title,
HeaderText = title,
DefaultCellStyle = new DataGridViewCellStyle
{
Font = font,
Alignment = DataGridViewContentAlignment.MiddleLeft,
},
};
protected static DataGridViewTextBoxColumn MakeValueNumberColumn(Font font, string title) => new()
{
Name = title,
HeaderText = title,
DefaultCellStyle = new DataGridViewCellStyle
{
Font = font,
Alignment = DataGridViewContentAlignment.MiddleRight,
NullValue = 0UL,
},
};
protected static DataGridViewCheckBoxColumn MakeValueBoolColumn(Font font, string title) => new()
{
Name = title,
HeaderText = title,
DefaultCellStyle = new DataGridViewCellStyle
{
Font = font,
Alignment = DataGridViewContentAlignment.MiddleCenter,
NullValue = false,
},
};
public abstract void Load();
public abstract void Save();
}
/// <summary>
/// Helper that creates and manages a DataGridView for editing an <see cref="EventWorkStorage64{T}"/> block.
/// </summary>
/// <typeparam name="T">Value type for the storage. Use bool for flags, ulong for values.</typeparam>
public sealed class EventWorkGrid64<T> : IEventWorkGrid where T : struct, IEquatable<T>
public sealed record EventWorkGrid64<T> : EventWorkGridBase where T : struct, IEquatable<T>
{
private readonly EventWorkStorage64<T> Storage;
private readonly Dictionary<ulong, string> Lookup;
private readonly DoubleBufferedDataGridView Grid;
private readonly SplitContainer Container;
private readonly TextBox SearchBox;
private readonly EventWorkLookup Names;
private const int ColumnIndex = 0;
private const int ColumnValue = 1;
private const int ColumnName = 2;
private EventWorkGrid64(EventWorkStorage64<T> storage, Dictionary<ulong, string> lookup, SplitContainer container, DoubleBufferedDataGridView grid, TextBox search)
private EventWorkGrid64(EventWorkStorage64<T> storage, EventWorkLookup names, SplitContainer container, DataGridView grid, TextBox search)
: base(container, grid, search)
{
Storage = storage;
Lookup = lookup;
Container = container;
Grid = grid;
SearchBox = search;
Names = names;
if (typeof(T) == typeof(ulong))
Grid.CellValidated += ValidateU64;
SearchBox.TextChanged += (_, _) => ApplyFilter(SearchBox.Text);
Search.TextChanged += (_, _) => ApplyFilter(Search.Text);
ToggleAutoSize(true);
}
@@ -62,68 +160,22 @@ private void ValidateU64(object? sender, DataGridViewCellEventArgs e)
var row = Grid.Rows[e.RowIndex];
var cell = row.Cells[e.ColumnIndex];
var text = cell.Value?.ToString();
if (ulong.TryParse(text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _))
if (ulong.TryParse(text, CultureInfo.InvariantCulture, out _))
return;
WinFormsUtil.Alert("Please enter a valid unsigned integer value.");
// Revert to the original stored value.
// Rather than use RowIndex, which may have changed due to sorting, use the Index cell value.
var index = Convert.ToInt32(row.Cells[ColumnIndex].Value);
var saved = Storage.GetValue(index);
cell.Value = saved.ToString();
}
public static EventWorkGrid64<bool> CreateFlags(TabPage host, EventWorkFlagStorage storage, Dictionary<ulong, string> lookup)
public static EventWorkGrid64<bool> CreateFlags(TabPage host, EventWorkFlagStorage storage, EventWorkLookup names)
{
var (container, grid, search) = CreateContainer(true);
var (container, search) = CreateContainerCommon();
host.Controls.Clear();
host.Controls.Add(container);
return new EventWorkGrid64<bool>(storage, lookup, container, grid, search);
}
public static EventWorkGrid64<ulong> CreateValues(TabPage host, EventWorkValueStorage storage, Dictionary<ulong, string> lookup)
{
var (container, grid, search) = CreateContainer(false);
host.Controls.Clear();
host.Controls.Add(container);
return new EventWorkGrid64<ulong>(storage, lookup, container, grid, search);
}
private static (SplitContainer Container, DoubleBufferedDataGridView Grid, TextBox Search) CreateContainer(bool isBoolean)
{
var container = new SplitContainer
{
Dock = DockStyle.Fill,
Orientation = Orientation.Horizontal,
FixedPanel = FixedPanel.Panel1,
IsSplitterFixed = true,
Margin = new Padding(0),
};
var search = new TextBox
{
Dock = DockStyle.Fill,
Margin = new Padding(0),
PlaceholderText = "Search...",
};
// Default row font to Courier New (match designer)
var font = new Font("Courier New", 8F, FontStyle.Regular, GraphicsUnit.Point, 0);
var grid = CreateGrid(isBoolean, font);
container.Panel1.Controls.Add(search);
container.Panel2.Controls.Add(grid);
// Size the top panel to the search box height, leave rest for grid.
var topHeight = Math.Max(search.PreferredHeight + 4, 24);
container.Panel1MinSize = topHeight;
container.SplitterDistance = topHeight;
return (container, grid, search);
}
private static DoubleBufferedDataGridView CreateGrid(bool isBoolean, Font font)
{
var dgv = new DoubleBufferedDataGridView
{
AllowUserToAddRows = false,
@@ -138,62 +190,38 @@ private static DoubleBufferedDataGridView CreateGrid(bool isBoolean, Font font)
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle { BackColor = SystemColors.ControlLight },
};
var colIndex = GetColumnIndex("Index", font);
var colName = GetColumnName("Name", font);
var colValue = GetColumnValue("Value", font, isBoolean);
dgv.Columns.AddRange(colIndex, colValue, colName);
return dgv;
dgv.Columns.AddRange(MakeIndexColumn(font), MakeValueBoolColumn(font, "Value"), MakeKeyColumn(font));
container.Panel2.Controls.Add(dgv);
return new EventWorkGrid64<bool>(storage, names, container, dgv, search);
}
private static DataGridViewTextBoxColumn GetColumnIndex(string title, Font font) => new()
public static EventWorkGrid64<ulong> CreateValues(TabPage host, EventWorkValueStorage storage, EventWorkLookup names)
{
Name = title,
HeaderText = title,
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
Width = 40,
ReadOnly = true,
};
var (container, search) = CreateContainerCommon();
host.Controls.Clear();
host.Controls.Add(container);
private static DataGridViewTextBoxColumn GetColumnName(string title, Font font) => new()
{
Name = title,
HeaderText = title,
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
};
private static DataGridViewColumn GetColumnValue(string title, Font font, bool isBoolean)
{
if (isBoolean)
return GetValueColumnBool(title, font);
return GetValueColumnNumber(title, font);
var font = new Font("Courier New", 8F, FontStyle.Regular, GraphicsUnit.Point, 0);
var dgv = new DoubleBufferedDataGridView
{
AllowUserToAddRows = false,
AllowUserToDeleteRows = false,
AllowUserToOrderColumns = true,
AllowUserToResizeRows = false,
AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells,
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None,
Dock = DockStyle.Fill,
RowHeadersVisible = false,
Margin = new Padding(0),
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle { BackColor = SystemColors.ControlLight },
};
dgv.Columns.AddRange(MakeIndexColumn(font), MakeValueNumberColumn(font, "Value"), MakeKeyColumn(font));
container.Panel2.Controls.Add(dgv);
return new EventWorkGrid64<ulong>(storage, names, container, dgv, search);
}
private static DataGridViewTextBoxColumn GetValueColumnNumber(string title, Font font) => new()
{
Name = title,
HeaderText = title,
DefaultCellStyle = new DataGridViewCellStyle
{
Font = font,
Alignment = DataGridViewContentAlignment.MiddleRight,
NullValue = 0,
},
};
private static DataGridViewCheckBoxColumn GetValueColumnBool(string title, Font font) => new()
{
Name = title,
HeaderText = title,
DefaultCellStyle = new DataGridViewCellStyle
{
Font = font,
Alignment = DataGridViewContentAlignment.MiddleCenter,
NullValue = false,
},
};
public void Load()
public override void Load()
{
var rows = Grid.Rows;
rows.Clear();
@@ -203,7 +231,7 @@ public void Load()
for (int i = 0; i < count; i++)
{
var hash = Storage.GetKey(i);
var name = GetNameDisplay(hash);
var name = Names.GetName(hash);
var value = Storage.GetValue(i);
var row = rows[i];
@@ -214,17 +242,6 @@ public void Load()
}
}
private string GetNameDisplay(ulong hash) => GetNameDisplay(hash, Lookup);
public static string GetNameDisplay(ulong hash, IReadOnlyDictionary<ulong, string> lookup)
{
if (lookup.TryGetValue(hash, out var name))
return name;
if (hash == FnvHash.HashEmpty)
return string.Empty;
return hash.ToString("X16");
}
private void ApplyFilter(string text)
{
ToggleAutoSize(false);
@@ -243,7 +260,7 @@ private void ApplyFilter(string text)
ToggleAutoSize(true);
}
public void Save()
public override void Save()
{
var rows = Grid.Rows;
var count = Math.Min(Storage.Count, rows.Count);
@@ -254,7 +271,7 @@ public void Save()
try
{
var name = cells[ColumnName].Value?.ToString() ?? string.Empty;
var hash = GetHash(name.Trim());
var hash = Names.GetHash(name.Trim());
var valObj = cells[ColumnValue].Value;
if (typeof(T) == typeof(bool))
@@ -269,10 +286,8 @@ public void Save()
}
else
{
// Should not happen for current use cases.
Storage.SetValue(i, (T)valObj!);
}
// Set hash last so that the entry only updates if value parsing succeeded.
Storage.SetKey(i, hash);
}
catch
@@ -280,24 +295,464 @@ public void Save()
// Ignore invalid row
}
}
Storage.Compress(); // Ensure the user didn't sneak in empty rows. There should be no gaps.
}
private ulong GetHash(string name)
{
// Generally, the Lookup will be empty. The user can enter an arbitrary flag name string or hash, and we should recognize both.
// If a recognized name is entered, use the known hash.
// If an unrecognized name is entered, check if it's a hex number and use that as the hash.
// Otherwise, just consider it as a name and hash it.
// O(n) reverse lookup is acceptable for a Saving operation.
var match = Lookup.FirstOrDefault(z => z.Value == name);
if (match.Value != null) // default if not found
return match.Key;
if (ulong.TryParse(name, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var hash))
return hash;
return FnvHash.HashFnv1a_64(name);
Storage.Compress();
}
}
/// <summary>
/// Grid for EventWorkStorage128 with one key columns and two values.
/// </summary>
public sealed record EventWorkGridTuple : EventWorkGridBase
{
private readonly EventWorkValueStorageKey128 Storage;
private readonly EventWorkLookup Names;
private const int ColumnIndex = 0;
private const int ColumnKey = 1;
private const int ColumnValue1 = 2;
private const int ColumnValue2 = 3;
private EventWorkGridTuple(EventWorkValueStorageKey128 storage, EventWorkLookup names, SplitContainer container, DataGridView grid, TextBox search)
: base(container, grid, search)
{
Storage = storage;
Names = names;
Grid.CellValueChanged += ValidateCell;
Search.TextChanged += (_, _) => ApplyFilter(Search.Text);
ToggleAutoSize(true);
}
public static EventWorkGridTuple CreateValues(TabPage host, EventWorkValueStorageKey128 storage, EventWorkLookup names)
{
var (container, search) = CreateContainerCommon();
host.Controls.Clear();
host.Controls.Add(container);
var font = new Font("Courier New", 8F, FontStyle.Regular, GraphicsUnit.Point, 0);
var dgv = new DoubleBufferedDataGridView
{
AllowUserToAddRows = false,
AllowUserToDeleteRows = false,
AllowUserToOrderColumns = true,
AllowUserToResizeRows = false,
AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells,
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None,
Dock = DockStyle.Fill,
RowHeadersVisible = false,
Margin = new Padding(0),
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle { BackColor = SystemColors.ControlLight },
};
var c1 = MakeIndexColumn(font);
var c2 = MakeKeyColumn(font, "Key A");
var v1 = MakeValueNumberColumn(font, "Value 1");
var v2 = MakeValueNumberColumn(font, "Value 2");
dgv.Columns.AddRange(c1, c2, v1, v2);
container.Panel2.Controls.Add(dgv);
return new EventWorkGridTuple(storage, names, container, dgv, search);
}
private void ToggleAutoSize(bool state)
{
var columns = Grid.Columns;
columns[ColumnIndex].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.ColumnHeader : DataGridViewAutoSizeColumnMode.None;
columns[ColumnKey].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
columns[ColumnValue1].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
columns[ColumnValue2].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
}
private void ValidateCell(object? sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex is ColumnValue1 or ColumnValue2)
{
var row = Grid.Rows[e.RowIndex];
var cell = row.Cells[e.ColumnIndex];
var text = cell.Value?.ToString() ?? string.Empty;
if (long.TryParse(text, CultureInfo.InvariantCulture, out _))
return;
WinFormsUtil.Alert("Please enter a valid value.");
var i = Convert.ToInt32(row.Cells[ColumnIndex].Value);
var (a, b) = Storage.GetKey(i);
if (e.ColumnIndex == ColumnKey)
cell.Value = Names.GetName(a);
else if (e.ColumnIndex == ColumnValue1)
cell.Value = ((long)b).ToString();
else
cell.Value = Storage.GetValue(i);
}
}
private void ApplyFilter(string text)
{
ToggleAutoSize(false);
bool has = text.Length != 0;
foreach (DataGridViewRow r in Grid.Rows)
{
if (!has)
{
r.Visible = true;
continue;
}
var a = r.Cells[ColumnKey].Value?.ToString() ?? string.Empty;
r.Visible = a.Contains(text, StringComparison.OrdinalIgnoreCase);
}
ToggleAutoSize(true);
}
public override void Load()
{
var rows = Grid.Rows;
rows.Clear();
var count = Storage.Count;
rows.Add(count);
for (int i = 0; i < count; i++)
{
var (a, b) = Storage.GetKey(i);
var value = Storage.GetValue(i);
var row = rows[i];
var cells = row.Cells;
cells[ColumnIndex].Value = i;
cells[ColumnKey].Value = Names.GetName(a);
cells[ColumnValue1].Value = ((long)b).ToString();
cells[ColumnValue2].Value = value.ToString();
}
}
public override void Save()
{
var rows = Grid.Rows;
var count = Math.Min(Storage.Count, rows.Count);
for (int i = 0; i < count; i++)
{
var cells = rows[i].Cells;
try
{
var aText = cells[ColumnKey].Value?.ToString() ?? string.Empty;
var vText1 = cells[ColumnValue1].Value?.ToString() ?? "0";
var vText2 = cells[ColumnValue2].Value?.ToString() ?? "0";
ulong a = Names.GetHash(aText);
ulong b = long.TryParse(vText1, CultureInfo.InvariantCulture, out var bt) ? (ulong)bt : 0UL;
ulong v = ulong.TryParse(vText2, CultureInfo.InvariantCulture, out var vt) ? vt : 0UL;
Storage.SetValue(i, v);
Storage.SetKey(i, a, b);
}
catch
{
// Ignore invalid row
}
}
Storage.Compress();
}
}
/// <summary>
/// Grid for EventWorkStorage192 with separate key columns A, B, and C, and one value.
/// </summary>
public sealed record EventWorkGrid128 : EventWorkGridBase
{
private readonly EventWorkValueStorageKey128 Storage;
private readonly EventWorkLookup Names;
private const int ColumnIndex = 0;
private const int ColumnKeyA = 1;
private const int ColumnKeyB = 2;
private const int ColumnValue = 3;
private EventWorkGrid128(EventWorkValueStorageKey128 storage, EventWorkLookup names, SplitContainer container, DataGridView grid, TextBox search)
: base(container, grid, search)
{
Storage = storage;
Names = names;
Grid.CellValidated += ValidateValue;
Search.TextChanged += (_, _) => ApplyFilter(Search.Text);
ToggleAutoSize(true);
}
public static EventWorkGrid128 CreateValues(TabPage host, EventWorkValueStorageKey128 storage, EventWorkLookup names)
{
var (container, search) = CreateContainerCommon();
host.Controls.Clear();
host.Controls.Add(container);
var font = new Font("Courier New", 8F, FontStyle.Regular, GraphicsUnit.Point, 0);
var dgv = new DoubleBufferedDataGridView
{
AllowUserToAddRows = false,
AllowUserToDeleteRows = false,
AllowUserToOrderColumns = true,
AllowUserToResizeRows = false,
AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells,
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None,
Dock = DockStyle.Fill,
RowHeadersVisible = false,
Margin = new Padding(0),
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle { BackColor = SystemColors.ControlLight },
};
var c1 = MakeIndexColumn(font);
var c2 = MakeKeyColumn(font, "Key A");
var c3 = MakeKeyColumn(font, "Key B");
var v1 = MakeValueNumberColumn(font, "Value");
dgv.Columns.AddRange(c1, c2, c3, v1);
container.Panel2.Controls.Add(dgv);
return new EventWorkGrid128(storage, names, container, dgv, search);
}
private void ToggleAutoSize(bool state)
{
var columns = Grid.Columns;
columns[ColumnIndex].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.ColumnHeader : DataGridViewAutoSizeColumnMode.None;
columns[ColumnKeyA].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
columns[ColumnKeyB].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
columns[ColumnValue].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
}
private void ValidateValue(object? sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex is ColumnValue)
{
var row = Grid.Rows[e.RowIndex];
var cell = row.Cells[e.ColumnIndex];
var text = cell.Value?.ToString() ?? string.Empty;
if (ulong.TryParse(text, CultureInfo.InvariantCulture, out _))
return;
WinFormsUtil.Alert("Please enter a valid value.");
var i = Convert.ToInt32(row.Cells[ColumnIndex].Value);
cell.Value = Storage.GetValue(i);
}
}
private void ApplyFilter(string text)
{
ToggleAutoSize(false);
bool has = text.Length != 0;
foreach (DataGridViewRow r in Grid.Rows)
{
if (!has)
{
r.Visible = true;
continue;
}
var a = r.Cells[ColumnKeyA].Value?.ToString() ?? string.Empty;
var b = r.Cells[ColumnKeyB].Value?.ToString() ?? string.Empty;
r.Visible = a.Contains(text, StringComparison.OrdinalIgnoreCase) ||
b.Contains(text, StringComparison.OrdinalIgnoreCase);
}
ToggleAutoSize(true);
}
public override void Load()
{
var rows = Grid.Rows;
rows.Clear();
var count = Storage.Count;
rows.Add(count);
for (int i = 0; i < count; i++)
{
var (a, b) = Storage.GetKey(i);
var value = Storage.GetValue(i);
var row = rows[i];
var cells = row.Cells;
cells[ColumnIndex].Value = i;
cells[ColumnKeyA].Value = Names.GetName(a);
cells[ColumnKeyB].Value = Names.GetName(b);
cells[ColumnValue].Value = value.ToString();
}
}
public override void Save()
{
var rows = Grid.Rows;
var count = Math.Min(Storage.Count, rows.Count);
for (int i = 0; i < count; i++)
{
var cells = rows[i].Cells;
try
{
var aText = cells[ColumnKeyA].Value?.ToString() ?? string.Empty;
var bText = cells[ColumnKeyB].Value?.ToString() ?? string.Empty;
var vText = cells[ColumnValue].Value?.ToString() ?? "0";
ulong a = Names.GetHash(aText);
ulong b = Names.GetHash(bText);
ulong v = ulong.TryParse(vText, CultureInfo.InvariantCulture, out var vt) ? vt : 0UL;
Storage.SetValue(i, v);
Storage.SetKey(i, a, b);
}
catch
{
// Ignore invalid row
}
}
Storage.Compress();
}
}
/// <summary>
/// Grid for EventWorkStorage192 with separate key columns A, B, and C, and one value.
/// </summary>
public sealed record EventWorkGrid192 : EventWorkGridBase
{
private readonly EventWorkValueStorageKey192 Storage;
private readonly EventWorkLookup Names;
private const int ColumnIndex = 0;
private const int ColumnKeyA = 1;
private const int ColumnKeyB = 2;
private const int ColumnKeyC = 3;
private const int ColumnValue = 4;
private EventWorkGrid192(EventWorkValueStorageKey192 storage, EventWorkLookup names, SplitContainer container, DataGridView grid, TextBox search)
: base(container, grid, search)
{
Storage = storage;
Names = names;
Grid.CellValidated += ValidateValue;
Search.TextChanged += (_, _) => ApplyFilter(Search.Text);
ToggleAutoSize(true);
}
public static EventWorkGrid192 CreateValues(TabPage host, EventWorkValueStorageKey192 storage, EventWorkLookup names)
{
var (container, search) = CreateContainerCommon();
host.Controls.Clear();
host.Controls.Add(container);
var font = new Font("Courier New", 8F, FontStyle.Regular, GraphicsUnit.Point, 0);
var dgv = new DoubleBufferedDataGridView
{
AllowUserToAddRows = false,
AllowUserToDeleteRows = false,
AllowUserToOrderColumns = true,
AllowUserToResizeRows = false,
AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells,
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None,
Dock = DockStyle.Fill,
RowHeadersVisible = false,
Margin = new Padding(0),
DefaultCellStyle = new DataGridViewCellStyle { Font = font },
AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle { BackColor = SystemColors.ControlLight },
};
var c1 = MakeIndexColumn(font);
var c2 = MakeKeyColumn(font, "Key A");
var c3 = MakeKeyColumn(font, "Key B");
var c4 = MakeKeyColumn(font, "Key C");
var v1 = MakeValueNumberColumn(font, "Value");
dgv.Columns.AddRange(c1, c2, c3, c4, v1);
container.Panel2.Controls.Add(dgv);
return new EventWorkGrid192(storage, names, container, dgv, search);
}
private void ToggleAutoSize(bool state)
{
var columns = Grid.Columns;
columns[ColumnIndex].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.ColumnHeader : DataGridViewAutoSizeColumnMode.None;
columns[ColumnKeyA].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
columns[ColumnKeyB].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
columns[ColumnKeyC].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
columns[ColumnValue].AutoSizeMode = state ? DataGridViewAutoSizeColumnMode.AllCells : DataGridViewAutoSizeColumnMode.None;
}
private void ValidateValue(object? sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex is ColumnValue)
{
var row = Grid.Rows[e.RowIndex];
var cell = row.Cells[e.ColumnIndex];
var text = cell.Value?.ToString() ?? string.Empty;
if (ulong.TryParse(text, CultureInfo.InvariantCulture, out _))
return;
WinFormsUtil.Alert("Please enter a valid value.");
var i = Convert.ToInt32(row.Cells[ColumnIndex].Value);
cell.Value = Storage.GetValue(i);
}
}
private void ApplyFilter(string text)
{
ToggleAutoSize(false);
bool has = text.Length != 0;
foreach (DataGridViewRow r in Grid.Rows)
{
if (!has)
{
r.Visible = true;
continue;
}
var a = r.Cells[ColumnKeyA].Value?.ToString() ?? string.Empty;
var b = r.Cells[ColumnKeyB].Value?.ToString() ?? string.Empty;
var c = r.Cells[ColumnKeyC].Value?.ToString() ?? string.Empty;
r.Visible = a.Contains(text, StringComparison.OrdinalIgnoreCase) ||
b.Contains(text, StringComparison.OrdinalIgnoreCase) ||
c.Contains(text, StringComparison.OrdinalIgnoreCase);
}
ToggleAutoSize(true);
}
public override void Load()
{
var rows = Grid.Rows;
rows.Clear();
var count = Storage.Count;
rows.Add(count);
for (int i = 0; i < count; i++)
{
var (a, b, c) = Storage.GetKey(i);
var value = Storage.GetValue(i);
var row = rows[i];
var cells = row.Cells;
cells[ColumnIndex].Value = i;
cells[ColumnKeyA].Value = Names.GetName(a);
cells[ColumnKeyB].Value = Names.GetName(b);
cells[ColumnKeyC].Value = Names.GetName(c);
cells[ColumnValue].Value = value.ToString();
}
}
public override void Save()
{
var rows = Grid.Rows;
var count = Math.Min(Storage.Count, rows.Count);
for (int i = 0; i < count; i++)
{
var cells = rows[i].Cells;
try
{
var aText = cells[ColumnKeyA].Value?.ToString() ?? string.Empty;
var bText = cells[ColumnKeyB].Value?.ToString() ?? string.Empty;
var cText = cells[ColumnKeyC].Value?.ToString() ?? string.Empty;
var vText = cells[ColumnValue].Value?.ToString() ?? "0";
ulong a = Names.GetHash(aText);
ulong b = Names.GetHash(bText);
ulong c = Names.GetHash(cText);
ulong v = ulong.TryParse(vText, CultureInfo.InvariantCulture, out var vt) ? vt : 0UL;
Storage.SetValue(i, v);
Storage.SetKey(i, a, b, c);
}
catch
{
// Ignore invalid row
}
}
Storage.Compress();
}
}

View File

@@ -50,7 +50,7 @@ private void InitializeComponent()
// B_Cancel
//
B_Cancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
B_Cancel.Location = new System.Drawing.Point(427, 401);
B_Cancel.Location = new System.Drawing.Point(507, 401);
B_Cancel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
B_Cancel.Name = "B_Cancel";
B_Cancel.Size = new System.Drawing.Size(88, 27);
@@ -62,7 +62,7 @@ private void InitializeComponent()
// B_Save
//
B_Save.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
B_Save.Location = new System.Drawing.Point(523, 401);
B_Save.Location = new System.Drawing.Point(603, 401);
B_Save.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
B_Save.Name = "B_Save";
B_Save.Size = new System.Drawing.Size(88, 27);
@@ -90,7 +90,7 @@ private void InitializeComponent()
GB_Research.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
GB_Research.Name = "GB_Research";
GB_Research.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
GB_Research.Size = new System.Drawing.Size(598, 351);
GB_Research.Size = new System.Drawing.Size(678, 351);
GB_Research.TabIndex = 2;
GB_Research.Text = "Research";
GB_Research.UseVisualStyleBackColor = true;
@@ -112,7 +112,7 @@ private void InitializeComponent()
// splitContainer1.Panel2
//
splitContainer1.Panel2.Controls.Add(RTB_Diff);
splitContainer1.Size = new System.Drawing.Size(590, 345);
splitContainer1.Size = new System.Drawing.Size(670, 345);
splitContainer1.SplitterDistance = 60;
splitContainer1.TabIndex = 7;
//
@@ -134,7 +134,7 @@ private void InitializeComponent()
TB_NewSAV.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
TB_NewSAV.Name = "TB_NewSAV";
TB_NewSAV.ReadOnly = true;
TB_NewSAV.Size = new System.Drawing.Size(487, 25);
TB_NewSAV.Size = new System.Drawing.Size(567, 25);
TB_NewSAV.TabIndex = 5;
//
// B_LoadNew
@@ -155,7 +155,7 @@ private void InitializeComponent()
TB_OldSAV.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
TB_OldSAV.Name = "TB_OldSAV";
TB_OldSAV.ReadOnly = true;
TB_OldSAV.Size = new System.Drawing.Size(487, 25);
TB_OldSAV.Size = new System.Drawing.Size(567, 25);
TB_OldSAV.TabIndex = 4;
//
// RTB_Diff
@@ -165,7 +165,7 @@ private void InitializeComponent()
RTB_Diff.Margin = new System.Windows.Forms.Padding(0);
RTB_Diff.Name = "RTB_Diff";
RTB_Diff.ReadOnly = true;
RTB_Diff.Size = new System.Drawing.Size(590, 281);
RTB_Diff.Size = new System.Drawing.Size(670, 281);
RTB_Diff.TabIndex = 6;
RTB_Diff.Text = "";
//
@@ -179,14 +179,14 @@ private void InitializeComponent()
TC_Features.Name = "TC_Features";
TC_Features.Padding = new System.Drawing.Point(0, 0);
TC_Features.SelectedIndex = 0;
TC_Features.Size = new System.Drawing.Size(606, 381);
TC_Features.Size = new System.Drawing.Size(686, 381);
TC_Features.TabIndex = 42;
//
// SAV_FlagWork9a
//
AllowDrop = true;
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit;
ClientSize = new System.Drawing.Size(624, 441);
ClientSize = new System.Drawing.Size(704, 441);
Controls.Add(TC_Features);
Controls.Add(L_EventFlagWarn);
Controls.Add(B_Save);
@@ -194,7 +194,6 @@ private void InitializeComponent()
Icon = Properties.Resources.Icon;
Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
MaximizeBox = false;
MaximumSize = new System.Drawing.Size(779, 917);
MinimizeBox = false;
MinimumSize = new System.Drawing.Size(640, 480);
Name = "SAV_FlagWork9a";

View File

@@ -9,7 +9,8 @@ namespace PKHeX.WinForms;
public sealed partial class SAV_FlagWork9a : Form
{
private readonly Dictionary<ulong, string> Lookup = [];
private readonly Dictionary<ulong, string> Lookup = new() { {FnvHash.HashEmpty, ""} };
private readonly EventWorkLookup Names;
private readonly IEventWorkGrid[] Grids;
@@ -21,18 +22,28 @@ public SAV_FlagWork9a(SAV9ZA sav)
if (File.Exists(path))
SCBlockMetadata.AddExtraKeyNames64(Lookup, File.ReadLines(path));
Names = new EventWorkLookup(Lookup);
// Create grids for each block
Grids =
[
EventWorkGrid64<bool>.CreateFlags(GetTab(nameof(sav.Blocks.Flags)), sav.Blocks.Flags, Lookup),
EventWorkGrid64<bool>.CreateFlags(GetTab(nameof(sav.Blocks.Event)), sav.Blocks.Event, Lookup),
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.Work)), sav.Blocks.Work, Lookup),
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.Quest)), sav.Blocks.Quest, Lookup),
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.WorkMable)), sav.Blocks.WorkMable, Lookup),
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.CountMable)), sav.Blocks.CountMable, Lookup),
EventWorkGrid64<bool>.CreateFlags(GetTab(nameof(sav.Blocks.Flags)), sav.Blocks.Flags, Names),
EventWorkGrid64<bool>.CreateFlags(GetTab(nameof(sav.Blocks.Event)), sav.Blocks.Event, Names),
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.Work)), sav.Blocks.Work, Names),
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.Quest)), sav.Blocks.Quest, Names),
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.WorkMable)), sav.Blocks.WorkMable, Names),
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.CountMable)), sav.Blocks.CountMable, Names),
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.CountTitle)), sav.Blocks.CountTitle, Lookup),
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.WorkSpawn)), sav.Blocks.WorkSpawn, Lookup),
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.CountTitle)), sav.Blocks.CountTitle, Names),
EventWorkGridTuple.CreateValues(GetTab(nameof(sav.Blocks.Report)), sav.Blocks.Report, Names),
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.WorkSpawn)), sav.Blocks.WorkSpawn, Names),
EventWorkGrid64<ulong>.CreateValues(GetTab(nameof(sav.Blocks.InfiniteRank)), sav.Blocks.InfiniteRank, Names),
EventWorkGridTuple.CreateValues(GetTab(nameof(sav.Blocks.Spawner2)), sav.Blocks.Spawner2, Names),
EventWorkGrid128.CreateValues(GetTab(nameof(sav.Blocks.Spawner4)), sav.Blocks.Spawner4, Names),
EventWorkGridTuple.CreateValues(GetTab(nameof(sav.Blocks.Obstruction)), sav.Blocks.Obstruction, Names),
EventWorkGrid192.CreateValues(GetTab(nameof(sav.Blocks.FieldObjectInteractable)), sav.Blocks.FieldObjectInteractable, Names),
];
// Translate headings
@@ -142,7 +153,7 @@ private void DiffSaves(string fileUpdated, string filePrevious)
break;
var u = update.GetValue(i);
var name = EventWorkGrid64<T2>.GetNameDisplay(hash, Lookup);
var name = Names.GetName(hash);
if (!previous.TryGetValue(hash, out var p))
result.Add($"{name} added with value {u}");
else if (!p.Equals(u))
@@ -161,7 +172,7 @@ private void DiffSaves(string fileUpdated, string filePrevious)
if (hashes.Contains(hash))
continue;
var p = previous.GetValue(i);
var name = EventWorkGrid64<T2>.GetNameDisplay(hash, Lookup);
var name = Names.GetName(hash);
result.Add($"{name} @ {i:X} removed, was {p}");
}
return result;