fix(ui): bounds-guard out-of-range list access in pop-out and tabs UI

Two pre-existing upstream defects fixed in v1.0.0:

- Ui/Popout.cs PopOutDocked[Idx] now bounds-checks Idx against
  ChatLogWindow.PopOutDocked.Count before reading or writing. A
  popout instance can outlive a list resize when AddPopOutsToDraw()
  rebuilds the docked-state list while a draw frame is in flight,
  which previously produced an out-of-range crash on tab drop
- Ui/SettingsTabs/Tabs.cs guards against an empty worlds list before
  indexing worlds[selectedWorld]. Empty lists can occur briefly when
  switching characters or before the datacenter sheet finishes
  loading — the previous code would crash with an
  ArgumentOutOfRangeException
This commit is contained in:
2026-05-03 22:04:45 +02:00
parent a651b3b9ad
commit 6d49dbad3e
2 changed files with 34 additions and 19 deletions
+5 -1
View File
@@ -80,7 +80,10 @@ internal class Popout : Window
if (!Tab.CanResize) if (!Tab.CanResize)
Flags |= ImGuiWindowFlags.NoResize; Flags |= ImGuiWindowFlags.NoResize;
if (!ChatLogWindow.PopOutDocked[Idx]) // Idx may point past the end if PopOutDocked was resized (e.g., a tab
// dropped) between the AddPopOutsToDraw() snapshot and this frame.
// Guard the read so we don't index into stale state.
if (Idx >= 0 && Idx < ChatLogWindow.PopOutDocked.Count && !ChatLogWindow.PopOutDocked[Idx])
{ {
if (Tab.IndependentOpacity) if (Tab.IndependentOpacity)
{ {
@@ -195,6 +198,7 @@ internal class Popout : Window
public override void PostDraw() public override void PostDraw()
{ {
if (Idx >= 0 && Idx < ChatLogWindow.PopOutDocked.Count)
ChatLogWindow.PopOutDocked[Idx] = ImGui.IsWindowDocked(); ChatLogWindow.PopOutDocked[Idx] = ImGui.IsWindowDocked();
if (Plugin.Config is { OverrideStyle: true, ChosenStyle: not null }) if (Plugin.Config is { OverrideStyle: true, ChosenStyle: not null })
+11
View File
@@ -181,6 +181,16 @@ internal sealed class Tabs : ISettingsTab
ImGui.SameLine(); ImGui.SameLine();
// Guard against an empty worlds list — can happen briefly
// when switching characters or if the datacenter sheet
// has not yet populated. Without the guard the indexed
// access into worlds[selectedWorld] would crash.
if (worlds.Count == 0)
{
ImGui.TextDisabled("(no worlds available)");
}
else
{
var selectedWorld = worlds.FindIndex(world => world.RowId == tab.TellTarget.World); var selectedWorld = worlds.FindIndex(world => world.RowId == tab.TellTarget.World);
if (selectedWorld == -1) if (selectedWorld == -1)
selectedWorld = 0; selectedWorld = 0;
@@ -207,6 +217,7 @@ internal sealed class Tabs : ISettingsTab
} }
} }
} }
}
var target = (Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target) as IPlayerCharacter; var target = (Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target) as IPlayerCharacter;
using (ImRaii.Disabled(target == null)) using (ImRaii.Disabled(target == null))