Explorar el Código

time: Make TimeZoneRule blittable and avoid copies (#3361)

* time: Make TimeZoneRule blittable and avoid copies

This drastically reduce overhead of using TimeZoneRule around the
codebase.

Effect on games is unknown

* Add missing Box type

* Ensure we clean the structure still

This doesn't perform any copies

* Address gdkchan's comments

* Simplify Box
Mary hace 3 años
padre
commit
30ee70a9bc

+ 12 - 0
Ryujinx.Common/Memory/Box.cs

@@ -0,0 +1,12 @@
+namespace Ryujinx.Common.Memory
+{
+    public class Box<T> where T : unmanaged
+    {
+        public T Data;
+
+        public Box()
+        {
+            Data = new T();
+        }
+    }
+}

+ 7 - 7
Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs

@@ -2,7 +2,10 @@ using Ryujinx.Common.Logging;
 using Ryujinx.Cpu;
 using Ryujinx.HLE.HOS.Services.Time.TimeZone;
 using Ryujinx.HLE.Utilities;
+using Ryujinx.Memory;
 using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 using System.Text;
 
 namespace Ryujinx.HLE.HOS.Services.Time.StaticService
@@ -100,15 +103,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
 
             string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24);
 
-            ResultCode resultCode = _timeZoneContentManager.LoadTimeZoneRule(out TimeZoneRule rules, locationName);
-
-            // Write TimeZoneRule if success
-            if (resultCode == ResultCode.Success)
+            using (WritableRegion region = context.Memory.GetWritableRegion(bufferPosition, Unsafe.SizeOf<TimeZoneRule>()))
             {
-                MemoryHelper.Write(context.Memory, bufferPosition, rules);
-            }
+                ref TimeZoneRule rules = ref MemoryMarshal.Cast<byte, TimeZoneRule>(region.Memory.Span)[0];
 
-            return resultCode;
+                return _timeZoneContentManager.LoadTimeZoneRule(ref rules, locationName);
+            }
         }
 
         [CommandHipc(100)]

+ 11 - 8
Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs

@@ -4,9 +4,12 @@ using Ryujinx.Cpu;
 using Ryujinx.HLE.HOS.Services.Time.Clock;
 using Ryujinx.HLE.HOS.Services.Time.TimeZone;
 using Ryujinx.HLE.Utilities;
+using Ryujinx.Memory;
 using System;
 using System.Diagnostics;
 using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 
 namespace Ryujinx.HLE.HOS.Services.Time.StaticService
 {
@@ -165,11 +168,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
 
             using (MemoryStream timeZoneBinaryStream = new MemoryStream(temp))
             {
-                result = _timeZoneManager.ParseTimeZoneRuleBinary(out TimeZoneRule timeZoneRule, timeZoneBinaryStream);
-
-                if (result == ResultCode.Success)
+                using (WritableRegion region = context.Memory.GetWritableRegion(timeZoneRuleBufferPosition, Unsafe.SizeOf<TimeZoneRule>()))
                 {
-                    MemoryHelper.Write(context.Memory, timeZoneRuleBufferPosition, timeZoneRule);
+                    ref TimeZoneRule rule = ref MemoryMarshal.Cast<byte, TimeZoneRule>(region.Memory.Span)[0];
+
+                    result = _timeZoneManager.ParseTimeZoneRuleBinary(ref rule, timeZoneBinaryStream);
                 }
             }
 
@@ -199,9 +202,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
                 throw new InvalidOperationException();
             }
 
-            TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, bufferPosition);
+            ReadOnlySpan<TimeZoneRule> rules = MemoryMarshal.Cast<byte, TimeZoneRule>(context.Memory.GetSpan(bufferPosition, (int)bufferSize));
 
-            ResultCode resultCode = _timeZoneManager.ToCalendarTime(rules, posixTime, out CalendarInfo calendar);
+            ResultCode resultCode = _timeZoneManager.ToCalendarTime(in rules[0], posixTime, out CalendarInfo calendar);
 
             if (resultCode == 0)
             {
@@ -244,9 +247,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
                 throw new InvalidOperationException();
             }
 
-            TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, inBufferPosition);
+            ReadOnlySpan<TimeZoneRule> rules = MemoryMarshal.Cast<byte, TimeZoneRule>(context.Memory.GetSpan(inBufferPosition, (int)inBufferSize));
 
-            ResultCode resultCode = _timeZoneManager.ToPosixTime(rules, calendarTime, out long posixTime);
+            ResultCode resultCode = _timeZoneManager.ToPosixTime(in rules[0], calendarTime, out long posixTime);
 
             if (resultCode == ResultCode.Success)
             {

+ 54 - 68
Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs

@@ -1,4 +1,5 @@
 using Ryujinx.Common;
+using Ryujinx.Common.Memory;
 using Ryujinx.Common.Utilities;
 using Ryujinx.HLE.Utilities;
 using System;
@@ -38,7 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             new int[] { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
         };
 
-        private const string TimeZoneDefaultRule = ",M4.1.0,M10.5.0";
+        private static readonly byte[] TimeZoneDefaultRule = Encoding.ASCII.GetBytes(",M4.1.0,M10.5.0");
 
         [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x10)]
         private struct CalendarTimeInternal
@@ -133,7 +134,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return (t1 - t0) == SecondsPerRepeat;
         }
 
-        private static bool TimeTypeEquals(TimeZoneRule outRules, byte aIndex, byte bIndex)
+        private static bool TimeTypeEquals(in TimeZoneRule outRules, byte aIndex, byte bIndex)
         {
             if (aIndex < 0 || aIndex >= outRules.TypeCount || bIndex < 0 || bIndex >= outRules.TypeCount)
             {
@@ -150,7 +151,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                    StringUtils.CompareCStr(outRules.Chars[a.AbbreviationListIndex..], outRules.Chars[b.AbbreviationListIndex..]) == 0;
         }
 
-        private static int GetQZName(ReadOnlySpan<char> name, int namePosition, char delimiter)
+        private static int GetQZName(ReadOnlySpan<byte> name, int namePosition, char delimiter)
         {
             int i = namePosition;
 
@@ -162,13 +163,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return i;
         }
 
-        private static int GetTZName(char[] name, int namePosition)
+        private static int GetTZName(ReadOnlySpan<byte> name, int namePosition)
         {
             int i = namePosition;
 
             char c;
 
-            while ((c = name[i]) != '\0' && !char.IsDigit(c) && c != ',' && c != '-' && c != '+')
+            while ((c = (char)name[i]) != '\0' && !char.IsDigit(c) && c != ',' && c != '-' && c != '+')
             {
                 i++;
             }
@@ -176,7 +177,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return i;
         }
 
-        private static bool GetNum(char[] name, ref int namePosition, out int num, int min, int max)
+        private static bool GetNum(ReadOnlySpan<byte> name, ref int namePosition, out int num, int min, int max)
         {
             num = 0;
 
@@ -185,7 +186,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                 return false;
             }
 
-            char c = name[namePosition];
+            char c = (char)name[namePosition];
 
             if (!char.IsDigit(c))
             {
@@ -205,7 +206,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                     return false;
                 }
 
-                c = name[namePosition];
+                c = (char)name[namePosition];
             }
             while (char.IsDigit(c));
 
@@ -217,7 +218,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return true;
         }
 
-        private static bool GetSeconds(char[] name, ref int namePosition, out int seconds)
+        private static bool GetSeconds(ReadOnlySpan<byte> name, ref int namePosition, out int seconds)
         {
             seconds = 0;
 
@@ -266,7 +267,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return true;
         }
 
-        private static bool GetOffset(char[] name, ref int namePosition, ref int offset)
+        private static bool GetOffset(ReadOnlySpan<byte> name, ref int namePosition, ref int offset)
         {
             bool isNegative = false;
 
@@ -304,7 +305,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return true;
         }
 
-        private static bool GetRule(char[] name, ref int namePosition, out Rule rule)
+        private static bool GetRule(ReadOnlySpan<byte> name, ref int namePosition, out Rule rule)
         {
             rule = new Rule();
 
@@ -347,7 +348,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
 
                 isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerWekk - 1);
             }
-            else if (char.IsDigit(name[namePosition]))
+            else if (char.IsDigit((char)name[namePosition]))
             {
                 rule.Type = RuleType.DayOfYear;
                 isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerLYear - 1);
@@ -385,19 +386,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return 0;
         }
 
-        private static bool ParsePosixName(ReadOnlySpan<char> name, out TimeZoneRule outRules, bool lastDitch)
+        private static bool ParsePosixName(ReadOnlySpan<byte> name, ref TimeZoneRule outRules, bool lastDitch)
         {
-            outRules = new TimeZoneRule
-            {
-                Ats   = new long[TzMaxTimes],
-                Types = new byte[TzMaxTimes],
-                Ttis  = new TimeTypeInfo[TzMaxTypes],
-                Chars = new char[TzCharsArraySize]
-            };
+            outRules = new TimeZoneRule();
 
             int        stdLen;
 
-            ReadOnlySpan<char> stdName = name;
+            ReadOnlySpan<byte> stdName = name;
             int namePosition = 0;
             int stdOffset = 0;
 
@@ -428,7 +423,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                 }
                 else
                 {
-                    namePosition = GetTZName(name.ToArray(), namePosition);
+                    namePosition = GetTZName(name, namePosition);
                     stdLen = namePosition;
                 }
 
@@ -449,7 +444,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             int destLen   = 0;
             int dstOffset = 0;
 
-            ReadOnlySpan<char> destName = name.Slice(namePosition);
+            ReadOnlySpan<byte> destName = name.Slice(namePosition);
 
             if (TzCharsArraySize < charCount)
             {
@@ -476,7 +471,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                 else
                 {
                     destName     = name.Slice(namePosition);
-                    namePosition = GetTZName(name.ToArray(), namePosition);
+                    namePosition = GetTZName(name, namePosition);
                     destLen      = namePosition;
                 }
 
@@ -507,7 +502,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
 
                 if (name[namePosition] == '\0')
                 {
-                    name = TimeZoneDefaultRule.ToCharArray();
+                    name = TimeZoneDefaultRule;
                     namePosition = 0;
                 }
 
@@ -515,7 +510,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                 {
                     namePosition++;
 
-                    bool IsRuleValid = GetRule(name.ToArray(), ref namePosition, out Rule start);
+                    bool IsRuleValid = GetRule(name, ref namePosition, out Rule start);
                     if (!IsRuleValid)
                     {
                         return false;
@@ -526,7 +521,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                         return false;
                     }
 
-                    IsRuleValid = GetRule(name.ToArray(), ref namePosition, out Rule end);
+                    IsRuleValid = GetRule(name, ref namePosition, out Rule end);
                     if (!IsRuleValid)
                     {
                         return false;
@@ -738,7 +733,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             }
 
             charsPosition += stdLen;
-            outRules.Chars[charsPosition++] = '\0';
+            outRules.Chars[charsPosition++] = 0;
 
             if (destLen != 0)
             {
@@ -746,7 +741,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                 {
                     outRules.Chars[charsPosition + i] = destName[i];
                 }
-                outRules.Chars[charsPosition + destLen] = '\0';
+                outRules.Chars[charsPosition + destLen] = 0;
             }
 
             return true;
@@ -882,20 +877,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             }
         }
 
-        internal static bool ParsePosixName(string name, out TimeZoneRule outRules)
+        internal static bool ParsePosixName(string name, ref TimeZoneRule outRules)
         {
-            return ParsePosixName(name.ToCharArray(), out outRules, false);
+            return ParsePosixName(Encoding.ASCII.GetBytes(name), ref outRules, false);
         }
 
-        internal static bool ParseTimeZoneBinary(out TimeZoneRule outRules, Stream inputData)
+        internal static bool ParseTimeZoneBinary(ref TimeZoneRule outRules, Stream inputData)
         {
-            outRules = new TimeZoneRule
-            {
-                Ats   = new long[TzMaxTimes],
-                Types = new byte[TzMaxTimes],
-                Ttis  = new TimeTypeInfo[TzMaxTypes],
-                Chars = new char[TzCharsArraySize]
-            };
+            outRules = new TimeZoneRule();
 
             BinaryReader reader = new BinaryReader(inputData);
 
@@ -1020,10 +1009,10 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                     outRules.Ttis[i] = ttis;
                 }
 
-                Encoding.ASCII.GetChars(p[..outRules.CharCount].ToArray()).CopyTo(outRules.Chars.AsSpan());
+                p[..outRules.CharCount].CopyTo(outRules.Chars);
 
                 p = p[outRules.CharCount..];
-                outRules.Chars[outRules.CharCount] = '\0';
+                outRules.Chars[outRules.CharCount] = 0;
 
                 for (int i = 0; i < outRules.TypeCount; i++)
                 {
@@ -1077,27 +1066,30 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                     throw new InvalidOperationException();
                 }
 
-                char[] tempName = new char[TzNameMax + 1];
+                byte[] tempName = new byte[TzNameMax + 1];
                 Array.Copy(workBuffer, position, tempName, 0, nRead);
 
                 if (nRead > 2 && tempName[0] == '\n' && tempName[nRead - 1] == '\n' && outRules.TypeCount + 2 <= TzMaxTypes)
                 {
-                    tempName[nRead - 1] = '\0';
+                    tempName[nRead - 1] = 0;
 
-                    char[] name = new char[TzNameMax];
+                    byte[] name = new byte[TzNameMax];
                     Array.Copy(tempName, 1, name, 0, nRead - 1);
 
-                    if (ParsePosixName(name, out TimeZoneRule tempRules, false))
+                    Box<TimeZoneRule> tempRulesBox = new Box<TimeZoneRule>();
+                    ref TimeZoneRule tempRules = ref tempRulesBox.Data;
+
+                    if (ParsePosixName(name, ref tempRulesBox.Data, false))
                     {
                         int abbreviationCount = 0;
                         charCount = outRules.CharCount;
 
-                        Span<char> chars = outRules.Chars;
+                        Span<byte> chars = outRules.Chars;
 
                         for (int i = 0; i < tempRules.TypeCount; i++)
                         {
-                            ReadOnlySpan<char> tempChars = tempRules.Chars;
-                            ReadOnlySpan<char> tempAbbreviation = tempChars[tempRules.Ttis[i].AbbreviationListIndex..];
+                            ReadOnlySpan<byte> tempChars = tempRules.Chars;
+                            ReadOnlySpan<byte> tempAbbreviation = tempChars[tempRules.Ttis[i].AbbreviationListIndex..];
 
                             int j;
 
@@ -1175,7 +1167,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                 {
                     for (int i = 1; i < outRules.TimeCount; i++)
                     {
-                        if (TimeTypeEquals(outRules, outRules.Types[i], outRules.Types[0]) && DifferByRepeat(outRules.Ats[i], outRules.Ats[0]))
+                        if (TimeTypeEquals(in outRules, outRules.Types[i], outRules.Types[0]) && DifferByRepeat(outRules.Ats[i], outRules.Ats[0]))
                         {
                             outRules.GoBack = true;
                             break;
@@ -1184,7 +1176,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
 
                     for (int i = outRules.TimeCount - 2; i >= 0; i--)
                     {
-                        if (TimeTypeEquals(outRules, outRules.Types[outRules.TimeCount - 1], outRules.Types[i]) && DifferByRepeat(outRules.Ats[outRules.TimeCount - 1], outRules.Ats[i]))
+                        if (TimeTypeEquals(in outRules, outRules.Types[outRules.TimeCount - 1], outRules.Types[i]) && DifferByRepeat(outRules.Ats[outRules.TimeCount - 1], outRules.Ats[i]))
                         {
                             outRules.GoAhead = true;
                             break;
@@ -1259,10 +1251,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             long remainingSeconds = time % SecondsPerDay;
 
             calendarTime           = new CalendarTimeInternal();
-            calendarAdditionalInfo = new CalendarAdditionalInfo()
-            {
-                TimezoneName = new char[8]
-            };
+            calendarAdditionalInfo = new CalendarAdditionalInfo();
 
             while (timeDays < 0 || timeDays >= YearLengths[IsLeap((int)year)])
             {
@@ -1353,13 +1342,10 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return 0;
         }
 
-        private static ResultCode ToCalendarTimeInternal(TimeZoneRule rules, long time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo)
+        private static ResultCode ToCalendarTimeInternal(in TimeZoneRule rules, long time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo)
         {
             calendarTime           = new CalendarTimeInternal();
-            calendarAdditionalInfo = new CalendarAdditionalInfo()
-            {
-                TimezoneName = new char[8]
-            };
+            calendarAdditionalInfo = new CalendarAdditionalInfo();
 
             ResultCode result;
 
@@ -1398,7 +1384,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                     return ResultCode.TimeNotFound;
                 }
 
-                result = ToCalendarTimeInternal(rules, newTime, out calendarTime, out calendarAdditionalInfo);
+                result = ToCalendarTimeInternal(in rules, newTime, out calendarTime, out calendarAdditionalInfo);
                 if (result != 0)
                 {
                     return result;
@@ -1450,17 +1436,17 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             {
                 calendarAdditionalInfo.IsDaySavingTime = rules.Ttis[ttiIndex].IsDaySavingTime;
 
-                ReadOnlySpan<char> timeZoneAbbreviation = rules.Chars.AsSpan()[rules.Ttis[ttiIndex].AbbreviationListIndex..];
+                ReadOnlySpan<byte> timeZoneAbbreviation = rules.Chars[rules.Ttis[ttiIndex].AbbreviationListIndex..];
 
                 int timeZoneSize = Math.Min(StringUtils.LengthCstr(timeZoneAbbreviation), 8);
 
-                timeZoneAbbreviation[..timeZoneSize].CopyTo(calendarAdditionalInfo.TimezoneName.AsSpan());
+                timeZoneAbbreviation[..timeZoneSize].CopyTo(calendarAdditionalInfo.TimezoneName.ToSpan());
             }
 
             return result;
         }
 
-        private static ResultCode ToPosixTimeInternal(TimeZoneRule rules, CalendarTimeInternal calendarTime, out long posixTime)
+        private static ResultCode ToPosixTimeInternal(in TimeZoneRule rules, CalendarTimeInternal calendarTime, out long posixTime)
         {
             posixTime = 0;
 
@@ -1604,7 +1590,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
 
                 int direction;
 
-                ResultCode result = ToCalendarTimeInternal(rules, pivot, out CalendarTimeInternal candidateCalendarTime, out _);
+                ResultCode result = ToCalendarTimeInternal(in rules, pivot, out CalendarTimeInternal candidateCalendarTime, out _);
                 if (result != 0)
                 {
                     if (pivot > 0)
@@ -1675,9 +1661,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return ResultCode.Success;
         }
 
-        internal static ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar)
+        internal static ResultCode ToCalendarTime(in TimeZoneRule rules, long time, out CalendarInfo calendar)
         {
-            ResultCode result = ToCalendarTimeInternal(rules, time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo);
+            ResultCode result = ToCalendarTimeInternal(in rules, time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo);
 
             calendar = new CalendarInfo()
             {
@@ -1697,7 +1683,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return result;
         }
 
-        internal static ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
+        internal static ResultCode ToPosixTime(in TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
         {
             CalendarTimeInternal calendarTimeInternal = new CalendarTimeInternal()
             {
@@ -1710,7 +1696,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                 Second = calendarTime.Second
             };
 
-            return ToPosixTimeInternal(rules, calendarTimeInternal, out posixTime);
+            return ToPosixTimeInternal(in rules, calendarTimeInternal, out posixTime);
         }
     }
 }

+ 12 - 14
Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs

@@ -16,7 +16,7 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 
-using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
+using TimeZoneRuleBox = Ryujinx.Common.Memory.Box<Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule>;
 
 namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
 {
@@ -149,7 +149,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                         continue;
                     }
 
-                    TimeZone.ParseTimeZoneBinary(out TimeZoneRule tzRule, tzif.Get.AsStream());
+                    TimeZoneRuleBox tzRuleBox = new TimeZoneRuleBox();
+                    ref TimeZoneRule tzRule = ref tzRuleBox.Data;
+
+                    TimeZone.ParseTimeZoneBinary(ref tzRule, tzif.Get.AsStream());
+
 
                     TimeTypeInfo ttInfo;
                     if (tzRule.TimeCount > 0) // Find the current transition period
@@ -174,10 +178,10 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                         continue;
                     }
 
-                    var abbrStart = tzRule.Chars.AsSpan(ttInfo.AbbreviationListIndex);
-                    int abbrEnd = abbrStart.IndexOf('\0');
+                    var abbrStart = tzRule.Chars[ttInfo.AbbreviationListIndex..];
+                    int abbrEnd = abbrStart.IndexOf((byte)0);
 
-                    outList.Add((ttInfo.GmtOffset, locName, abbrStart.Slice(0, abbrEnd).ToString()));
+                    outList.Add((ttInfo.GmtOffset, locName, abbrStart[..abbrEnd].ToString()));
                 }
             }
 
@@ -276,15 +280,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return (ResultCode)result.Value;
         }
 
-        internal ResultCode LoadTimeZoneRule(out TimeZoneRule outRules, string locationName)
+        internal ResultCode LoadTimeZoneRule(ref TimeZoneRule rules, string locationName)
         {
-            outRules = new TimeZoneRule
-            {
-                Ats   = new long[TzMaxTimes],
-                Types = new byte[TzMaxTimes],
-                Ttis  = new TimeTypeInfo[TzMaxTypes],
-                Chars = new char[TzCharsArraySize]
-            };
+            rules = default;
 
             if (!HasTimeZoneBinaryTitle())
             {
@@ -295,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
 
             if (result == ResultCode.Success)
             {
-                result = Manager.ParseTimeZoneRuleBinary(out outRules, timeZoneBinaryStream);
+                result = Manager.ParseTimeZoneRuleBinary(ref rules, timeZoneBinaryStream);
 
                 ncaFile.Dispose();
             }

+ 15 - 21
Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs

@@ -1,14 +1,14 @@
-using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
 using Ryujinx.HLE.Utilities;
 using System.IO;
-using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
 
 namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
 {
     class TimeZoneManager
     {
         private bool                 _isInitialized;
-        private TimeZoneRule         _myRules;
+        private Box<TimeZoneRule>    _myRules;
         private string               _deviceLocationName;
         private UInt128              _timeZoneRuleVersion;
         private uint                 _totalLocationNameCount;
@@ -21,15 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             _deviceLocationName  = "UTC";
             _timeZoneRuleVersion = new UInt128();
             _lock                = new object();
-
-            // Empty rules
-            _myRules = new TimeZoneRule
-            {
-                Ats   = new long[TzMaxTimes],
-                Types = new byte[TzMaxTimes],
-                Ttis  = new TimeTypeInfo[TzMaxTypes],
-                Chars = new char[TzCharsArraySize]
-            };
+            _myRules             = new Box<TimeZoneRule>();
 
             _timeZoneUpdateTimePoint = SteadyClockTimePoint.GetRandom();
         }
@@ -78,7 +70,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
 
             lock (_lock)
             {
-                bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out TimeZoneRule rules, timeZoneBinaryStream);
+                Box<TimeZoneRule> rules = new Box<TimeZoneRule>();
+
+                bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(ref rules.Data, timeZoneBinaryStream);
 
                 if (timeZoneConversionSuccess)
                 {
@@ -154,13 +148,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return result;
         }
 
-        public ResultCode ParseTimeZoneRuleBinary(out TimeZoneRule outRules, Stream timeZoneBinaryStream)
+        public ResultCode ParseTimeZoneRuleBinary(ref TimeZoneRule outRules, Stream timeZoneBinaryStream)
         {
             ResultCode result = ResultCode.Success;
 
             lock (_lock)
             {
-                bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out outRules, timeZoneBinaryStream);
+                bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(ref outRules, timeZoneBinaryStream);
 
                 if (!timeZoneConversionSuccess)
                 {
@@ -208,7 +202,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             {
                 if (_isInitialized)
                 {
-                    result = ToCalendarTime(_myRules, time, out calendar);
+                    result = ToCalendarTime(in _myRules.Data, time, out calendar);
                 }
                 else
                 {
@@ -220,13 +214,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return result;
         }
 
-        public ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar)
+        public ResultCode ToCalendarTime(in TimeZoneRule rules, long time, out CalendarInfo calendar)
         {
             ResultCode result;
 
             lock (_lock)
             {
-                result = TimeZone.ToCalendarTime(rules, time, out calendar);
+                result = TimeZone.ToCalendarTime(in rules, time, out calendar);
             }
 
             return result;
@@ -240,7 +234,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             {
                 if (_isInitialized)
                 {
-                    result = ToPosixTime(_myRules, calendarTime, out posixTime);
+                    result = ToPosixTime(in _myRules.Data, calendarTime, out posixTime);
                 }
                 else
                 {
@@ -252,13 +246,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
             return result;
         }
 
-        public ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
+        public ResultCode ToPosixTime(in TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
         {
             ResultCode result;
 
             lock (_lock)
             {
-                result = TimeZone.ToPosixTime(rules, calendarTime, out posixTime);
+                result = TimeZone.ToPosixTime(in rules, calendarTime, out posixTime);
             }
 
             return result;

+ 4 - 5
Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarAdditionalInfo.cs

@@ -1,4 +1,5 @@
-using System.Runtime.InteropServices;
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
 
 namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
 {
@@ -8,14 +9,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
         public uint DayOfWeek;
         public uint DayOfYear;
 
-        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
-        public char[] TimezoneName;
+        public Array8<byte> TimezoneName;
 
         [MarshalAs(UnmanagedType.I1)]
         public bool IsDaySavingTime;
 
-        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
-        public char[] Padding;
+        public Array3<byte> Padding;
 
         public int GmtOffset;
     }

+ 7 - 6
Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeTypeInfo.cs

@@ -1,17 +1,19 @@
-using System.Runtime.InteropServices;
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
 
 namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
 {
-    [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
+    [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 4)]
     struct TimeTypeInfo
     {
+        public const int Size = 0x10;
+
         public int GmtOffset;
 
         [MarshalAs(UnmanagedType.I1)]
         public bool IsDaySavingTime;
 
-        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
-        public char[] Padding1;
+        public Array3<byte> Padding1;
 
         public int AbbreviationListIndex;
 
@@ -21,7 +23,6 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
         [MarshalAs(UnmanagedType.I1)]
         public bool IsGMT;
 
-        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
-        public char[] Padding2;
+        public ushort Padding2;
     }
 }

+ 32 - 15
Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeZoneRule.cs

@@ -1,16 +1,18 @@
-using System.Runtime.InteropServices;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
 
 namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
 {
     [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x4000, CharSet = CharSet.Ansi)]
     struct TimeZoneRule
     {
-        public const int TzMaxTypes        = 128;
-        public const int TzMaxChars        = 50;
-        public const int TzMaxLeaps        = 50;
-        public const int TzMaxTimes        = 1000;
-        public const int TzNameMax         = 255;
-        public const int TzCharsArraySize  = 2 * (TzNameMax + 1);
+        public const int TzMaxTypes = 128;
+        public const int TzMaxChars = 50;
+        public const int TzMaxLeaps = 50;
+        public const int TzMaxTimes = 1000;
+        public const int TzNameMax = 255;
+        public const int TzCharsArraySize = 2 * (TzNameMax + 1);
 
         public int TimeCount;
         public int TypeCount;
@@ -22,17 +24,32 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
         [MarshalAs(UnmanagedType.I1)]
         public bool GoAhead;
 
-        [MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTimes)]
-        public long[] Ats;
+        [StructLayout(LayoutKind.Sequential, Size = sizeof(long) * TzMaxTimes)]
+        private struct AtsStorageStruct {}
 
-        [MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTimes)]
-        public byte[] Types;
+        private AtsStorageStruct _ats;
 
-        [MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTypes)]
-        public TimeTypeInfo[] Ttis;
+        public Span<long> Ats => SpanHelpers.AsSpan<AtsStorageStruct, long>(ref _ats);
 
-        [MarshalAs(UnmanagedType.ByValArray, SizeConst = TzCharsArraySize)]
-        public char[] Chars;
+        [StructLayout(LayoutKind.Sequential, Size = sizeof(byte) * TzMaxTimes)]
+        private struct TypesStorageStruct {}
+
+        private TypesStorageStruct _types;
+
+        public Span<byte> Types => SpanHelpers.AsByteSpan<TypesStorageStruct>(ref _types);
+
+        [StructLayout(LayoutKind.Sequential, Size = TimeTypeInfo.Size * TzMaxTimes)]
+        private struct TimeTypeInfoStorageStruct { }
+
+        private TimeTypeInfoStorageStruct _ttis;
+
+        public Span<TimeTypeInfo> Ttis => SpanHelpers.AsSpan<TimeTypeInfoStorageStruct, TimeTypeInfo>(ref _ttis);
+
+        [StructLayout(LayoutKind.Sequential, Size = sizeof(byte) * TzCharsArraySize)]
+        private struct CharsStorageStruct {}
+
+        private CharsStorageStruct _chars;
+        public Span<byte> Chars => SpanHelpers.AsByteSpan(ref _chars);
 
         public int DefaultType;
     }

+ 3 - 3
Ryujinx.HLE/Utilities/StringUtils.cs

@@ -128,7 +128,7 @@ namespace Ryujinx.HLE.Utilities
             }
         }
 
-        public static int CompareCStr(ReadOnlySpan<char> s1, ReadOnlySpan<char> s2)
+        public static int CompareCStr(ReadOnlySpan<byte> s1, ReadOnlySpan<byte> s2)
         {
             int s1Index = 0;
             int s2Index = 0;
@@ -142,11 +142,11 @@ namespace Ryujinx.HLE.Utilities
             return s2[s2Index] - s1[s1Index];
         }
 
-        public static int LengthCstr(ReadOnlySpan<char> s)
+        public static int LengthCstr(ReadOnlySpan<byte> s)
         {
             int i = 0;
 
-            while (s[i] != '\0')
+            while (s[i] != 0)
             {
                 i++;
             }