Files
bd2/plugins/LocalIdentity/SystemTimeZoneRegion.cs
T

106 lines
5.0 KiB
C#

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using BepInEx.Logging;
using HarmonyLib;
namespace Bd2LocalIdentity;
// Neo's device country is derived directly from the OS time-zone identifier.
// Maps an explicit zone identifier to its representative territory, never a UTC offset.
// Unmapped identifiers have no inferred country.
internal static class SystemTimeZoneRegion
{
private static ManualLogSource log;
private static string lastLoggedIdentifier;
internal static void Install(Harmony harmony, ManualLogSource logger)
{
log = logger;
// NeoMobilePlatform is internal in the original SDK; typeof cannot reference it.
Type platform = Assembly.Load("Neo.Unity.Device").GetType("Neo.Unity.Platform.NeoMobilePlatform", false);
MethodInfo method = platform?.GetMethod("GetDeviceCountryIso", BindingFlags.Instance | BindingFlags.Public,
null, Type.EmptyTypes, null);
if (method == null || method.ReturnType != typeof(string))
throw new MissingMethodException("NeoMobilePlatform.GetDeviceCountryIso was not found");
harmony.Patch(method, prefix: new HarmonyMethod(typeof(SystemTimeZoneRegion), nameof(GetDeviceCountryPrefix)));
logger.LogInfo("Neo device country uses the operating system time zone");
}
private static bool GetDeviceCountryPrefix(ref string __result)
{
string identifier = ReadId();
__result = SystemTimeZoneCountries.Resolve(identifier);
if (!string.Equals(lastLoggedIdentifier, identifier, StringComparison.Ordinal))
{
lastLoggedIdentifier = identifier;
if (__result.Length == 0)
log?.LogWarning("OS time zone country is unknown: source=OS, timeZone=" + identifier);
else
log?.LogInfo("OS time zone country: source=OS, timeZone=" + identifier + ", country=" + __result);
}
return false;
}
public static string ReadId()
{
try
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (GetDynamicTimeZoneInformation(out DynamicTimeZoneInformation information) == uint.MaxValue) return string.Empty;
return information.TimeZoneKeyName ?? string.Empty;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
IntPtr zone = CFTimeZoneCopySystem();
if (zone == IntPtr.Zero) return string.Empty;
try
{
IntPtr name = CFTimeZoneGetName(zone);
if (name == IntPtr.Zero) return string.Empty;
long length = CFStringGetLength(name).ToInt64();
if (length <= 0 || length > 1024) return string.Empty;
ushort[] characters = new ushort[(int)length];
CFStringGetCharacters(name, new CFRange { Length = new IntPtr(length) }, characters);
char[] text = new char[characters.Length];
for (int i = 0; i < text.Length; i++) text[i] = (char)characters[i];
return new string(text);
}
finally { CFRelease(zone); }
}
}
catch (Exception)
{
log?.LogWarning("Could not read the operating system time zone");
}
return string.Empty;
}
[StructLayout(LayoutKind.Sequential)]
private struct SystemTime { public ushort Year, Month, DayOfWeek, Day, Hour, Minute, Second, Milliseconds; }
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DynamicTimeZoneInformation
{
public int Bias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string StandardName;
public SystemTime StandardDate;
public int StandardBias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string DaylightName;
public SystemTime DaylightDate;
public int DaylightBias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string TimeZoneKeyName;
public byte DynamicDaylightTimeDisabled;
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern uint GetDynamicTimeZoneInformation(out DynamicTimeZoneInformation information);
[StructLayout(LayoutKind.Sequential)]
private struct CFRange { public IntPtr Location, Length; }
private const string CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
[DllImport(CoreFoundation)] private static extern IntPtr CFTimeZoneCopySystem();
[DllImport(CoreFoundation)] private static extern IntPtr CFTimeZoneGetName(IntPtr zone);
[DllImport(CoreFoundation)] private static extern IntPtr CFStringGetLength(IntPtr value);
[DllImport(CoreFoundation)] private static extern void CFStringGetCharacters(IntPtr value, CFRange range, [Out] ushort[] characters);
[DllImport(CoreFoundation)] private static extern void CFRelease(IntPtr value);
}