File Details
Loothing v2.0.18
- R
- Apr 16, 2026
- 803.61 KB
- 26
- 12.0.5+1
- Retail
File Name
loothing_2.0.18.zip
Supported Versions
- 12.0.5
- 12.0.1
Fixed
- Observer settings were silently overwritten by the Master Looter's config. When the ML broadcast their observer roster, non-ML receivers were persisting the ML's permissions, open-observation flag, and ML-as-observer flag into their own saved profile. After the raid, if you were later your own ML on a different toon, your observer settings were whatever the previous raid's ML had configured. The remote data is now held only in-memory for the duration of the session and never written to your saved variables; the authoritative MLDB session-settings apply-and-restore path still handles legitimate per-session application.
- Replay-protection dedup now covers legacy v3 messages. The addon's replay-protection dedup (introduced with Protocol v4) keyed on a monotonic message ID that v3 senders don't include, which meant any v3-formatted message — including one a peer had crafted to bypass dedup — slipped past the dedup table entirely. v3 messages now dedup on a content hash (sender + Adler-32 of the encoded blob + length) within the same 120-second window that v4 uses.
- Session hijack protection. A non-Master-Looter group member could previously spoof a
SESSION_INITaddon message and force-end another player's active loot council session by sending one with a different session ID. Session-init is now restricted to the current Master Looter, or — only when the receiver doesn't yet know who the Master Looter is — a raid leader/assistant as a one-time bootstrap. Once the receiver knows the Master Looter, the bootstrap door closes. - Malformed addon messages can no longer slip through to session state. The combined
SESSION_INITmessage used to bypass the per-payload schema and authorization checks that direct broadcasts ran, andRESPONSE_BATCHdidn't validate each item inside the batch. A buggy (or malicious) peer sending garbage inside either wrapper would propagate it straight into session state. All sub-payloads and each inner response are now validated individually; one bad item in a batch drops just that item instead of poisoning the rest. - Silent auto-pass against the wrong item, fixed. When Loothing's auto-pass had to wait for the game's item cache to warm up, the deferred retry could fire later against an item you'd already moved on from (or that had been removed from the session) and silently queue an auto-pass response for it. Switching to a new voting item now cancels every deferred retry, timer, and item-info hook from the prior item — including the
OnItemInfoLoadedcallback that used to linger until the cache warmed. - Heartbeat observer-sync spam, fixed. If the Master Looter had Loothing's observer system loaded with at least one observer and your client didn't have the observer module, your client was triggering an observer incremental sync once every 10 seconds that it couldn't actually apply. The observer hash compare now skips when the module isn't loaded locally.
- Sync cooldown no longer suppresses a real mismatch after a false alarm. When a pending sync was cancelled because state converged before it fired, the 60-second cooldown watermark stayed set — a genuine state divergence arriving within that window was silently ignored. The cooldown is now reset when the cancellation was due to convergence.
- Communication diagnostic stats (
/lt diag stats) are now accurate. Sends that Loolib's queue dropped under heavy pressure were being counted as successful. Those are now counted as dropped with a reason attached. Test-mode traffic intercepts also match — a dropped send is no longer also recorded as outgoing. - Guild-channel drops under pressure now fire the same drop notification as group drops. Subscribers to the
OnMessageDroppedevent saw group drops but not guild drops; parity restored. - PARTY-channel addon messages are now dropped client-side when you're in a raid instead of being handed to the game and bouncing back with
GeneralError(9). Matches the same error-frame-spam protection the addon already has for raid/instance/guild/whisper channels. - Message sequence counter (used for replay protection) wraps strictly above 2,147,483,647 — previously the exact max value was skipped. Nothing user-visible; a cosmetic tightening.
- Test-mode guard on history writes is now defence-in-depth. The check covered one of the two test-mode modules (
ns.TestMode); a remoteHISTORY_ENTRYarriving during test mode could theoretically slip through if the state tracker and harness were ever out of sync. Both sources are now consulted. - Council-table right-click menu is ordered, stable, and now shows custom responses. The "Award:" and "Change Response" submenus used
pairs(ResponseInfo)to enumerate responses, so the Need / Greed / Offspec / Transmog / Pass items appeared in a different order every time you opened the menu. An earlier fix in this cycle iterated the static priority list instead, which silently hid any response the guild had customized (added a sixth response, renamed an ID, etc.). The menus now iterate your active button set viaResponseManager:GetSortedResponses()— every configured response is present, in the order you set. The menu also no longer relies on Blizzard's undocumentednopglobal. - Orphan auto-roll-on-submit locale strings cleaned. The auto-roll-on-submit toggle was removed in 2.0.8, but translations for it still shipped in ruRU, ptBR, esES, deDE, and the Brainrot locale. Now gone.
- Cross-realm trade-in matching no longer fails on realm-name case drift. A player whose name arrived in chat as
Jimbo-illidan(chat-server lowercasing) was being treated as a different player fromJimbo-Illidan(API casing) during tradability and time-remaining updates, so items would miss their owner in cross-realm raids. Name normalization now canonicalizes the realm segment to lowercase, and the four directitem.looter ==comparisons in the session layer now route throughUtils.IsSamePlayer. This tightens every cross-realm player comparison in the addon — council members, voting tally keys, comm senders, history lookups — not just the trade path. - Manually added items no longer escape the class-blacklist when the ML does the adding. The bag-scanner already rejected consumables, reagents, tradeskill items, quest items, and housing decor before adding to a session; the
AddItementry point shared by automatic paths was missing that same gate, so anything else flowing in withoutforceset could still land in a voting session. The filter is now applied symmetrically. Explicit/lt add, sync restore, and reconnect restore still passforce=trueand retain their intentional override. - History no longer keeps orphaned entries when the ML revotes a completed item — everywhere. Calling revote on an already-awarded item returned the item to voting state but left the original award row in history; a subsequent re-award wrote a second row with no link back. Revoting now cleans up the superseded history entry so the final award is the only one recorded. This works both locally on the ML and on every council member: the history layer is receiver-side idempotent on the item's session GUID, so when the re-award's history entry arrives over the wire, every receiver evicts the stale row before inserting the new one. No extra broadcast is required. Pre-existing history entries are unaffected — the back-reference only applies to awards made after this version is installed.
- Majority-recommendation label now shows the ballot count as a fraction. The winner text reads
Recommended: Jimbo (3/5 votes)instead ofRecommended: Jimbo (3 votes)so it's clear that the majority denominator is "ballots submitted," not the total council size — non-voters are not counted. This matches what the voting engine has always done; we just now say it out loud. The voting-engine docstring and internal comments have been updated with the same clarification. - SESSION_INIT bootstrap tightened against assistant-impersonates-ML. The earlier session-hijack fix accepted
SESSION_INITfrom a raid leader or assistant when the receiver didn't yet know the Master Looter, and provisionally adopted that sender as ML pending the MLDB sub-payload's authoritative assignment. If the sub-payload was missing or malformed, the assistant stayed stuck as the receiver's ML until the next legitimate MLDB — long enough to forge council-roster changes, item adds, and votes as the supposed ML. Bootstrap now requires a structurally valid MLDB sub-payload up front; without it, SESSION_INIT is rejected and the receiver never promotes the sender. - Deferred auto-pass no longer fires against a torn-down frame. When an item's class/subclass info wasn't yet cached, the auto-pass check was scheduled behind an
OnItemInfoLoadedcallback + retry timer. If the frame was torn down (session ended, loot finished) between scheduling and firing, a nilself.itemswas silently treated as "item is still present" and auto-pass ran against stale state, queueing a phantom response. The nil case is now treated as "frame gone, bail out." The callback itself always unregisters on fire, so the item no longer leaks through the closure.
Removed (internal)
- Deleted three unused comm-layer helpers:
CommMixin:SendViaRelay(cross-realm relay wrapper — replaced by theXREALMenvelope path, never called),CommMixin:BroadcastVoteUpdate(replaced by the batchedQueueForBatch(VOTE_UPDATE, …)path), andSessionMixin:OnVoteTimeout(legacy per-session timeout — replaced by per-item timeouts in a prior refactor). All three were sitting as public-looking methods with zero callers. - Thirteen unused popup definitions and their orphan locale strings deleted across six locale files.
LOOTHING_CONFIRM_USAGE,LOOTHING_CONFIRM_ABORT,LOOTHING_CONFIRM_AWARD_LATER,LOOTHING_TRADE_ADD_ITEM,LOOTHING_KEEP_ITEM,LOOTHING_CONFIRM_CLEAR_COUNCIL,LOOTHING_CONFIRM_SKIP,LOOTHING_CONFIRM_REVOTE,LOOTHING_CONFIRM_CLEAR_IGNORED,LOOTHING_CONFIRM_RESET_REASONS,LOOTHING_CONFIRM_DELETE_SET,LOOTHING_CONFIRM_REANNOUNCE, andLOOTHING_CONFIRM_PROFILE_OVERWRITEwere registered but never invoked — either superseded byLOOTHING_CONFIRM_START_SESSIONduring the v2.0 UI overhaul, or declared for features that never shipped. Removing them trims the Popups surface and eliminates 132 dead translation lines.
Changed
TempTableleak tracking is now off by default for release builds. The per-Acquirestack capture the pool was doing was a measurable cost on hot paths (communication batching, event dispatch). The dev build (Loothing (Dev)TOC) re-enables leak tracking automatically, and the/lt diagpanel now shows "(tracking off)" in the Health section when the tracking is off, instead of reporting a misleading 0 leaks.
Internal / Library (Loolib 2.1.2)
- Callback registry hardened against mid-dispatch unregister. If a callback unregistered itself or any other callback while its event was still being dispatched, the
pairs()iteration over the live callback table hit undefined behavior. The registry now marks unregistered entries with a tombstone sentinel instead of deleting them mid-iteration, and sweeps them after dispatch drains. ThrottleWithTrailingandDebouncepreserve the exact arguments of the last call. Previously,ThrottleWithTrailingcalled its wrapped function with no arguments on the trailing edge, and both helpers truncated varargs with trailing nils. The trailing fire now forwards the full last-call argument list.CommMixin:Shutdownteardown hook added (test harnesses / debug reloads). A subsequent init rebuilds cleanly — the frame reference is nilled, queues/prefix tables/throttle state are reset, and thehooksecurefuncbudget tracker is installed exactly once per Lua state to prevent stacking.RESPONSE_BATCHitem rejections and Loolib queue-full drops now both emitOnMessageDroppedevents with a reason code, suitable for rate-limit / alert subscribers.Loolib.Locale:GetLocaleno longer returns a bare{}when a locale lookup has no result. Both the "no locales registered for this application" and "no default locale found" paths now return a shared fallback table whose__indexechoes the requested key back as a string. This preventsstring.format(L["KEY"], …)from crashing on an unreachable edge case, and makes missing keys visually obvious during debugging. The fallback also rejects writes, so callers can't accidentally mutate shared state viaL["FOO"] = ….SendCommMessageInstantcan no longer leave the bypass-tracking flag stuck ontrue. The instant-send path flipped an internal "this is our own send" flag before callingC_ChatInfo.SendAddonMessageand back off afterwards. If theC_ChatInfocall raised a Lua error (throttle lockdown, invalid state), the flag stuck, and thehooksecurefuncthat audits bypass-traffic then ignored every subsequent bypass send for the rest of the session — silently poisoning the throttle-budget measurement in/lt diagstats. The call is nowpcall-guarded; the flag always resets.
Desktop (Loothing Desktop 1.6.1)
Fixed
- The app now recovers when configuration loading fails instead of silently landing you on the setup screen. Previously, a transient backend error while loading your saved WoW path would throw you back to the "Configure WoW" screen with no indication that anything had gone wrong; you'd assume your setup was lost. The app now shows a clear "Couldn't load configuration" card with the underlying error and a Retry button, so it's obvious when something is recoverable.
- No more blank window when one page hits an unexpected error. Every major page (Dashboard, Roster, History, Wishlists, Changelog, Settings) is now wrapped in its own error boundary. If a page crashes because of unexpected data (e.g. a malformed wishlist payload from the server), you see a recovery card with "Try again" and "Back to dashboard" buttons — the rest of the app stays usable, and navigating tabs resets the crashed view.
- "Try again" on an error now actually re-runs the page. The error-recovery button used to leave stale React state in place, so clicking it often just re-showed the same error. It now forces the view to fully remount, giving the code a clean slate.
- Wishlists and roster no longer crash when the server sends an unexpected shape. If the wishlist API returned a
byItemIDentry as null or a string instead of the expected array, the Wishlists page and the Roster member-detail panel would throw and take the view down. Those sites now normalize defensively and keep rendering. - Roster tank/healer/DPS/flex counts now add up to total member count. Members whose role was missing or unknown were silently dropped from the role counts (making the sum lower than the member list), and they rendered with a broken role icon. Unknown roles now count as "Flex" and show the Flex icon.
- Retrying a stuck boot no longer clobbers the fresh retry with stale data from the previous attempt. Clicking "Retry" while the previous load was still in flight could let the older response overwrite the newer one mid-retry. Each boot now gets its own generation id; any in-flight request whose id is stale silently drops its result. Same protection added to Dashboard stats/tier-data loads, so rapid filter/guild switches no longer race.
- Retry buttons no longer fire duplicate requests when clicked rapidly. Retry, Skip, and warnings-banner retry buttons are now disabled while a boot is in progress.
- Themed guild branding now clears correctly when you leave a guild. Switching from a themed guild to "no guild selected" used to keep the old theme applied; the reset now fires in the right place in the effect cycle.
- Login errors are now human-readable. OAuth token exchange failures used to surface as raw
Error: invalid_grant/[object Object]strings. Common patterns — expired tokens, timeouts, network errors — are now translated to friendly sentences. Pasted tokens are validated locally for basic format before being sent. - Sync status error messages no longer leak internal file paths and other backend-only detail to the Sync panel. Error strings are routed through a formatter that strips paths and truncates long messages.
- Addon updates can no longer leave you with no installed addon if the install fails mid-extract. The updater used to delete the current
Loothing/folder before extracting the new one; a crash or disk error in between would leave you with no addon and a "manual reinstall" problem. The new version extracts into a staging directory and atomically swaps — the original is never touched until the new version is fully in place. If the swap itself fails, the old install is restored. - Addon zip extraction now rejects files that could compromise your system. Symlink entries (which would follow out of the addon directory), absolute paths, NUL bytes, leading-dot config files (
.DS_Store,.git), and parent-directory traversal components are all refused with clear error messages. - SavedVariables writes are now atomic. The desktop app used to overwrite
Loothing.luain place; if it crashed mid-write, you could end up with a truncated file — and because the app re-serializes every top-level global in that file, a bad write could corrupt other addons' saved data too. The write now goes to a.tmpfile first, isfsynced, then renamed into place. WoW never sees a partial file, other addons' data is safe. - NaN or infinity values in synced data no longer corrupt your SavedVariables. If a piece of synced data contained NaN or Infinity (e.g. from a divide-by-zero in a server stat), the old writer would emit
NaN/inf— neither of which is valid Lua — and WoW would fail to load your settings entirely. Those values are now emitted as(0/0),(1/0),(-1/0), which parse and round-trip correctly. - Addon auto-update version detection now tolerates common TOC formatting variations (
## Version:vs.##Version:, different cases, extra spaces) so it no longer silently decides "no update available" because the addon author's build pipeline happened to format the version line slightly differently. - Pre-release version strings no longer falsely report "you're ahead" and suppress real updates. Versions like
1.2.3-betaused to silently compare as(1, 2, 0)— older than1.2.2— so the next update check would see "nothing newer" even when a legitimate newer version existed. The version parser now returns "unknown" on any unparseable component, and the update check treats that as "don't auto-prompt, don't falsely claim currency." - File watcher thread no longer leaks on reconfigure. Previously, each time you changed your WoW path or account in Setup, the prior SavedVariables file watcher leaked a thread that never exited. The app now signals the old watcher to stop before spawning a new one.
- Authentication state no longer silently recovers from panics. The auth check previously tried to recover from a poisoned mutex (the internal signal that something panicked while holding the lock), which could return a stale "you're logged in" state after a crash. It now surfaces the condition so the UI can prompt for a restart instead of drifting.
Added
- Safer WoW path handling. The path and account fields you type into Setup are now validated end-to-end — absolute path required, no
..traversal, no path separators or NUL bytes in account names, Windows reserved device names (CON,PRN,AUX,NUL,COM1-COM9,LPT1-LPT9) are refused. The app also canonicalizes the WoW path so symlinks insideWTF/Account/<name>/can't escape the account directory. - SavedVariables parser now refuses oversized files (64MB cap) and deeply-nested tables (128-level cap). Real LoolibDB files are under 5MB with modest nesting; the caps prevent a corrupted or hand-crafted file from exhausting memory or crashing the app with a stack overflow during parse.
- When "Couldn't load your guild list" happens at boot, you now see a warning banner with a retry link instead of the app just quietly moving on without guild data.
- Multiple simultaneous warnings are now shown compactly in the banner rather than only showing the most recent one.
Changed
- HTTP client initialization failures now crash the app immediately with a clear log line instead of silently falling back to a client with no timeouts (which would cause every network call to hang forever). If the TLS stack is unusable you learn about it at startup.
- Event listener cleanup hardened across the app. The sync/auth/update hooks each register multiple Tauri event listeners; cleanup is now handled via
Promise.allSettledso one listener failing doesn't leak the others, and crashes during unlisten are logged rather than swallowed. - Directory-scan errors during WoW auto-detect (on
/mnt,/media,/run/media/$USER) now surface as debug log lines instead of being silently ignored, making installation-discovery issues diagnosable.
Internal
resolve_sv_pathis now the single canonical entry point for resolving SavedVariables paths — all SV read and write sites route through it, closing a bypass where one read path had reimplemented the path-joining without the validation checks.extract_existing_exchange_fieldno longer silently returns an empty object on read/parse failure. It now returnsResult, and every caller (20+ sync paths) explicitly handles the failure case — if we can't read existing data we skip the SV write entirely rather than wiping the user's synced state with empty objects.- Addon downloads now share a unified HTTP client helper (
http_client_with_timeout) instead of building bespokereqwest::Clientinstances inline. - Lua writer hardening. Unicode line separators (U+2028, U+2029) and C0/C1 control characters are now escaped in the serialized output so the file stays parseable by both the desktop app and WoW's Lua runtime regardless of what Unicode crept into server-supplied strings.
- Parser error paths are no longer silent. Trailing garbage after the last valid assignment now raises an explicit "failed to parse assignment near..." error instead of quietly returning partial data.
- Mutex-poisoning recovery across the app is now consistent — poisoned auth state surfaces as an error with a log line instead of being silently recovered from, so panic-induced state drift is visible in support diagnostics.

