The previous approach read the live config.yml back through NIO to
recover the raw ^{ENV_VAR} tokens, on the assumption that SubstAgent
only intercepted java.io.FileInputStream. Current SubstAgent also hooks
sun.nio.fs.UnixNativeDispatcher and sun.nio.ch.FileDispatcherImpl, so
NIO reads come back substituted too and the tokens were unrecoverable.
Reading through a hard link with a non-config extension does not help
either. SubstAgent keys its cache by file descriptor number and only
sanity checks the inode, and a hard link shares the inode of the file it
points at, so a recycled descriptor matches an already consumed entry
and the read reports EOF instead of returning the real bytes.
Take the variable names from the default config bundled in the plugin
jar instead. A jar is not a config path, so that copy keeps its raw
placeholders, verified against the agent. A key is only rewritten when
its in-memory value equals the environment variable the default names
for it, which is the only way SubstAgent could have produced it.
Also use ^{ENV_VAR} for the database credentials, which is the syntax
SubstAgent expands.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Corrupt shop loading was destructive and crashed. Both branches of the
debug.delete-corrupt-shops check called deleteShop, so a shop whose
container was temporarily missing lost its signs and its database row
even with the option disabled. deleteShop then refunded through a null
economy core and threw, aborting shop loading and ChunkLoadEvent.
- ContainerShop#handleLoading now unloads instead of deleting when
delete-corrupt-shops is false, leaving the row and signs intact.
- SimpleEconomyTransaction fails transactions with a lastError instead
of dereferencing a null core in checkBalance, commit and
failSafeCommit.
- SimpleShopManager#refundShop skips and warns when no economy is
loaded, matching the guards every other economy call site has.
- The economy bridge loads before shops instead of one tick after, so
refunds during loading can see it. EconomyLoader#load is now
idempotent and the post-startup attempt reports a missing bridge at
WARN rather than only at debug level.
Adventure was compiled against 4.22.0 while Purpur 1.19.4 provides
4.13.1 at runtime, so any 4.14+ call would have been a
NoSuchMethodError. Pin the API to 4.13.1, align the platform artifacts
to 4.3.2 in both the build and plugin.yml, and drop the ANSI and JSON
serializers, which do not exist at 4.13.1 and were only used to print
classpaths in diagnostics.
Also:
- Add /qs beacon, which marks empty shops with particle columns that are
only sent to the command runner.
- Add ShopManager#transferShopsOwnership so ShopBridge-OG can move shops
to a new owner when an AreaShop-OG plot is transferred.
- Keep SubstAgent $ENV_VAR placeholders on disk. Bukkit reads configs
through FileInputStream, which SubstAgent intercepts, so saving the
config persisted resolved credentials. The config file is now only
rewritten when a migration actually changed something, and every save
restores the raw tokens read back through NIO.
- Use SubstAgent's $VAR syntax for the database credentials. The ^{VAR}
form was never substituted, since SubstAgent only triggers on '$'.
- Read the configuration once instead of in both onLoad and onEnable.
- Name the log sink that logging.location selects instead of always
claiming qs.log.
- Warn when an item expression references an unregistered item, which
previously produced a rule that silently never matched.
- Stop shipping example rules as live config, and tolerate an empty
limits.ranks section instead of throwing.
- Default debug.disable-debuglogger to false and legacy-updater
shop-sign to false.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>