Last active
February 14, 2018 14:50
-
-
Save Jofairden/29a330db427e37e45140ee655f078f3d to your computer and use it in GitHub Desktop.
tModLoader item hashing tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Diagnostics.CodeAnalysis; | |
using System.IO; | |
using System.Linq; | |
using System.Reflection; | |
using System.Runtime.InteropServices; | |
using System.Runtime.Serialization.Formatters.Binary; | |
using System.Security.Cryptography; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace HashCodeTests | |
{ | |
class Program | |
{ | |
// Basic hash test | |
// Make a few items with the same data, they will have the same hash (same type, stack etc) | |
// Put different modded data on the same item, hash changes. Same item, different data | |
// Todo: find a way to exclude certain fields, specifically stack/maxStack or whatever (generate hash excluding those fields) | |
static void Main(string[] args) | |
{ | |
ModItem item1 = new UnstackableItem() | |
{ | |
stack = 1, | |
maxStack = 10, | |
type = 1 | |
}; | |
ModItem item2 = new UnstackableItem() | |
{ | |
stack = 1, | |
maxStack = 10, | |
type = 1 | |
}; | |
ModItem item3 = item2; | |
Console.WriteLine($"item1 BEFORE: {item1.GetHashCode()}"); | |
Console.WriteLine($"item2 BEFORE: {item2.GetHashCode()}"); | |
item1.AddGlobalItem<TestGI>(); | |
item2.AddGlobalItem<TestGI>(); | |
// uncomment this and see the changes | |
//item3.AddGlobalItem<TestGI2>(); | |
Console.WriteLine($"item1: {item1.GetHashCode()}"); | |
Console.WriteLine($"item2: {item2.GetHashCode()}"); | |
Console.WriteLine($"item3: {item3.GetHashCode()}"); | |
Console.WriteLine($"Can item1 stack with item2? {item1.CanStack(item2)}"); | |
// item 10 and 11 are the same | |
// item 15 is different | |
ModItem item10 = new UnstackableItem() | |
{ | |
stack = 5, | |
maxStack = 20, | |
type = 10 | |
}; | |
ModItem item11 = new UnstackableItem() | |
{ | |
stack = 5, | |
maxStack = 20, | |
type = 10 | |
}; | |
ModItem item15 = new UnstackableItem() | |
{ | |
stack = 100, | |
maxStack = 999, | |
type = 15 | |
}; | |
Console.WriteLine($"item10: {item10.GetHashCode()}"); | |
Console.WriteLine($"item11: {item11.GetHashCode()}"); | |
Console.WriteLine($"item15: {item15.GetHashCode()}"); | |
Console.ReadKey(); | |
} | |
} | |
class TestGI2 : GlobalItem | |
{ | |
public bool yoBro = false; | |
public int itsGood = 20; | |
} | |
class TestGI : GlobalItem | |
{ | |
public bool testB = true; | |
private int int32 = 32; | |
private int int64 = 64; | |
public int[] ints = | |
{ | |
1, 2, 3 | |
}; | |
} | |
abstract class GlobalItem | |
{ | |
public override int GetHashCode() => FNVModHash.GetHashCode(this); | |
} | |
class UnstackableItem : ModItem | |
{ | |
} | |
abstract class ModItem : Item | |
{ | |
public virtual bool CanStack(Item sItem) => GetHashCode() == sItem.GetHashCode(); | |
public virtual void OnStack(Item sItem) | |
{ | |
} | |
} | |
class Item | |
{ | |
public int stack; | |
public int maxStack; | |
public int type; | |
internal GlobalItem[] globalItems = new GlobalItem[0]; | |
public void AddGlobalItem<T>() where T : GlobalItem, new() | |
{ | |
T gI2 = new T(); | |
if (!globalItems.Contains(gI2)) | |
{ | |
Array.Resize(ref globalItems, globalItems.Length + 1); | |
globalItems[globalItems.Length - 1] = gI2; | |
} | |
} | |
public T GetGlobalItem<T>(T gI) where T : GlobalItem | |
{ | |
return globalItems.FirstOrDefault(x => x == gI) as T; | |
} | |
public override int GetHashCode() => FNVModHash.GetHashCode(this); | |
} | |
internal static class FNVModHash | |
{ | |
internal static FNV1a32 _fnv1a32 = new FNV1a32(); | |
private static byte[] ObjectToByteArray(object obj) | |
{ | |
var size = Marshal.SizeOf(obj); | |
// Both managed and unmanaged buffers required. | |
byte[] bytes = new byte[size]; | |
IntPtr ptr = Marshal.AllocHGlobal(size); | |
// Copy object byte-to-byte to unmanaged memory. | |
Marshal.StructureToPtr(obj, ptr, false); | |
// Copy data from unmanaged memory to managed buffer. | |
Marshal.Copy(ptr, bytes, 0, size); | |
// Release unmanaged memory. | |
Marshal.FreeHGlobal(ptr); | |
return bytes; | |
} | |
private static byte[] JoinArrays(IEnumerable<byte[]> arrays) | |
{ | |
int offset = 0; | |
byte[] fullArray = new byte[arrays.Sum(a => a.Length)]; | |
foreach (byte[] array in arrays) | |
{ | |
Buffer.BlockCopy(array, 0, fullArray, offset, array.Length); | |
offset += array.Length; | |
} | |
return fullArray; | |
} | |
internal static int GetHashCode(object obj) | |
{ | |
// Create byte buffer | |
List<byte[]> buffer = new List<byte[]>(); | |
// Fill buffer with item info, modded info | |
foreach (FieldInfo field in obj.GetType() | |
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) | |
{ | |
object value = field.GetValue(obj); | |
if (value != null) | |
{ | |
if (value is IEnumerable) | |
{ | |
// Small trick here, since using sizeOf throws exceptions (try it yourself) | |
List<byte> enumBuffer = new List<byte>(); | |
foreach (byte entry in ((IEnumerable)value).OfType<byte>()) | |
{ | |
enumBuffer.Add(entry); | |
} | |
buffer.Add(enumBuffer.ToArray()); | |
} | |
else | |
{ | |
buffer.Add(ObjectToByteArray(value)); | |
} | |
} | |
} | |
byte[] digest = JoinArrays(buffer); | |
_fnv1a32.Initialize(); | |
byte[] hashBytes = _fnv1a32.ComputeHash(digest); | |
int hash = BitConverter.ToInt32(hashBytes, 0); | |
return hash; | |
//int hash = unchecked((int)2166136261); | |
//foreach (FieldInfo field in obj.GetType() | |
// .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) | |
//{ | |
// object value = field.GetValue(obj); | |
// if (value != null) | |
// { | |
// if (value is IEnumerable) | |
// { | |
// foreach (object entry in (IEnumerable)value) | |
// { | |
// hash = unchecked((hash ^ entry.GetHashCode()) * 16777619); | |
// } | |
// } | |
// else | |
// { | |
// hash = unchecked((hash ^ value.GetHashCode()) * 16777619); | |
// } | |
// } | |
//} | |
//return hash; | |
} | |
} | |
// basic implementation of FNV-1a hash algorithm | |
// just proof of concept | |
internal sealed class FNV1a32 : HashAlgorithm | |
{ | |
private const uint FnvPrime = 0x01000193; // 0x01000193 // 16777619 | |
private const uint FnvOffsetBasis = 0x811C9DC5; // 0x811C9DC5 // 2166136261 | |
private uint _hash; | |
public FNV1a32() | |
{ | |
this.Initialize(); | |
this.HashSizeValue = 32; | |
} | |
public override void Initialize() => this._hash = FnvOffsetBasis; | |
protected override void HashCore(byte[] array, int ibStart, int cbSize) | |
{ | |
if (array == null) | |
{ | |
throw new ArgumentNullException(nameof(array)); | |
} | |
for (var i = ibStart; i < cbSize; i++) | |
{ | |
unchecked | |
{ | |
this._hash ^= array[i]; | |
this._hash *= FnvPrime; | |
} | |
} | |
} | |
protected override byte[] HashFinal() => BitConverter.GetBytes(this._hash); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment