Compare commits

...

4 Commits

Author SHA1 Message Date
56e6339553 hle: cheats: Prevent NullRef and throw a TamperCompilationException instead
for null base instruction byte arrays on the current block in EndConditionalBlock
2025-08-31 23:06:42 -05:00
042362ee2b Update Simplified Chinese translation. (ryubing/ryujinx!133)
See merge request ryubing/ryujinx!133
2025-08-30 22:40:05 -05:00
7347ee2212 [ci skip] chore: UI: Add localization key for LDN Game Viewer filters dropdown button heading 2025-08-30 22:13:38 -05:00
01a9b636af Memory changes 2.1 (ryubing/ryujinx!132)
See merge request ryubing/ryujinx!132
2025-08-30 20:30:17 -05:00
11 changed files with 222 additions and 119 deletions

View File

@ -24338,7 +24338,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "打开 LDN 游戏列表",
"zh_TW": ""
}
},
@ -24363,7 +24363,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "LDN 游戏浏览器 - {0} 个游戏",
"zh_TW": ""
}
},
@ -24388,7 +24388,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "搜索到 {0} 个 LDN 游戏...",
"zh_TW": ""
}
},
@ -24413,7 +24413,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "什么是 LDN",
"zh_TW": ""
}
},
@ -24438,7 +24438,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "在 {0} 时从服务器刷新可用游戏",
"zh_TW": ""
}
},
@ -24463,7 +24463,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "玩家计数 - 关闭",
"zh_TW": ""
}
},
@ -24488,7 +24488,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "玩家计数 - 递增",
"zh_TW": ""
}
},
@ -24513,12 +24513,37 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "玩家计数 - 递减",
"zh_TW": ""
}
},
{
"ID": "LdnGameListOnlyShowPublicGames",
"ID": "LdnGameListFiltersHeading",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Filters",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "筛选",
"zh_TW": ""
}
},
{
"ID": "LdnGameListFiltersOnlyShowPublicGames",
"Translations": {
"ar_SA": "",
"de_DE": "",
@ -24538,12 +24563,12 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "仅显示公开游戏",
"zh_TW": ""
}
},
{
"ID": "LdnGameListOnlyShowJoinableGames",
"ID": "LdnGameListFiltersOnlyShowJoinableGames",
"Translations": {
"ar_SA": "",
"de_DE": "",
@ -24563,7 +24588,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "仅显示可加入的游戏",
"zh_TW": ""
}
},
@ -24588,7 +24613,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "主服务器代理",
"zh_TW": ""
}
},
@ -24613,7 +24638,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": null,
"zh_TW": ""
}
},
@ -24638,7 +24663,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "通过 RyuLDN 服务器进行连接 (较慢)。",
"zh_TW": ""
}
},
@ -24663,7 +24688,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "通过 UPnP 进行点对点连接 (较快)。",
"zh_TW": ""
}
},
@ -24688,7 +24713,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "已创建: {0} ",
"zh_TW": ""
}
},
@ -24713,7 +24738,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "玩家 ({0} 之 {1}):",
"zh_TW": ""
}
},
@ -24738,7 +24763,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "可加入",
"zh_TW": ""
}
},
@ -24763,7 +24788,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "如果游戏是公开的或您知道口令则它是可加入的。",
"zh_TW": ""
}
},
@ -24788,7 +24813,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "不可加入",
"zh_TW": ""
}
},
@ -24813,7 +24838,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "游戏当前正在进行中。",
"zh_TW": ""
}
},
@ -24838,7 +24863,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "公开",
"zh_TW": ""
}
},
@ -24863,7 +24888,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "任何人都可以加入此游戏。",
"zh_TW": ""
}
},
@ -24888,7 +24913,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "私密",
"zh_TW": ""
}
},
@ -24913,7 +24938,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "如果您在设置中有某些 LDN 口令则可加入此游戏。",
"zh_TW": ""
}
}

View File

@ -81,16 +81,8 @@ namespace Ryujinx.Graphics.Device
if (index < Size)
{
uint alignedOffset = index * RegisterSize;
Func<int> readCallback = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_readCallbacks), (nint)index);
if (readCallback != null)
{
return readCallback();
}
else
{
return GetRefUnchecked<int>(alignedOffset);
}
return _readCallbacks[index]?.Invoke() ?? GetRefUnchecked<int>(alignedOffset);
}
return 0;
@ -107,7 +99,7 @@ namespace Ryujinx.Graphics.Device
GetRefIntAlignedUncheck(index) = data;
Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_writeCallbacks), (nint)index)?.Invoke(data);
_writeCallbacks[index]?.Invoke(data);
}
}
@ -124,7 +116,7 @@ namespace Ryujinx.Graphics.Device
changed = storage != data;
storage = data;
Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_writeCallbacks), (nint)index)?.Invoke(data);
_writeCallbacks[index]?.Invoke(data);
}
else
{

View File

@ -109,7 +109,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (index < BlockSize)
{
int groupIndex = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_registerToGroupMapping), (nint)index);
int groupIndex = _registerToGroupMapping[index];
if (groupIndex != 0)
{
groupIndex--;

View File

@ -120,11 +120,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
public void ExcludeModifiedRegions(ulong address, ulong size, Action<ulong, ulong> action)
{
// Slices a given region using the modified regions in the list. Calls the action for the new slices.
bool lockOwner = Lock.IsReadLockHeld;
if (!lockOwner)
{
Lock.EnterReadLock();
}
Lock.EnterReadLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
@ -145,10 +141,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
current = current.Next;
}
if (!lockOwner)
{
Lock.ExitReadLock();
}
Lock.ExitReadLock();
if ((long)size > 0)
{
@ -179,9 +172,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
return;
}
BufferModifiedRange buffPost = null;
bool extendsPost = false;
bool extendsPre = false;
if (first == last)
{
@ -196,14 +187,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (first.Address < address)
{
first.Value.Size = address - first.Address;
extendsPre = true;
Update(first);
if (first.EndAddress > endAddress)
{
buffPost = new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
first.Value.SyncNumber, first.Value.Parent);
extendsPost = true;
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
first.Value.SyncNumber, first.Value.Parent));
}
}
else
@ -212,6 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
first.Value.Size = first.EndAddress - endAddress;
first.Value.Address = endAddress;
Update(first);
}
else
{
@ -219,11 +209,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
if (extendsPre && extendsPost)
{
Add(buffPost);
}
Add(new BufferModifiedRange(address, size, syncNumber, this));
Lock.ExitWriteLock();
@ -231,6 +216,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
BufferModifiedRange buffPre = null;
BufferModifiedRange buffPost = null;
bool extendsPost = false;
bool extendsPre = false;
if (first.Address < address)
{
@ -329,7 +317,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
public bool HasRange(ulong address, ulong size)
{
Lock.EnterReadLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> _) = FindOverlaps(address, size);
RangeItem<BufferModifiedRange> first = FindOverlapFast(address, size);
bool result = first is not null;
Lock.ExitReadLock();
return result;
@ -386,9 +374,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong clampAddress = Math.Max(address, overlap.Address);
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress);
Lock.EnterWriteLock();
ClearPart(overlap, clampAddress, clampEnd);
Lock.ExitWriteLock();
RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
}
@ -418,40 +404,33 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong endAddress = address + size;
ulong currentSync = _context.SyncNumber;
int rangeCount = 0;
List<RangeItem<BufferModifiedRange>> overlaps = [];
// Range list must be consistent for this operation
Lock.EnterReadLock();
if (_migrationTarget != null)
{
rangeCount = -1;
}
else
{
// We use the non-span method here because the array is partially modified by the code, which would invalidate a span.
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
RangeItem<BufferModifiedRange> current = first;
while (last != null && current != last.Next)
{
rangeCount++;
overlaps.Add(current);
current = current.Next;
}
}
Lock.ExitReadLock();
if (rangeCount == -1)
{
_migrationTarget!.WaitForAndFlushRanges(address, size);
return;
}
Lock.EnterWriteLock();
// We use the non-span method here because the array is partially modified by the code, which would invalidate a span.
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
RangeItem<BufferModifiedRange> current = first;
while (last != null && current != last.Next)
{
overlaps.Add(current);
current = current.Next;
}
int rangeCount = overlaps.Count;
if (rangeCount == 0)
{
Lock.ExitWriteLock();
return;
}
@ -474,6 +453,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (highestDiff == long.MinValue)
{
Lock.ExitWriteLock();
return;
}
@ -481,6 +462,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
_context.Renderer.WaitSync(currentSync + (ulong)highestDiff);
RemoveRangesAndFlush(overlaps.ToArray(), rangeCount, highestDiff, currentSync, address, endAddress);
Lock.ExitWriteLock();
}
/// <summary>
@ -607,22 +590,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
return;
}
BufferModifiedRange buffPost = null;
bool extendsPost = false;
bool extendsPre = false;
if (first == last)
{
if (first.Address < address)
{
first.Value.Size = address - first.Address;
extendsPre = true;
Update(first);
if (first.EndAddress > endAddress)
{
buffPost = new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
first.Value.SyncNumber, first.Value.Parent);
extendsPost = true;
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
first.Value.SyncNumber, first.Value.Parent));
}
}
else
@ -631,6 +609,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
first.Value.Size = first.EndAddress - endAddress;
first.Value.Address = endAddress;
Update(first);
}
else
{
@ -638,16 +617,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
if (extendsPre && extendsPost)
{
Add(buffPost);
}
Lock.ExitWriteLock();
return;
}
BufferModifiedRange buffPre = null;
BufferModifiedRange buffPost = null;
bool extendsPost = false;
bool extendsPre = false;
if (first.Address < address)
{

View File

@ -50,7 +50,7 @@ namespace Ryujinx.HLE.HOS.Tamper
Logger.Error?.Print(LogClass.TamperMachine, ex.ToString());
}
Logger.Error?.Print(LogClass.TamperMachine, "There was a problem while compiling the Atmosphere cheat");
Logger.Error?.Print(LogClass.TamperMachine, $"There was a problem while compiling the Atmosphere cheat '{name}'");
return null;
}
@ -126,7 +126,7 @@ namespace Ryujinx.HLE.HOS.Tamper
DebugLog.Emit(instruction, context);
break;
default:
throw new TamperCompilationException($"Code type {codeType} not implemented in Atmosphere cheat");
throw new TamperCompilationException($"Code type {codeType} not implemented in Atmosphere cheat compiler");
}
}

View File

@ -40,7 +40,8 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
}
// Use the conditional begin instruction stored in the stack.
byte[] upperInstruction = context.CurrentBlock.BaseInstruction;
byte[] upperInstruction = context.CurrentBlock.BaseInstruction
?? throw new TamperCompilationException($"Base instruction in current block was null; termination type '{terminationType}'");
CodeType codeType = InstructionHelper.GetCodeType(upperInstruction);
// Pop the current block of operations from the stack so control instructions

View File

@ -87,6 +87,43 @@ namespace Ryujinx.Memory.Range
return false;
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The RangeItem to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(RangeItem<T> item)
{
int index = BinarySearch(item.Address);
RangeItem<T> rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next };
if (index > 0)
{
Items[index - 1].Next = rangeItem;
}
if (index < Count - 1)
{
Items[index + 1].Previous = rangeItem;
}
foreach (ulong addr in item.QuickAccessAddresses)
{
_quickAccess.Remove(addr);
_fastQuickAccess.Remove(addr);
}
Items[index] = rangeItem;
if (item.Address != rangeItem.Address)
_quickAccess.Remove(item.Address);
_quickAccess[rangeItem.Address] = rangeItem;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Insert(int index, RangeItem<T> item)
{
@ -193,10 +230,9 @@ namespace Ryujinx.Memory.Range
return;
}
int startIndex = BinarySearch(startItem.Address);
int endIndex = BinarySearch(endItem.Address);
(int startIndex, int endIndex) = BinarySearchEdges(startItem.Address, endItem.EndAddress);
for (int i = startIndex; i <= endIndex; i++)
for (int i = startIndex; i < endIndex; i++)
{
_quickAccess.Remove(Items[i].Address);
foreach (ulong addr in Items[i].QuickAccessAddresses)
@ -206,23 +242,23 @@ namespace Ryujinx.Memory.Range
}
}
if (endIndex < Count - 1)
if (endIndex < Count)
{
Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
Items[endIndex].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
}
if (startIndex > 0)
{
Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null;
Items[startIndex - 1].Next = endIndex < Count ? Items[endIndex] : null;
}
if (endIndex < Count - 1)
if (endIndex < Count)
{
Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1);
Array.Copy(Items, endIndex, Items, startIndex, Count - endIndex);
}
Count -= endIndex - startIndex + 1;
Count -= endIndex - startIndex;
}
/// <summary>

View File

@ -81,12 +81,73 @@ namespace Ryujinx.Memory.Range
{
if (Items[index].Value.Equals(item))
{
RangeItem<T> rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next };
if (index > 0)
{
Items[index - 1].Next = rangeItem;
}
if (index < Count - 1)
{
Items[index + 1].Previous = rangeItem;
}
foreach (ulong address in Items[index].QuickAccessAddresses)
{
_quickAccess.Remove(address);
}
Items[index] = new RangeItem<T>(item);
Items[index] = rangeItem;
return true;
}
if (Items[index].Address > item.Address)
{
break;
}
index++;
}
}
return false;
}
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The RangeItem to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected override bool Update(RangeItem<T> item)
{
int index = BinarySearch(item.Address);
if (index >= 0)
{
while (index < Count)
{
if (Items[index].Equals(item))
{
RangeItem<T> rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next };
if (index > 0)
{
Items[index - 1].Next = rangeItem;
}
if (index < Count - 1)
{
Items[index + 1].Previous = rangeItem;
}
foreach (ulong address in item.QuickAccessAddresses)
{
_quickAccess.Remove(address);
}
Items[index] = rangeItem;
return true;
}

View File

@ -30,7 +30,7 @@ namespace Ryujinx.Memory.Range
return u1 == u2;
}
public int GetHashCode(ulong value) => (int)(value >> 5);
public int GetHashCode(ulong value) => (int)(value << 5);
public static readonly AddressEqualityComparer Comparer = new();
}
@ -63,6 +63,13 @@ namespace Ryujinx.Memory.Range
/// <returns>True if the item was located and updated, false otherwise</returns>
protected abstract bool Update(T item);
/// <summary>
/// Updates an item's end address on the list. Address must be the same.
/// </summary>
/// <param name="item">The RangeItem to be updated</param>
/// <returns>True if the item was located and updated, false otherwise</returns>
protected abstract bool Update(RangeItem<T> item);
public abstract bool Remove(T item);
public abstract void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem);

View File

@ -182,11 +182,15 @@ namespace Ryujinx.Memory.Tracking
{
if (region.Guest)
{
_guestVirtualRegions.Lock.EnterWriteLock();
_guestVirtualRegions.Remove(region);
_guestVirtualRegions.Lock.ExitWriteLock();
}
else
{
_virtualRegions.Lock.EnterWriteLock();
_virtualRegions.Remove(region);
_virtualRegions.Lock.ExitWriteLock();
}
}

View File

@ -115,7 +115,7 @@
Margin="10, 0, 148, 0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="Filters"
Content="{ext:Locale LdnGameListFiltersHeading}"
DockPanel.Dock="Right">
<DropDownButton.Flyout>
<Flyout Placement="Bottom">
@ -124,10 +124,10 @@
<TextBlock Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
</CheckBox>
<CheckBox IsChecked="{Binding OnlyShowPublicGames}">
<TextBlock Text="{ext:Locale LdnGameListOnlyShowPublicGames}" />
<TextBlock Text="{ext:Locale LdnGameListFiltersOnlyShowPublicGames}" />
</CheckBox>
<CheckBox IsChecked="{Binding OnlyShowJoinableGames}">
<TextBlock Text="{ext:Locale LdnGameListOnlyShowJoinableGames}" />
<TextBlock Text="{ext:Locale LdnGameListFiltersOnlyShowJoinableGames}" />
</CheckBox>
</StackPanel>
</Flyout>
@ -213,7 +213,7 @@
Margin="10, 5, 20, 5"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="Filters"
Content="{ext:Locale LdnGameListFiltersHeading}"
DockPanel.Dock="Right">
<DropDownButton.Flyout>
<Flyout Placement="Bottom">
@ -222,10 +222,10 @@
<TextBlock Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
</CheckBox>
<CheckBox IsChecked="{Binding OnlyShowPublicGames}">
<TextBlock Text="{ext:Locale LdnGameListOnlyShowPublicGames}" />
<TextBlock Text="{ext:Locale LdnGameListFiltersOnlyShowPublicGames}" />
</CheckBox>
<CheckBox IsChecked="{Binding OnlyShowJoinableGames}">
<TextBlock Text="{ext:Locale LdnGameListOnlyShowJoinableGames}" />
<TextBlock Text="{ext:Locale LdnGameListFiltersOnlyShowJoinableGames}" />
</CheckBox>
</StackPanel>
</Flyout>