Files
bd2/plugins/LocalIdentity/SystemTimeZoneRegion.cs
T
Flechazo 992fa47fd5 feat(all): add game SDK and harden client login and game rules
- Add costume burst upgrades with durable replay and fix permanent SR equipment gacha.
- Add server game.json rules for optional collaboration UR weapons, with packaging and documentation.
- Add GameNames/GameSdk NuGet tooling, readable types, reobfuscation, complete source/PDB navigation and shared-runtime installation.
- Split plugin responsibilities, prefer typeof/nameof, centralize MSBuild settings and ignore local paths; remove the unused LoginUI test project.
- Fix login recovery and TLS through native platform HTTP and a private game relay; read device region directly from the OS time zone.

Validation: go test ./... -count=1, go vet ./..., plugin Release builds, SDK/runtime self-tests, Roslyn embedded-source navigation, gofmt check and git diff --check.
2026-10-03 23:04:32 +08:00

107 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))
{
DynamicTimeZoneInformation information;
if (GetDynamicTimeZoneInformation(out 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);
}