Skip to content

Instantly share code, notes, and snippets.

@Jofairden
Last active February 14, 2018 14:50
Show Gist options
  • Save Jofairden/29a330db427e37e45140ee655f078f3d to your computer and use it in GitHub Desktop.
Save Jofairden/29a330db427e37e45140ee655f078f3d to your computer and use it in GitHub Desktop.
tModLoader item hashing tests
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