Commit graph

12 commits

Author SHA1 Message Date
Vadim Peretokin
c45f151385
fix: Discord getters no longer treat presence text as a format string (#9660)
#### Brief overview of PR changes/additions

- The six Discord getters (`getDiscordDetail`, `getDiscordState`,
`getDiscordLargeIcon`/`Text`, `getDiscordSmallIcon`/`Text`) passed the
stored presence text to `lua_pushfstring()` as its *format* string, so
every `%` in it was read as a printf specifier consuming an argument
that was never passed: `"Level %d Mage"` came back as `"Level 3202416
Mage"`, `"%%"` was silently halved, and a text ending in `%` read past
the end of the buffer and returned the bytes that followed it as part of
the Lua string.
- All six now push the text as data. Output is byte-identical to today's
for any text without a `%`.
- Five regression specs added to `Discord_spec.lua`, covering all six
getters and the `%d`, `%s`, `%%` and trailing-`%` shapes; the
`pending()` entry that recorded this defect is unpended.

#### Motivation for adding to Mudlet

Presence text arrives from the game server over GMCP
(`Host::processDiscordGMCP`), so a game whose status line contains a
stray `%` can garble - and, with a trailing `%` or a `%s`, crash or leak
adjacent heap bytes into a Lua string - for any user whose script reads
the presence back. There is no write primitive (Lua 5.1's
`lua_pushfstring` has no `%n`), so the ceiling is a crash plus memory
disclosure, not code execution.

Stacks on #9631, whose Discord IPC fixture is what makes these specs
possible; it retargets to `development` once that merges.

**Test case:** all five new specs fail against the unfixed binary
(`Level 3202416 Mage`, `100% health` for `100%% health`, and `mana at
50%` returned with trailing heap garbage) and pass with it; Lua suite
1853 successes / 0 failures / 0 errors / 40 pending twice with the
fixture and 1829 / 0 / 0 / 64 without it, ctest 69/70 (only the
known-environmental `TKeySequenceEditTest` Xvfb flake).

Assisted-by: Claude:claude-opus-5
2026-08-05 06:49:43 +02:00
Vadim Peretokin
8d1e061e35
Fix: strand no heap objects across the remaining lua_error() raises (#9605)
#### Brief overview of PR changes/additions
- Makes #9602's by-example sweep exhaustive. A static pass over the
**674** functions taking a `lua_State*` - the only frames a
`lua_error()` longjmp can unwind - found **377 sites in 160 functions**
holding a QString, QStringList, QByteArray, std::string or TMediaData
across a raise. **334 are converted, clearing 133 functions.** The **43
sites in 27 functions** left are each verified non-owning: `static`
storage, `qsl()`/QStringLiteral, default-constructed or `""` (Qt's
shared empty buffer), `isEmpty()` being the raise condition itself, a
local declared in the branch that does not raise, and one
`QFileInfo::exists` vs `TLuaInterpreter::exists` name collision. Same
scanner both sides, nothing newly flagged.
- Mechanism is #9602's: the `checkStringArg()` family, extended with
`checkNumberArg`, `checkStringOrIntegerArg`,
`checkCommandOrFunctionArg`, `checkCommandsOrFunctionsTable` and
`checkHintsTable`.
`getVerifiedString`/`Int`/`Bool`/`Float`/`Double`/`StringOrInteger`,
`parseCommandOrFunction` and the two table parsers are reimplemented or
paired on top, so message text and the order errors are reported in
cannot drift.
- Raisers are not only Mudlet's own helpers - lauxlib's `luaL_check*`
and `luaL_opt*` raise too, and `spawn()` was the worst case.
`TForkedProcess`'s constructor raised three times while holding the
program name and the argument list it was still filling, and because a
longjmp out of a constructor skips the rest of it, the `QProcess` that
`startProcess()` had just `new`'d leaked whole. Checking and failure
reporting move to `startProcess()`; the constructor no longer takes a
`lua_State` and cannot raise. `waitForEvent()` swaps `luaL_checkstack`
for the non-raising `lua_checkstack`.
- Two sub-classes a site scan structurally cannot see were caught
separately. Temporaries passed *into* a raising call rather than named
locals: `setLabelCallback()` and `movieFunc()` take a `const char*` now,
as a QByteArray built from their QString name was alive inside every
raising check. And loop-carried accumulation, where a container looks
non-owning at its declaration and only fills up once the loop runs -
`setMergeTables("Char", {})` and `TForkedProcess`'s argument loop both
stranded lists that way. A second detector (container declared outside a
loop, mutated inside it, raise in the same body) reproduces both on the
parent commit and reports nothing here.

#### Motivation for adding to Mudlet
#9602 removed the LSan suppression hiding this class but only fixed what
the suite happened to reach, so the first spec touching any of the rest
would turn CI red. Three things are worth recording for anyone repeating
the audit: `lua_error` only unwinds frames between the raise and the
enclosing `lua_pcall`, so closing the call graph over every function
name gives 1834 "raisers" and thousands of bogus hits - the `lua_State*`
universe is the right scope; `__func__` inside a lambda expands to
`"operator()"`, so wrapping a function body in one silently renames its
error messages; and a constructor that raises strands whatever `new`'d
it.

**Test case:** `Spawn_spec.lua` drives every `spawn()` error path, which
strand 4260 bytes in 20 allocations on the parent commit and nothing
here; `expandAlias`, `findItems` and `setModulePriority` likewise, with
byte-identical error messages either side. Busted 2x 1316 successes / 0
failures / 0 errors / 0 pending, ctest 60/60.

Stacked on #9602 - that one goes in first.

Assisted-by: Claude:claude-opus-5
2026-08-03 11:39:32 +02:00
Vadim Peretokin
775e223405
infrastructure: clean vestigial widget includes from the Lua engine files (#9526)
#### Brief overview of PR changes/additions

Step 1 of the libmudlet Wave 3 de-widgeting work: a cleanup +
correctness pass over the `TLuaInterpreter*` engine files.

- Delete the dead `#include <edbee/texteditorwidget.h>` from
`TLuaInterpreter.h` (no `edbee`/`TextEditorWidget` use anywhere in the
TLuaInterpreter family).
- Drop the copy-pasted, unused `QFileDialog`/`QTableWidget`/`QToolTip`
include block from the seven `TLuaInterpreter*` files carrying it,
keeping only the includes each file actually uses.
`TLuaInterpreterMudletObjects.cpp` keeps `QFileDialog` (used by
`invokeFileDialog`); `TLuaInterpreter.cpp` gains an explicit
`<QApplication>` for its remaining `QApplication::alert`, which the
removed headers had been supplying transitively.
- Retarget two Widgets-free statics for audit accuracy:
`QApplication::clipboard()` -> `QGuiApplication::clipboard()` and
`QApplication::applicationPid()` -> `QCoreApplication::applicationPid()`
(both are inherited statics, so behaviour is identical).
- Fix a null-path bug: `Host::echoWindow` and `Host::pasteWindow`
returned `-1` (which promotes to `true`) when the console is null, a
silent success-lie on a torn-down profile. They now return `false`,
matching the existing missing-window path, so the checked `echo` caller
yields the honest `nil + message` instead of a false success.

#### Motivation for adding to Mudlet

Part of the incremental libmudlet refactor (#8681, #9011) that drives
raw Qt Widgets coupling out of `mudlet_core`. This removes 5 files from
the `cmake/audit-core-widgets.sh` Widgets-dependent set (156 -> 151)
with no behaviour change on the GUI path, and folds in an independent
correctness fix for the `-1`->`true` null-path pattern.

#### Other info (issues closed, discussion etc)

Relates to #8681 and #9011. Closes no issues.

**Test case:**

1. Build (this was verified with Qt 6.12.0, Ninja, ASan) and run the C++
suite:
`ctest --output-on-failure` -> 48/49 pass. The only failure is
`TKeySequenceEditTest`, a known pre-existing headless-focus failure
(`qWaitForWindowActive` under Xvfb), unrelated to these changes.
2. `bash cmake/audit-core-widgets.sh --summary` reports 151 files depend
on Qt Widgets (was 156); the five now-clean files are
`TLuaInterpreterUI/Mapper/Networking/Discord/TextToSpeech.cpp`.
3. Lua sanity for the retargeted statics: `getClipboardText()`,
`setClipboardText("hi")`, and `getProcessID()` behave exactly as before.
4. Null-path fix: `echo` targeting a window that does not exist returns
the honest `nil + "console/label '...' does not exist"` rather than
reporting success. (The null-console branch it fixes is reachable when a
profile's main console has been torn down.)

Assisted-by: Claude:claude-opus-4-8
2026-07-28 16:26:02 +02:00
Vadim Peretokin
dc2e639132
Fix: apply Discord API permission gating consistently (#9472)
#### Brief overview of PR changes/additions

Since the original 2018 Discord commit, 14 of the 22 Lua Discord
functions carried inverted permission gating from copy-paste. Four
getters required WRITE access and failed with a self-contradictory "API
is read-only" error, nine setters only checked READ so scripts could
mutate presence in read-only mode, and `resetDiscordData` had no gate at
all. Getters now check read access; setters and `resetDiscordData` check
write access. The permission *meanings* are unchanged and the GMCP
server path is unaffected.

#### Motivation for adding to Mudlet

Gating is now consistent: read-only scripts can no longer mutate Discord
presence, and legitimate getters no longer fail with a contradictory
error.

#### Other info (issues closed, discussion etc)

Tests: a source-scan contract test in `DiscordTest` enforcing the full
22-function gating matrix (it fails against the pre-fix source), plus 5
Lua-level functional cases in `TDiscordModeTest` with the bundled
discord-rpc actually loaded.

Tested by hand. Squash-merge with:
```
Assisted-by: Claude:claude-fable-5
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
```


https://github.com/user-attachments/assets/99850189-f483-43e5-b46f-1823df8699dc
2026-07-26 10:13:28 +02:00
Stephen Lyons
113218930f
Infrastructure: Swap out QtConcurrent module header for sub-module ones (#9246)
#### Brief overview of PR changes/additions
The Qt documentation for `QtConcurrent` points out:
> If you include the `<QtConcurrent>` header, the entire Qt Concurrent
module with the entire Qt Core module will be included, which may
increase compilation times and binary sizes. To use individual functions
from the QtConcurrent namespace, you can include more specific headers.
>
> The table below lists the functions in the QtConcurrent namespace and
their corresponding headers:

|Function|Header|
|--------|------|
|`QtConcurrent::run()`|`<QtConcurrentRun>`|
|`QtConcurrent::task()`| `<QtConcurrentTask>`|

|`QtConcurrent::filter()`,<br>`QtConcurrent::filtered()`,<br>`QtConcurrent::filteredReduced()`|`<QtConcurrentFilter>`|
|`QtConcurrent::map()`,<br>`QtConcurrent::mapped()`,<br>`QtConcurrent::mappedReduced()`|`<QtConcurrentMap>`|

#### Motivation for adding to Mudlet
To speed up the build a little by removing stuff that isn't needed.

#### Other info (issues closed, discussion etc)
In doing this I happened to start cleaning up a couple of header files
`T2DMap.h` and then `mudlet.h`, I then got into converting some
`#include`s into forward declarations in a "include-what-you-use" move.
This then rippled through into a (more than 10!) number of files but
should "improve" things.

Note that the ordering of `#include` in many files seems to be rather
haphazard and is due for some serious overhaul - I suggest that we
should actually declare an "official" style for this project so that
everyone knows what it is.

**During the CI/CB process I discovered that Linux and then MacOS builds
were failing because the file referred to by the `#include
<QtConcurrentTask>` header file was missing, yet was present on my local
PC when I was using the Qt framework from the On-line installer.
Initially I suspected a Debian (and then Devuan - as the packaged
version on my own machine also had this defect AND Ubuntu) package
problem; however it now seems to be an upstream Qt issue as the various
Qt versions & OS combinations suggest that Qt themselves fixed it for Qt
6.10:**
| OS     | QtVersion             | Missing header |
|--------|-----------------------|----------------|
| Windows| 6.11.0 package        |       No       |
| Devuan | 6.8.2 package         |      Yes       |
| Devuan | 6.10.0 online install |       No       |
| Ubuntu | 6.9.0 package         |      Yes       |
| Debian | 6.8.2 package         |      Yes       |
| MacOS  | 6.9.0 package         |      Yes       |

**To fix this I reverted to an `#include <qtconcurrenttask.h>` for Linux
and MacOS builds - although it would probably have been better to make
it conditional on the Qt Version instead...**

*I have reported this upstream to Debian - see:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1135197*

---------

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2026-04-29 13:35:29 +01:00
Morquin
3036e1f3d7
improve: Give players full control over Discord Rich Presence (#9116)
## Summary

Players had no clear way to control what Discord shows about their
Mudlet activity. The old checkbox in the connection pane only gated
server GMCP data but didn't prevent Discord from showing "Playing
Mudlet", and the privacy controls were confusing. This PR replaces all
of that with three straightforward modes via radio buttons in Profile
Preferences > Chat:

- **Show full game details (if supported)** - full game integration with
server-provided presence (default)
- **Show Mudlet only** - only shows "playing Mudlet", game server is not
told about Discord
- **Disabled** - Discord shows nothing about Mudlet

Players pick the mode that matches their comfort level, and the existing
privacy checkboxes (hide detail, hide state, etc.) remain available in
Game details mode for finer control.

### What changed

- **Three-mode radio buttons** in Profile Preferences > Chat with a
two-column layout (modes on the left, privacy controls on the right),
replacing the old connection-pane checkbox
- **Server-origin tracking** so privacy checkboxes only gate data sent
by the game server - Lua API calls always pass through (only Disabled
mode blocks Lua entirely)
- **Mid-session mode switching** via dynamic GMCP negotiation
(Core.Supports.Add/Remove + External.Discord.Hello/Get)
- **Deferred RPC init** - Discord RPC now starts when a profile loads,
not on app launch
- **Username restriction improvements** - takes effect immediately,
case-insensitive (Discord usernames are lowercase-only since 2023),
shuts down RPC when mismatched
- **Shows logged-in Discord user** in preferences next to the
restriction field, with a tooltip explaining the desktop app requirement
when not connected
- **Presence fix** - empty string fields now send nullptr so Discord
hides them instead of showing blanks
- **Memory leak fix** - presence allocations are now freed in the
destructor regardless of RPC state

### Cleanup

- Removed obsolete discriminator field
(`mRequiredDiscordUserDiscriminator`) - Discord removed discriminators
in 2023
- Removed dead code (`getDiscordUserDetails()`, never called)
- Restored `Discord_ClearPresence` function pointer for potential future
use
- Use proper `Host::DiscordOptionFlags` types instead of raw `int`
(thanks @SlySven)

### Known quirks

- The "Hide timer" checkbox correctly omits timestamps from presence
data, but Discord's client starts its own activity timer for any
presence without a timestamp - this is Discord client behavior outside
our control.
- The "Hide large icon" setting clears the image key, but some Discord
clients fall back to the application's default icon instead of hiding it
entirely.

### Test plan

- [ ] Open Profile Preferences > Chat tab
- [ ] Switch between the three radio button modes and verify Discord
presence updates accordingly
- [ ] In Game details mode, toggle privacy checkboxes and verify fields
are hidden/shown
- [ ] Use Lua API (e.g. `setDiscordDetail("test")`) in Mudlet only mode
- should work. Try in Disabled mode - should fail with error
- [ ] Set a username restriction and verify presence clears immediately
if mismatched
- [ ] Run unit tests: `cd build && ./test/DiscordTest`
- [ ] Run functional tests: `cd build && ctest -R TDiscordModeTest -V`

Closes #6967. Supersedes #7438.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: SlySven <slysven@virginmedia.com>
2026-04-08 13:33:09 +02:00
Stephen Lyons
f0b60a197f
Fix: handle over/underflows in (int) TLuaInterpreter::getVerifiedInt(…) (#8924)
#### Brief overview of PR changes/additions
Revises `(int) TLuaInterpreter::getVerifiedInt(...)` so it checks and
errors out should the call of `lua_tointeger(...)` return a value
outside the range that can be conveyed as an `int` (actually `int32_t`).
This function actually returns a `lua_Integer` which is a `typedef` of
`ptrdiff_t` - which is a signed 32-bit integer on 32-Bit Windows OS but
is actually a signed 64-bit integer on all the platforms we currently
support.

#### Motivation for adding to Mudlet
Prevent odd behaviour in the event of integer over/underflows.

#### Other info (issues closed, discussion etc)
This came about from the changes in
3609e945a1 as part of #4661 in 2021 (which
seemes to assume `getVerifiedInt(...)` did actually return an `int`) -
ironically I had actually fixed the conversion of 64-bit to 32-bit
integers for exit weights in the earlier #2106 but which that PR undid.

---------

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
2026-04-04 07:37:14 +02:00
Vadim Peretokin
b6738dd8c2
infrastructure: Apply clang-format to all CPP files (#8804)
#### Brief overview of PR changes/additions
Ran clang-format on all 134 CPP files in src/ using the project's
.clang-format config

#### Motivation for adding to Mudlet
Ensures consistent code formatting across the codebase.

#### Other info (issues closed, discussion etc)
None

**Test case:** Build the project and verify it compiles successfully.
2026-01-19 18:10:44 +01:00
Vadim Peretokin
e7f22e5dbc
Infrastructure: reduce Mudlet build times by 30s (#8403)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Reduce Mudlet build times
#### Motivation for adding to Mudlet
Address #6765, and give developers a better experience.
#### Other info (issues closed, discussion etc)
  Benchmark Results

=== Testing: development (before) (development) ===
Run 1 of 3...
  Time: 226.071397436s
Run 2 of 3...
  Time: 216.059263209s
Run 3 of 3...
  Time: 221.592760908s

=== Testing: PR #8403 (after) (pr-8403) ===
Run 1 of 3...
  Time: 191.112400957s
Run 2 of 3...
  Time: 193.975717783s
Run 3 of 3...
  Time: 196.569316252s


[benchmark-mudlet-build.sh](https://github.com/user-attachments/files/24466717/benchmark-mudlet-build.sh)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2026-01-08 21:04:24 +01:00
Vadim Peretokin
8cb58cf0ca
Infrastructure: remove MSVC leak detection (#8378)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Remove support for leak detection using MSVC - this functionality hasn't
been in use in a decade, and we're switching to to using
[LeakSanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer)
instead.
#### Motivation for adding to Mudlet
Cleaner code.
#### Other info (issues closed, discussion etc)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-21 15:11:09 +01:00
Vadim Peretokin
387fb0bd9b
Improve: add a new, experimental 3D mapper (#8087)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
This adds an experimental, new 3D mapper that uses shaders, more modern
openGL, and a far better code reorganization that makes it an easier
foundation to build upon.

The new 3D mapper is here side by side with the original and can be
toggled on for experimentation. There's a lot of work to be done, so I'd
rather merge it early instead of making a mega-PR.
#### Motivation for adding to Mudlet
So we have a new foundation to build upon and improve.
#### Other info (issues closed, discussion etc)
Old and new mapper can be toggled dynamically with:
```lua
-- this can be a keybinding
setConfig("experiment.3dmap.modernmapper", not getConfig("experiment.3dmap.modernmapper"))
```

Smooth movement is one experiment in the new mapper, and it can be
enabled with:
```lua
lua setConfig("experiment.rendering.smooth-camera", true)
```

As you notice an experiments system has been added so we can implement
things at once and experiment to choose the one that works best. This
system can be used in other places in Mudlet as well.

<details><summary>Details</summary>
<p>

 ## Experiments System

  ### Overview
Allows enabling/disabling experimental features via
`setConfig`/`getConfig` with validation against a predefined
  whitelist.

  ### Usage
  ```lua
  -- Enable experiment
  setConfig("experiment.rendering.more-transparent", true)

  -- Check if enabled
local enabled = getConfig("experiment.rendering.more-transparent") --
returns true/false

  -- Get active experiment in group
local active = getConfig("experiment.rendering.active") -- returns
"more-transparent"

  -- List all valid experiments
local experiments = getConfig("experiment.list") -- returns table of
valid keys
```

### Behavior

  - Grouped experiments: Mutually exclusive (enabling one disables others in same group)
  - Validation: Only predefined experiments allowed, invalid keys return errors
  - Persistence: Experiment states saved/loaded with profiles


  ### Adding New Experiments

  Edit Host::mValidExperiments in src/Host.cpp:
```cpp
  const QSet<QString> Host::mValidExperiments = {
      qsl("experiment.rendering.originalish"),
      qsl("experiment.rendering.more-transparent"),
      qsl("experiment.newfeature.option1"),  // Add here
  };
```

  ### Current Experiments

  - experiment.rendering.originalish
  - experiment.rendering.more-transparent


</p>
</details>

---------

Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2025-08-29 12:15:48 +02:00
Vadim Peretokin
5250b45c20
Infrastructure: split TLuaInterpreter.cpp's Discord files out separately (#7133)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Split TLuaInterpreter.cpp into a domain-specific Discord functions file.
#### Motivation for adding to Mudlet
A 18k line C++ files makes a lot of tools cry.
#### Other info (issues closed, discussion etc)
I've purposefully put the new files into a similar but random-ish
location in mudlet.pro and CMakeLists.txt to reduce the changes of
conflicts when merging the PRs. We can fix the order in a single PR
afterwards.

---------

Co-authored-by: Vadim Peretokin <vadim.peretokin@carasent.com>
Co-authored-by: Kebap <kebap_spam@gmx.net>
2024-02-17 18:42:01 +01:00