Skip to content

Fix css_unban / css_unmute silently failing on SQLite (long -> int via dynamic binder)#285

Open
playaopindo-dev wants to merge 1 commit into
daffyyyy:mainfrom
playaopindo-dev:fix/sqlite-unban-unmute-long-to-int
Open

Fix css_unban / css_unmute silently failing on SQLite (long -> int via dynamic binder)#285
playaopindo-dev wants to merge 1 commit into
daffyyyy:mainfrom
playaopindo-dev:fix/sqlite-unban-unmute-long-to-int

Conversation

@playaopindo-dev
Copy link
Copy Markdown

Fix css_unban / css_unmute silently failing on SQLite

Fixes #284

Summary

css_unban, !unban, css_unmute, css_ungag, css_unsilence all print their cosmetic success message but never write to the database on SQLite. Root cause: in both BanManager.UnbanPlayer and MuteManager.UnmutePlayer, the row id is read via int x = row.id; against a Dapper dynamic. On SQLite the value is boxed as long, the C# dynamic binder refuses the implicit longint conversion at runtime and throws RuntimeBinderException before either INSERT or UPDATE runs. The surrounding bare catch { } then swallows it — no log, no DB write, but the admin already saw "Unbanned with pattern X". MySQL is unaffected because MySqlConnector materialises INT as int directly.

Full repro, exception trace, and caller-format matrix in the linked issue.

Changes

Three minimal hunks against eea700b:

  1. BanManager.cs::UnbanPlayerint banId = ban.id;int banId = Convert.ToInt32((object)ban.id);. The (object) cast bypasses the dynamic binder so Convert.ToInt32 resolves the well-defined object overload, which handles both boxed int (MySQL) and boxed long (SQLite) without runtime binding surprises.
  2. MuteManager.cs::UnmutePlayer — same one-line change at the corresponding int muteId = mute.id;.
  3. BanManager.cs::UnbanPlayer — bare catch { } replaced by catch (Exception ex) { CS2_SimpleAdmin.Instance?.Logger?.LogError(ex, "UnbanPlayer failed for pattern {Pattern}", playerPattern); }. This matches the logging style already used by every other try/catch in the same file (BanPlayer, AddBanBySteamid, AddBanByIp). Without this hunk the original bug would have been impossible to root-cause from logs alone; keeping it as a guard is what surfaced the issue in the first place. Drop this hunk if you prefer to land the behavioural fix in isolation.

MuteManager.UnmutePlayer's existing catch (Exception ex) { Console.WriteLine(ex); } is left untouched in this PR — its routing to Console.WriteLine (which writes to the CS2 console scrollback but not to the rolling SA log file) is also worth tightening, but that's a separate cleanup and out of scope for this fix.

Test plan

  • SQLite: synthetic ACTIVE ban planted, css_unban <steamid> from server console pre-fix → RuntimeBinderException surfaced via the logging catch, sa_bans.status stayed ACTIVE, sa_unbans empty.
  • SQLite: same synthetic ban, post-fix → sa_bans.status='UNBANNED', sa_bans.unban_id populated, sa_unbans row inserted with matching ban_id, no exception in log-CS2-SimpleAdmin*.txt.
  • dotnet build -c Release clean (0 warnings, 0 errors) against net8.0.
  • MySQL: not retested by submitter — I don't have a MySQL test environment for this plugin handy. The fix preserves the existing MySQL semantics: on MySQL, MySqlConnector returns INT as int, Convert.ToInt32((object)int) returns the same int, so the loop body executes identically to before. A second pair of eyes on a MySQL deployment would be welcome before merge.
  • Mute-path live test: not separately exercised because root cause is identical to the ban path and the fix is symmetric. Recommend a quick css_unmute smoke test on SQLite at merge time.

Notes

Diff is intentionally tight. The bigger architectural smell — Task.Run(async () => await ...) fire-and-forget in OnUnbanCommand paired with the pre-emptive cosmetic success reply — is what made this bug invisible to admins for so long. I'd happily follow up with a separate PR that funnels these through a small result type so command replies reflect actual outcomes, if there's appetite for that direction.

Tested SA version: 1.7.9a (eea700b). Repo branch: main.

On SQLite, INTEGER PRIMARY KEY AUTOINCREMENT values flow through
Dapper as boxed long. `int banId = ban.id;` / `int muteId = mute.id;`
against a dynamic row triggers RuntimeBinderException
("Cannot implicitly convert type 'long' to 'int'") before any
INSERT/UPDATE runs, and the surrounding bare `catch { }` in
BanManager.UnbanPlayer swallows it — leaving sa_bans.status='ACTIVE',
sa_unbans empty, and the admin staring at a cosmetic
"Unbanned with pattern X." reply. MySQL is unaffected because
MySqlConnector materialises INT as int.

Normalise the conversion path via Convert.ToInt32((object)x.id),
which handles both boxed int (MySQL) and boxed long (SQLite)
without invoking the C# dynamic binder. Same pattern fixed in
both UnbanPlayer and UnmutePlayer.

Also tighten BanManager.UnbanPlayer's bare `catch { }` to log via
the existing CS2_SimpleAdmin.Instance.Logger, matching the style
of BanPlayer/AddBanBySteamid/AddBanByIp in the same file. This is
what surfaced the bug during root-cause analysis.
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.

css_unban / css_unmute silently fail on SQLite — RuntimeBinderException swallowed by bare catch in BanManager/MuteManager

1 participant