Skip to content

8.31 release#2395

Merged
3vcloud merged 159 commits into
masterfrom
dev
Jul 11, 2026
Merged

8.31 release#2395
3vcloud merged 159 commits into
masterfrom
dev

Conversation

@henderkes

Copy link
Copy Markdown
Member

No description provided.

3vcloud and others added 30 commits July 2, 2026 05:33
Github returns "body": null for a release published with no description
text, which made Glaze fail the whole releases-array parse and silently
broke the update check (the 8.30 release triggered this). Make the JSON
body field std::optional in both the exe installer and the in-game dll
updater, and have the release workflow extract the version's notes from
history.md into the release body (with a non-empty fallback) so the API
never returns a null body again.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The download-button version scanned releases?per_page=30 and filtered out
drafts, but the build token has push access so GitHub returns the repo's
154 draft releases first, crowding every published release out of the page.
The filtered list came back empty and the site silently served a stale
fallback version. Use /releases/latest instead, which excludes drafts and
prereleases by definition and tracks future releases without a config bump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drive the shown condition through one eased 0..1 intensity: hold the current
condition until it fades out, then swap and fade the new one back in, so a
weather change reads as a brief lull rather than a hard pop. The same
intensity rides the particle draw alpha, the ambient dimming, and newly
spawned splash tints. Reset (loading/world map) starts the new condition
from clear.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Skill icons and other dat images could stay blank for a session:
- AcquireMapping matched only a handle literally named "Gw.dat" via a
  MAX_PATH buffer, so renamed/-dat-relocated archives and deep (>260 char)
  install paths were dropped. Match any ".dat" handle (MapDat's 3ANa magic
  still confirms it) using a length-sized path buffer, and record the bound
  path for diagnostics.
- A decode that ran before the client's Gw.dat was mapped cached a null
  texture forever. Track pending/attempts and re-decode until a
  mapped-archive attempt settles the result.
- Surface a one-time chat warning when the handle scan itself is blocked
  (usually AV/anti-cheat), plus log lines to make reports diagnosable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… update

The loader's Windows Defender exception prompt reappears on every launch
until the exclusion is actually added, which nags users who can't or won't
elevate. Swap the Yes/No message box for a TaskDialog carrying a "Don't ask
again until GWToolbox is updated" checkbox; when ticked, record the current
build in HKCU\\Software\\GWToolbox and skip the prompt while that version
runs, re-prompting automatically after an update.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…checkbox

feat(defender): mute Windows Security prompt until next update
…loop

The native reader parsed the on-disk MFT once, so files the client streamed
in mid-session (a partial/streaming Gw.dat) stayed invisible and their icons
blank. Make the reader pick them up, and move the work off the render thread.

- GwDatArchive: guard the index with a shared_mutex so ReadFile is callable
  from any thread; on a miss, re-map the (grown) dat and re-parse the MFT,
  throttled and non-destructively (swap only on success). Factored the parse
  into AcquireMappingInto + ParseFrom; ReadRaw does the shared-locked lookup.
- GwDatTextureModule: run the dat read + re-parse + ATEX/dye/greyscale decode
  on a Resources worker thread, uploading only via MakeTextureFromArgb on the
  DX thread. Retry a mapped-but-missing file over a ~60s window so a streamed
  file is caught. SaveTextureFromFileIdToFile (pure CPU) also moves to a worker.

Needs an in-game validation pass: rests on the client flushing streamed files
to the on-disk MFT within the retry window (not empirically measured).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retrying missing-file decodes (up to the ~60s window) plus the occasional
re-parse could crowd the shared 20-worker pool that also serves curl and
wiki-image downloads. Give the dat module its own single worker thread that
serialises reads/decodes (and the archive re-parse); the GPU upload still
hops to the DX thread. Started in Initialize, stopped+joined in Terminate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep dat-file concerns (mapping, MFT parse/re-parse, the dedicated worker
thread) in GwDatArchive and leave only texture concerns (decode to ARGB,
GPU upload, caching, retry window) in GwDatTextureModule. The archive now
owns StartWorker/StopWorker/EnqueueTask; the texture module submits its
decode task to it and drives its lifecycle from Initialize/Terminate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Conform the dat reader/texture module to the single-line comment guideline
and trim repetition: factor the three loaders' find-or-create into a
GetOrCreate helper, and use memcmp(kDatMagic) for the archive magic check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The world-map path/line overlay inlined its own world->screen transform and
dropped the world_map_scale factor, so it only aligned when fully zoomed in.
Route the line and navmesh blocks through the shared CalculateViewportPos
helper instead, and make that helper animation-correct: derive a per-frame
pixels-per-world-coord scale from the live visible rect (top_left..bottom_right,
which the client re-derives every frame from the animating zoom) and the
world-map frame's on-screen size, falling back to the rest-state factor until
the client populates the rect.

With projection now correct at every zoom level, drop the five "Map is
animating" early-returns so quest/boss/area/portal overlays track the zoom
animation too, and scale the boss capture icon smoothly (std::lerp 16->32)
instead of snapping between two sizes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cross-map route was rendered only on the world map: QuestModule tags its
world-coord tail segments draw_on_mission_map/minimap=false, and both the
mission map and compass render their lines in game coords, which can't place
other maps' positions.

- Mission map: draw the world-coord route tails in an ImGui overlay via the
  existing WorldMapCoordsToMissionMapScreenPos, clipped to the widget, so the
  whole continent route shows (incl. the stretch past the nearest portal that
  the game-coord lines don't reach). Honors the existing mission-map toggle.
- Compass: project world-coord tails back into current-map game space with
  WorldMapWidget::WorldMapToGamePos and feed the existing game-coord vertex
  path, so the route continues off toward the next map. Honors the minimap
  toggle.

Also fix the mission-map pmap drop-shadow: the -100 VIEW-space offset lands in
raw pixels under the mission map's game->px view (vs a small offset in the
compass's zoomed space), so it was huge and didn't scale with zoom. Make the
offset a MinimapRenderContext field (compass keeps the -100 default) and set it
on the mission map from a small game-unit value scaled into pixels, so it stays
subtle and tracks the mission-map zoom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a dedicated Party Statistics page covering the skill-usage tracking,
the display/print options, and that Ctrl+LeftClick (and /skillstats)
output goes to team chat as text — nothing is saved to disk. Register
it in the sidebar nav and point the /skillstats command link at it
instead of the unrelated Party Window page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the agent instruction file to AGENTS.md so non-Claude agent models
pick it up too; CLAUDE.md becomes a one-line pointer (@AGENTS.md import)
so Claude Code still loads it. Tighten the existing sections to cut
context tokens, and add an 'Answering feature questions' section:
check whether a feature is documented (site/src/content/docs or the live
https://www.gwtoolbox.com/docs/) before/while explaining it, and link
the relevant page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3vcloud and others added 26 commits July 7, 2026 16:32
beam_width -> kBeamWidth = 35, z_lift -> kZLift = 5. Drops both
sliders; render_max_distance and fog_factor remain configurable.
Replace magic 0x4/0x1400/0x83 literals with a SoundFlags enum. Values
are cross-checked against the game's native sound dispatcher: several
(Looping, NoHandle, Streaming, Positional) are confirmed straight from
the binary rather than just inferred from toolbox call sites.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…maps

Route builds re-evaluate their edge graph from scratch on every recompute,
so a map that's permanently unreadable this session (missing pathfinding
data on a partial/streaming dat install, or never visited/StoC-resolved)
re-triggered the same DAT read attempt and re-logged the same error on
every single route query. Cache both failure modes per map/file_id so
each is only logged once.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per AGENTS.md: comments explain why, one concise line, no prose blocks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per AGENTS.md: comments explain why, one concise line, no prose blocks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Minimap: unify agent colors/sizes into the Custom Agents list
docs: add missing /noexecheck launch option
Get-MpPreference's ExclusionPath entries carry an inconsistent trailing
separator versus our canonicalized folder path, so the prefix-match in
ListCoversPath never matched and Toolbox kept nagging to add an
exclusion that was already there. Also stop skipping verification for
non-admin runs (Get-MpPreference doesn't require admin), so a real
failure to add the exclusion is no longer silently reported as success.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…g-slash

Fix repeated Defender exclusion prompt on already-excluded folders
The Launch button's height was hardcoded to match the character combo box's
requested height, but Windows fixes a combo box's closed-state height from
its font metrics regardless of what's passed to CreateWindowW, so they only
lined up at some font sizes/DPI settings. The button now reads back the
combo box's actual rendered rect and matches it exactly.

Also opts the launcher into Per-Monitor-V2 DPI awareness (with a fallback
for older Windows) and scales every hardcoded child-control layout constant
by the real monitor DPI, so scaled displays get accurate layout instead of
blurry bitmap-stretching.
Previously every failure (no GW process, wrong game, RVA/pattern error,
antivirus-blocked memory, no valid character) popped its own MessageBoxW
with bespoke Yes/No/Retry logic scattered across main.cpp. Now
InjectWindow::AskInjectProcess always shows the single launcher window:
on success it's the existing character selector, on failure it's a pink
error box in the same slot with a Retry button that re-scans in place.

The antivirus-blocked case gets a troubleshooting-guide link button, and
the admin-restart button is reused across states via the same bottom-row
slot. main.cpp's ~70-line reply-branch chain collapses to a single Cancel
check. As a side effect, /quiet mode now actually suppresses all UI on
error instead of still popping message boxes.
…k apply

Replaces the blocking pre-launch MessageBox update prompts (one for the exe,
one for the dll, each with their own Yes/No/Retry popups) with a status row
in the launcher window, to the left of Settings. It shows "Checking..." while
a background Github releases fetch runs (non-blocking - character selection
or error display above it is unaffected), then "Updates available!" with an
Update button if either the exe or dll is stale.

Clicking Update reuses the existing DownloadWindow progress-bar dialog to
install whichever of the two need it in one pass, with no extra popups. The
exe replacement goes through the same rename-swap dance as before (Windows
won't let a running exe be overwritten) but no longer force-restarts the
launcher - the new exe just takes effect next launch, while the dll is live
immediately for the injection about to happen.

Also fixes a latent bug: AsyncRestClient asserts it isn't mid-request when
destroyed, so UpdateChecker now aborts a still-pending check if the user
launches before it finishes.
…for non-installed exe

Opening the diagnostic log file at the top of WinMain used to show a blocking
MessageBoxW and abort the entire launch (return 0) on failure. Since this
happens before any install/Defender work, a fresh exe sitting in a
CFA-protected folder (Documents, Desktop, ...) could never even get past this
point on first run. Now it's non-fatal: falls back to %TEMP%, logging a
PathDiagnoseWritability diagnosis of the original failure, and if even that
fails, just continues without file logging - it was never load-bearing for
functionality.

Also closes a gap in the pre-injection Controlled Folder Access check: it
only ever CFA-allowed the *installed* copy of GWToolbox.exe, not whichever
copy is actually running. A freshly-downloaded, not-yet-installed exe (e.g.
run with /noinstall) never got itself added to the allow-list. Now both
paths are added, mirroring what Install() already does.
UpdateChecker::ApplyUpdates() went straight to downloading and writing the
exe/dll with no readiness check first, unlike Install() which explicitly
grants exceptions before it writes. The pre-injection check in main.cpp
can't cover this either, since clicking Update happens entirely inside
InjectWindow, before that check ever runs.

The write here is also done by the launcher exe itself (replacing its own
.exe and/or the dll), not by Gw.exe, so the CFA-allowed-apps entry needed is
for GWToolbox.exe. Call EnsureDefenderReadiness() first, CFA-allowing
whichever exe path(s) are about to be replaced (or the running exe for a
dll-only update), so a real permissions gap now surfaces as the existing
"Add Windows Security exceptions?" prompt instead of a dead-end write error.
Dying in first-person then resurrecting could leave the camera's pitch_to_go
NaN (the death-orbit -> follow-cam transition clamps pitch against the wrong
limits and pushes it out of its valid [-1,1] range). The NaN eases into pitch
and then the eye position, and the game's terrain line-of-sight ray-march
(Gw.exe @0x0070eb40) only exits on an ordered float compare, so a NaN
coordinate makes it loop forever and the render thread hangs until you die
again.

Hook the per-frame camera-eye read (Gw.exe @0x004f7b80, asserts "*fov != 0.0f")
and clamp pitch/pitch_to_go back into [-1,1] (NaN -> 0), sanitizing a NaN eye
position, before the renderer and ray-march consume the camera.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an Enabled checkbox per weather condition, shown as [Disabled] in
the collapsed header when off. Disabled conditions are excluded from
manual toggling, automatic weather rolls, and the /weather chat
command. Fog now defaults to disabled since it's quite invasive to
gameplay, which lets the Weather module itself default to enabled.
@henderkes
henderkes requested a review from 3vcloud July 11, 2026 07:31
@3vcloud
3vcloud merged commit b8c0f77 into master Jul 11, 2026
4 checks passed
@henderkes

Copy link
Copy Markdown
Member Author

@claude please update history.md with all the changes in this PR that aren't listed under 8.31 release section yet.

@claude

claude Bot commented Jul 11, 2026

Copy link
Copy Markdown

Claude finished @henderkes's task in 8m 12s —— View jobclaude/pr-2395-20260711-0740Create PR ➔


Updating history.md for 8.31 release

  • Read AGENTS.md for repo guidelines
  • Read current history.md to see what's already listed under 8.31
  • Analyze PR changes from changed files list
  • Update history.md with missing changes
  • Commit and push changes

Added 13 new entries to the ## Version 8.31 section in site/src/content/docs/history.md:

New features added:

  • Mission Map: minimap overlays (agents, range rings, effects, etc.) on the in-game mission map frame
  • Mission Map: walkable terrain overlay
  • Quest paths on 3D terrain and mission map
  • Armory: right-click context menu (copy chat command, open wiki)
  • Minimap: "Show props on minimap" for map objects
  • Game Settings: disable camera smoothing (mouse + controller)
  • Game Settings: block item sparkle effect
  • Game Settings: block vanquish complete + dungeon chest popups
  • Audio Settings: sound blocking + sound log
  • Launcher: inline update check in character-select window

Fixes/minor added:

  • Fix: Camera unlock NaN pitch hang (dying in first-person → resurrect freeze)
  • Minor: Windows Defender prompt "don't ask again for this version"
  • Minor: Launcher per-monitor DPI-aware layout

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants