Roblox-File-Format/DataTypes/SharedString.cs
2021-02-18 13:15:08 -06:00

95 lines
2.8 KiB
C#

using System;
using System.Text;
using System.Collections.Generic;
using System.Collections.Concurrent;
using Konscious.Security.Cryptography;
namespace RobloxFiles.DataTypes
{
// SharedString is a datatype that takes a sequence of bytes and stores it in a
// lookup table that is shared by the entire file. It originally used MD5 for the
// hashing, but Roblox now uses Blake2B to avoid the obvious problems with using MD5.
// In practice the value of a SharedString does not have to match the hash of the
// data it represents, it just needs to be distinct and MUST be 16 bytes long.
// The XML format still uses 'md5' as its attribute key to the lookup table.
public class SharedString
{
private static readonly ConcurrentDictionary<string, byte[]> Lookup = new ConcurrentDictionary<string, byte[]>();
public string Key { get; internal set; }
public string ComputedKey { get; internal set; }
public byte[] SharedValue => Find(ComputedKey ?? Key);
public override string ToString() => $"Key: {ComputedKey ?? Key}";
public override int GetHashCode()
{
return Key.GetHashCode();
}
public override bool Equals(object obj)
{
if (!(obj is SharedString other))
return false;
return Key.Equals(other.Key);
}
internal SharedString(string key)
{
Key = key;
}
internal static void Register(string key, byte[] buffer)
{
if (Lookup.ContainsKey(key))
return;
Lookup.TryAdd(key, buffer);
}
private SharedString(byte[] buffer)
{
using (var blake2B = new HMACBlake2B(16 * 8))
{
byte[] hash = blake2B.ComputeHash(buffer);
ComputedKey = Convert.ToBase64String(hash);
Key = ComputedKey;
}
if (Lookup.ContainsKey(ComputedKey))
return;
Register(ComputedKey, buffer);
}
public static byte[] Find(string key)
{
byte[] result = null;
if (Lookup.ContainsKey(key))
result = Lookup[key];
return result;
}
public static SharedString FromBuffer(byte[] buffer)
{
return new SharedString(buffer ?? Array.Empty<byte>());
}
public static SharedString FromString(string value)
{
byte[] buffer = Encoding.UTF8.GetBytes(value);
return new SharedString(buffer);
}
public static SharedString FromBase64(string base64)
{
byte[] buffer = Convert.FromBase64String(base64);
return new SharedString(buffer);
}
}
}