Skip to content

feat: add Quest Journal Overhaul compatibility#32

Merged
codepuncher merged 13 commits into
mainfrom
feat/qjo-compat
May 24, 2026
Merged

feat: add Quest Journal Overhaul compatibility#32
codepuncher merged 13 commits into
mainfrom
feat/qjo-compat

Conversation

@codepuncher
Copy link
Copy Markdown
Owner

@codepuncher codepuncher commented May 23, 2026

Summary

Adds compatibility with Quest Journal Overhaul.

The problem: QJO hooks JournalMenu::ProcessMessage and resets the tab index to System on every kShow, so the Journal always opens on the System tab regardless of HoldFast's configured action — and regardless of which tab the player was last on.

The fix has two parts:

1. Long-press tab dispatch (_pendingTab path)

When HoldFast triggers a Journal long-press, MenuOpenCloseEvent(opening=true) calls into the live Scaleform movie directly, after QJO's hook has already run:

  • Stats/System: RestoreSavedSettings(tabIdx, false) — sets the active tab, updates the tab bar highlight, and populates page data
  • Quests: QJO_EndPage() — closes the Journal and opens QuestMenu (QJO's native Quests navigation path); falls back to vanilla SwitchPageToFront without QJO

2. General tab restore (_lastKnownTab path)

To also fix QJO's forced System override on normal (non-HoldFast) Journal opens, HoldFast tracks the player's last active tab:

  • SnapshotJournalTab — called on every input tick while the Journal is open (during GameIsPaused); reads iCurrentTab from the live SWF and caches it as _lastKnownTab
  • InvokeRestoreTabIfNeeded — called on opening=true when no _pendingTab is set; if QJO has reset the tab away from _lastKnownTab, calls RestoreSavedSettings to restore it

QJO detection

DetectQJOIfNeeded lazily probes for QJO_EndPage in the Quests page SWF and caches the result in _qjoInstalled. Both restore paths are skipped entirely if QJO is not installed, leaving vanilla behaviour unchanged.

Backwards compatibility

Without QJO installed, _qjoInstalled caches false on the first Journal open and all QJO paths are bypassed — behaviour is identical to pre-patch.

Copilot AI review requested due to automatic review settings May 23, 2026 12:37
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Quest Journal Overhaul (QJO) compatibility for HoldFast’s “open Journal on specific tab” long-press actions by switching the Journal page via Scaleform after the menu is shown, bypassing QJO’s sJournalTabIdx reset.

Changes:

  • Track a pending target Journal tab (_pendingTab) to apply on MenuOpenCloseEvent(opening=true).
  • Invoke Scaleform SwitchPageToFront(tabIndex, animate=false) on the live Journal uiMovie when the Journal opens.
  • Clear pending state on restore/close paths to avoid stale tab switches.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/InputHandler.h Adds _pendingTab state and declares InvokeScaleformTab for Scaleform-based tab switching.
src/InputHandler.cpp Implements the opening-event Scaleform invocation and wires pending-tab lifecycle into Journal open/restore logic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/InputHandler.cpp Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/InputHandler.cpp:330

  • OpenJournalOnTab sets _pendingTab before checking sJournalTabIdx, but then clears _pendingTab and returns when the relocation is unavailable. That means the Scaleform path won’t run either, so the comment about working without sJournalTabIdx doesn’t match behavior. Either keep _pendingTab and add a safe mechanism to clear it if the Journal never opens (separate from _tabRestorePending), or update the comment/logic so the intended fallback is accurate.
	// Set pendingTab before the sJournalTabIdx guard so the Scaleform path (QJO compat)
	// works even if the sJournalTabIdx relocation is unavailable.
	_pendingTab = tab;

	if (!sJournalTabIdx.get()) {
		logger::warn("{} long press: sJournalTabIdx unavailable — Journal will open on default tab", buttonName);
		_pendingTab.reset();  // Dispatch may succeed but opening=true may never fire; avoid stale fire.
		return;

Comment thread src/InputHandler.cpp
Comment thread src/InputHandler.cpp Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread src/InputHandler.cpp Outdated
Comment thread src/InputHandler.cpp
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread src/InputHandler.cpp Outdated
Comment thread src/InputHandler.cpp Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread src/InputHandler.cpp
@codepuncher codepuncher requested a review from Copilot May 24, 2026 21:09
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread src/InputHandler.cpp Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/InputHandler.cpp:475

  • current.GetNumber() is cast directly to std::uint32_t. If the Scaleform variable ever becomes NaN/Inf or otherwise out of integer range, this conversion is undefined behavior. Validate the double (finite + expected bounds) before casting to an integer.
	const bool   got = journal->uiMovie->GetVariable(&current, "_root.QuestJournalFader.Menu_mc.iCurrentTab");
	if (!got || current.GetType() != RE::GFxValue::ValueType::kNumber) {
		logger::warn("QJO tab restore: could not read iCurrentTab");
		return;
	}
	const auto currentIdx = static_cast<std::uint32_t>(current.GetNumber());
	if (currentIdx == tabIdx) {
		return;

Comment thread src/InputHandler.cpp Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread src/InputHandler.cpp
Comment thread src/InputHandler.cpp
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment thread src/InputHandler.cpp
Comment thread src/InputHandler.cpp Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@codepuncher codepuncher merged commit b4eeb1d into main May 24, 2026
15 checks passed
@codepuncher codepuncher deleted the feat/qjo-compat branch May 24, 2026 22:03
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.

2 participants