I want to read data from the registry in my standalone WPF application, which is created inside a game on Unity using PlayerPrefs. The question is how to read and write float to the registry as DWORD32.
public static float Int64ToFloat(long value)
{
byte[] bytes = BitConverter.GetBytes(value);
byte[] floatBytes = new byte[4];
Array.Copy(bytes, floatBytes, 4);
if (BitConverter.IsLittleEndian)
Array.Reverse(floatBytes);
float floatValue = BitConverter.ToSingle(floatBytes, 0);
return floatValue;
}
This code returns extremely small values, such as 1.35E-43.
Unity does not save a float
but rather a double
! It is indeed quite a hack but if you look closely you will find that your DWORD (32bit)
contains indeed a 64
-bit long
! (no clue why they didn't properly go for QWORD
or directly binary there)
As mentioned already for floating point types you can't simply cut the bytes
and expect the value to remain the same as you could do with long
-> int
.
You can simply use
// Have in mind that the key here is NOT the one you use in Unity but also contains a certain trailing hash string
public static float GetFloat(string key, float fallback = 0f)
{
using (RegistryKey registryKey = Registry.CurrentUser.OpenSubKey(@"Software\Unity\UnityEditor\" + Application.companyName + "\\" + Application.productName))
{
var value = registryKey?.GetValue(key);
if (value is long longValue)
{
// Convert the 64-bit value to a double
var doubleValue = BitConverter.Int64BitsToDouble(longValue);
// then simply cast to cut its precision down
var floatValue = (float)doubleValue;
return floatValue;
}
}
return fallback;
}
For you to verify
var input = 12345678901234567890.1234567890f;
PlayerPrefs.SetFloat("TestFloat", input);
var output = GetFloat("TestFloat_h1435782019");
Assert.AreApproximatelyEqual(input,output, "Value retrieved from registry does not match the value stored in PlayerPrefs.");
I just also stumbled over this snippet
If we can trust it the hash string is basically calculated as
public class PlayerPrefsHash { // Returns the string property name Unity would generate for a player prefs registry key value // e.g., "PlayerGold" -> "PlayerGold_hXXXXXXXXXXXXXX" public static string Hash(string name) { uint hash = 5381; foreach (char c in name) hash = hash * 33 ^ c; string key = name + "_h" + hash; return key; } }
So you could use this in my method above and be able to use the same key constant on Unity as in WPF side.
And at least in my quick test it seams to be accurate
var key = PlayerPrefsHash.Hash("TestFloat");
Assert.AreEqual("TestFloat_h1435782019", key, "PlayerPrefsHash.Hash does not match PlayerPrefs generated hash.");