CRITICAL-1/2: Remove duplicate online=1 writes from doPlayerJoin.
The synchronous onPlayerLoggedInKickCheck already sets online=1.
The background thread writes raced with logout's online=0, permanently
locking players as "online" after crash-disconnect during join.
HIGH-1: Startup SQL uses PreparedStatement for server_id (was string concat).
HIGH-2: update() method now uses try-with-resources for PreparedStatement.
HIGH-3: NPE guard in RS2 data file logging when getRS2DataFile returns null.
Vyrriox
CRITICAL fixes:
- C-1/C-2/C-4: Auto-save and logout now run on MAIN THREAD. All entity
reads (inventory, curios, effects) were happening off-thread, causing
duplication exploits (player interacts during save → items duplicated).
Auto-save uses tryLock() to skip players already being saved.
- C-5: NPE fix for non-RS2 items (null check on registry key lookup)
- C-6: RS2 .dat file written atomically (temp file + rename) to prevent
corruption of entire RS2 storage on crash mid-write
HIGH fixes:
- H-3: Deadlock prevention: lock released BEFORE latch.await() in
doPlayerJoin. Prevents shutdown deadlock where background thread
holds lock while waiting for main thread, and shutdown holds main
thread while waiting for lock.
- H-5: Curios cache now works WITHOUT keepInventory. Players who die
then disconnect before respawning no longer lose curios data.
- H-8: server_id SQL uses PreparedStatements instead of string concat
MEDIUM fixes:
- M-1: NumberFormatException in LocalJsonUtil caught per-entry instead
of crashing entire map parse (prevents losing all cosmetic armor)
Vyrriox
1. Sophisticated Storage shulkers/barrels/chests:
- ROOT CAUSE: UUID stored as DataComponent (not in CustomData).
extractStorageUuid() only checked CustomData, missing the UUID.
- FIX: Use StackStorageWrapper.fromStack(provider, item).getContentsUuid()
which reads the DataComponent via the proper API.
- Also scan ender chest for packed storage items.
2. Refined Storage 2 disks:
- ROOT CAUSE: save() on StorageRepositoryImpl returned data in an
unknown codec format that our extraction couldn't parse.
- FIX: Read/write the .dat file directly from disk after forcing
a save flush. This uses the exact NBT format RS2 writes.
- Search multiple NBT structures (direct keys, nested compounds,
list-of-pairs) to handle any codec format.
- On restore: write entries into .dat file, clear DimensionDataStorage
cache via reflection to force RS2 to reload.
3. Kick system:
- ROOT CAUSE: PlayerNegotiationEvent.getConnection().disconnect()
does NOT work in NeoForge 1.21.1 (too early in connection).
- FIX: Full duplicate check moved to PlayerLoggedInEvent with
HIGHEST priority. Uses player.connection.disconnect() which
is reliable on the server thread.
- Marks online=1 synchronously to close race condition.
Vyrriox
1. CRITICAL - Anti-dupe: Player inventory mutations now run on the main
server thread via server.execute(). DB reads stay async, but all
setItem/setHealth/addEffect calls happen on the tick thread.
CountDownLatch ensures the lock is held until apply completes.
2. CRITICAL - Resource leaks: 3 QueryResults in PlayerSync.java startup
now use try-with-resources + PreparedStatements instead of raw
String.format SQL.
3. HIGH - Curios save: UPDATE changed to REPLACE INTO to prevent silent
no-ops when the curios row doesn't exist yet (new player who died
before first init save).
4. HIGH - RS2 restore: Removed skip-if-exists check. DB is always the
source of truth - stale local data was persisting permanently.
5. HIGH - Race conditions: Shutdown save now acquires per-player lock.
All logout saves (curios, mod-compat, inventory) moved inside
doPlayerLogout under a single lock acquisition.
6. HIGH - SQL injection: DATABASE_NAME validated against [A-Za-z0-9_]+
regex on startup to prevent injection via config.
Vyrriox
1. NeoForge attachments (SOL Onion, Ars Nouveau, etc.):
- deserializeAttachments signature is (Provider, CompoundTag) not
(CompoundTag) - reflection was failing silently, nothing restored
- Use serializeAttachments(Provider) directly for saving instead of
saveWithoutId() for cleaner approach
- This fixes SOL Onion food diversity, Ars Nouveau mana/glyphs,
Iron's Spellbooks, Pehkui scale, and all other NeoForge attachments
2. Multi-server kick:
- Add secondary kick check in PlayerLoggedInEvent as fallback
- Mark online=1 SYNCHRONOUSLY on login to close race condition
where async doPlayerJoin hasn't set online=1 yet
3. Backpack upgrades:
- Call refreshInventoryForInputOutput() before reading from
BackpackStorage to flush pending wrapper changes
Vyrriox
Root cause: Sophisticated Storage uses its own ItemContentsStorage
(SavedData) for packed items, NOT BackpackStorage from Sophisticated
Backpacks. The code was calling BackpackStorage which returned empty
data for storage items.
Fixes:
- Use ItemContentsStorage.get().getOrCreateStorageContents() for save
- Use ItemContentsStorage.get().setStorageContents() for restore
- Add extractStorageUuid() for "storageUuid" key (SS uses this, not
"contentsUuid" which is for backpacks only)
- Try both UUID keys when scanning inventory items
- Add sophisticatedstorage as compileOnly dependency
Vyrriox
- Sync RS2 disk storage contents between servers (storageReference UUID)
- Support both refinedstorage and extradisks namespaces
- Save: extract individual entries from StorageRepository SavedData
- Restore: decode via RS2 codec and inject into target server repository
- Skip restore if storage already exists on target server (no overwrite)
- Scan inventory + ender chest for disks
Vyrriox
Adds a generic system that syncs ALL NeoForge player attachments,
covering per-player data from every mod in the modpack:
- Ars Nouveau: mana, glyph/spell knowledge
- Iron's Spellbooks: mana, learned spells, cooldowns
- Pehkui: player scale
- Spice of Life: Onion: food diversity history
- And ANY other mod using NeoForge's attachment system
Implementation:
- Save: extracts neoforge:attachments tag from player.saveWithoutId()
- Restore: uses reflection to call NeoForge's deserializeAttachments()
which ensures exact same deserialization path as normal player load
- Stored as BNBT in mod_player_data table (mod_id=neoforge_attachments)
Also verified CosmeticArmours (mod id: cosmeticarmoursmod) and
CosmeticWeapons (mod id: cosmeticweaponsmod) are content-only mods
that add craftable items - no custom player storage, fully handled
by existing inventory sync.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Accessories API sync for Aether mod accessory slots (pendant, cape,
gloves, rings, shield, misc). Uses same pattern as Curios: validate data
before clearing slots, PreparedStatements for DB operations
- Add Cosmetic Armor Reworked sync for 4 cosmetic armor slots via
InventoryManager/CosArmorAPI
- Add Apotheosis + Placebo as compileOnly deps. Apotheosis item data
(affixes, gems, sockets, rarity) travels with items via DataComponents
and is already synced by the inventory sync
- New generic mod_player_data DB table with composite key (uuid, mod_id)
for extensible mod-specific data storage
- Integrated save/restore in join, logout, and auto-save pipelines
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix advancements disappearing: use PreparedStatements for all SQL with
user data (advancement JSON contains chars that broke string-concat SQL),
add null safety for advancement file
- Fix multi-server kick: run doPlayerConnect synchronously instead of async
(players could join before the duplicate check completed)
- Fix Curios disappearing: clear slots AFTER validating data exists (not
before), use CuriosCache for dead players on logout instead of empty API
- Fix Sophisticated Storage items: add storeSophisticatedStorageItems() and
restoreSophisticatedStorageItems() to sync packed barrels/shulkers/chests
- Anti-duplication: clear all inventories before restoring from DB on join
- Fix tick counter: remove LevelTickEvent (fired per dimension = 3x too
fast), merge heartbeat into ServerTickEvent
- Fix connection leaks: use try-with-resources for all QueryResult
- Fix logout order: save data BEFORE marking player offline
- Skip auto-save for dead/unsynced players to prevent saving empty data
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>