|
Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
|
How to find and fix systemic problems — not individual errors, but categories of failure that affect many files at once or require investigative tooling to locate.
Editor crashes can silently truncate source files mid-line. Symptoms: QML components show blank data, C++ fails to compile or link with incomplete symbols, functions seem to vanish.
Detection script (run from repo root):
Notes:
Once a truncated file is identified, the repair approach depends on how much is missing.
Short missing tail — identify exact truncated suffix and append:
Important: Match the line endings already in the file. Check with:
Longer missing section — reconstruct from:
Example: mainwindow.cpp was truncated before injectIntoQML() and ssConnect(). The .h file had their declarations; the reconstructed bodies were:
When files are corrupted/truncated and git is no help (e.g. HEAD was committed after the damage, or the work was never committed), the Cowork chat transcripts are a near-complete backup. Every tool call from every prior session is logged as JSONL.
Where: APPDATA%\Claude\local-agent-mode-sessions\<...>\<...>\local_<id>\.claude\projects\<enc>\*.jsonl (also audit.jsonl). Copy them somewhere readable (PowerShell, reliable) before parsing. Each line is a JSON event; message.content[] holds tool_use (with input.file_path + input.content for Write, input.old_string/new_string for Edit, input.command for bash) and tool_result (the Read output as "<lineno>\t<text>" lines — strip the ^\d+\t prefix to rebuild). Each event has a timestamp for ordering. Exclude the corrupting session's own ops when replaying.
Recovery techniques, in descending fidelity:
Always validate the result against the most recent reads (line-for-line), confirm brace balance, and that every base function survived. Always write with PowerShell ([System.IO.File]::WriteAllText, UTF8-no-BOM) and re-read to verify — never trust a bash-mount write here (it caused the corruption).
App compiles clean but hangs 40+ seconds on startup, window never appears.
Step 1 — Add timing instrumentation
In boot/boot.cpp, wrap each boot phase with QElapsedTimer:
The phase that shows a very large elapsed time (10,000+ ms) is the hang.
Step 2 — Per-DB timing (if hang is in DB::loadAll())
In db/db.cpp:
The DB that hangs will show an enormous elapsed time. The one just before it in the list is the last one to succeed.
Known hang causes (all fixed — document here as reference):
| Symptom | Root cause | Fix |
|---|---|---|
| Hang in MainWindow constructor | qt_add_qml_module() conflicts with app.qrc | Remove qt_add_qml_module() from CMakeLists |
| Hang in QQuickWidget::setSource() | Same as above | Same fix |
| Hang during QSurfaceFormat setup | setSamples(N) hangs Windows FBO context creation | Remove QSurfaceFormat setup entirely |
| Hang in one specific DB's inst() | Static-init mutex deadlock (Qt 6) | Remove load() from DB constructors — see decisions/architecture.md |
Symptom: A deep property chain like brg.file.data.dataExpanded.player.basics.money returns undefined in QML, causing TypeError: Cannot read property 'X' of undefined on every screen that uses the chain.
The actual root cause (confirmed session 13): Q_DECLARE_OPAQUE_POINTER (or a bare forward-declaration) on a QObject type in the chain. It forces IsPointerToTypeDerivedFromQObject<T*> = false, so Qt stores the pointer as opaque and QML can't read its sub-properties. qRegisterMetaType and qmlRegisterUncreatableType do NOT override it. Fix: #include the full header at the declaring site, remove the opaque decl. See reference/qt-patterns.md and decisions/architecture.md.
The fastest way to confirm it (the "natural experiment"): find a property at the same depth that works and one that fails, and compare how their types are declared.
Pinpoint which hop breaks from the error text: Cannot read property 'player' of undefined for a.b.c.dataExpanded.player means …dataExpanded evaluated to undefined (so data is the opaque hop). The break is at the FIRST opaque/forward-declared hop; fix each hop QML walks.
Diagnosis order (revised):
Truncation / qRegisterMetaType are NOT the cause (sessions 10–12 thought so; disproven in 13 — the binary was current and registered, chain still undefined). They were still worth fixing.
The signal parameter is not the cause. SaveFile::dataExpandedChanged(SaveFileExpanded*) with a parameter is correct. Do not remove the parameter. See decisions/rejected.md.
Symptoms: app runs fine, then after some interaction a feature breaks (rendering goes blank, a dropdown empties, saving stops) OR it crashes with 0xC0000005 read access violation at an address like 0xffff…ffff; an app restart fixes it. In the project debugger the crash frame is in C++ code dereferencing a pointer obtained from a QObject the QML side touched.
Root cause: QML garbage-collected a parentless C++ QObject that C++ still holds. QML ownership rules:
How to confirm: get a project-debugger stack trace (Qt Creator's own debugger, not a random system one). It will point at the exact deref (e.g. pokemonstoragemodel.cpp:146 mon->isBoxMon()). The freed object is whatever a Q_INVOKABLE recently handed to QML.
Fixes (this project's standing solutions):
Full rule + reasoning: reference/qt-patterns.md → "QML garbage-collects parentless C++ QObjects" and "Q_PROPERTY returns are safe; Q_INVOKABLE returns are NOT". Note: system-wide Qt-debugger pop-ups (also in other apps) are an environment issue, not this — see status.md.
If an entire screen shows nothing and the QML error log is quiet, suspect truncated QML files.
Quick check: In debug builds, QQuickWidget can show QML errors in a dialog. If it's connected (see mainwindow.cpp statusChanged handler), QML parse errors will appear.
If the dialog doesn't show and the screen is blank:
Note: In debug mode, QML files load from the filesystem at runtime (not embedded QRC). QML changes take effect immediately. C++ changes always require a rebuild.
This almost always means one of:
For (1): add the implementation body. For (2): add DB_AUTOPORT to the class declaration + #include "./db_autoport.h" to the header. The DB_AUTOPORT macro must appear immediately before the class name: