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
+6 -2
View File
@@ -80,7 +80,10 @@ internal class Popout : Window
if (!Tab.CanResize)
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)
{
@@ -195,7 +198,8 @@ internal class Popout : Window
public override void PostDraw()
{
ChatLogWindow.PopOutDocked[Idx] = ImGui.IsWindowDocked();
if (Idx >= 0 && Idx < ChatLogWindow.PopOutDocked.Count)
ChatLogWindow.PopOutDocked[Idx] = ImGui.IsWindowDocked();
if (Plugin.Config is { OverrideStyle: true, ChosenStyle: not null })
StyleModel.GetConfiguredStyles()?.FirstOrDefault(style => style.Name == Plugin.Config.ChosenStyle)?.Pop();
+28 -17
View File
@@ -181,28 +181,39 @@ internal sealed class Tabs : ISettingsTab
ImGui.SameLine();
var selectedWorld = worlds.FindIndex(world => world.RowId == tab.TellTarget.World);
if (selectedWorld == -1)
selectedWorld = 0;
using (var combo = ImRaii.Combo("###player-world", worlds[selectedWorld].Name.ToString()))
// 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)
{
if (combo.Success)
ImGui.TextDisabled("(no worlds available)");
}
else
{
var selectedWorld = worlds.FindIndex(world => world.RowId == tab.TellTarget.World);
if (selectedWorld == -1)
selectedWorld = 0;
using (var combo = ImRaii.Combo("###player-world", worlds[selectedWorld].Name.ToString()))
{
var lastDc = worlds.First().DataCenter.RowId;
foreach (var (idx, world) in worlds.Index())
if (combo.Success)
{
if (ImGui.Selectable(world.Name.ToString(), selectedWorld == idx))
var lastDc = worlds.First().DataCenter.RowId;
foreach (var (idx, world) in worlds.Index())
{
selectedWorld = idx;
tab.TellTarget.World = worlds[selectedWorld].RowId;
if (ImGui.Selectable(world.Name.ToString(), selectedWorld == idx))
{
selectedWorld = idx;
tab.TellTarget.World = worlds[selectedWorld].RowId;
}
if (lastDc == world.DataCenter.RowId)
continue;
lastDc = world.DataCenter.RowId;
ImGui.Separator();
}
if (lastDc == world.DataCenter.RowId)
continue;
lastDc = world.DataCenter.RowId;
ImGui.Separator();
}
}
}