Repository navigation
Conversation
v2 starts from a fresh skeleton (plan decision 1). The v1 sources, the copied and prebuilt libraries, premake, the Lanes submodule and the old workflow are removed here; master keeps v1, and later phases port the bindings from it file by file. The VC:MP SDK header moves to third_party/vcmp/vcmp.h. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 1 of V2_PLAN.md: the new layout, the build, and proof that every dependency links into one self-contained plugin binary that loads. - vcpkg manifest (baseline pinned, Lua 5.4.8 override) for Lua, sol2, spdlog, fmt, SQLite, OpenSSL, libpq, MariaDB Connector/C, curl and zlib, with static, release-only triplets (hidden visibility; /MT on Windows). - Pinned tarballs (SHA256) for LuaSQL, lua-cjson, LuaSocket, LuaFileSystem, Copas, binaryheap, timerwheel, inspect.lua and digestpp. C modules are registered in package.preload; Lua modules are embedded and load in text mode, so require needs no file on disk. - Minimal runtime that follows the B3 rules: one owner with a fixed shutdown order, noexcept callbacks, structSize checks, OpenSSL initialised first (no atexit handler, no openssl.cnf), panic handling that marks the runtime dead, and a sandboxed luaconfig.lua with field-path errors. - tests/host: plugin_host loads the built binary with a fake server API and runs init, frames, shutdown and unload; 6 tests, including older servers and invalid configs. ci/check-binary.sh/.ps1 enforce the B8 rules. - Pinned manylinux_2_28 build image and CI for Linux and Windows. - MySQL gate (tests/integration): passes, so MySQL support stays. - tests/probe and docs/internals.md: callback order on a real server. Deviations from the plan: - LuaFileSystem is built from its tarball instead of the vcpkg port, and lfs.h and lua_cjson.c are patched: both hard-code __declspec(dllexport), which would export luaopen_* from the Windows DLL. - timerwheel requires "coxpcall" on every Lua version, so a small built-in coxpcall module returns the standard functions. - LuaSQL 2.8.1 never sets MYSQL_READ_DEFAULT_GROUP. Its MySQL driver gets two patches instead: memory-safety fixes, and connection options (timeouts, TLS mode, CA file, and a plugin directory that cannot exist, so the connector never loads a client plugin from disk). Verified locally: Linux build, 6/6 host tests, check-binary.sh, the MySQL gate, and LuaPlugin_x64.so in a real VC:MP server. Not verified yet: the Windows job (runs once the branch is pushed) and the probe's player events (need a game client). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A VC:MP client joined the probe server three times: kicked, quit, and online during ShutdownServer(). OnPlayerDisconnect fires inside KickPlayer, the player stays valid during the event, and a player who is online at shutdown is disconnected only after OnServerShutdown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Cache the vcpkg packages under a new key per run and save them right after they are built, even when a later step fails, so a fix cycle never rebuilds the dependencies. A run saves only when it built new packages. - On failure, print the vcpkg build logs and upload them with the CMake and CTest logs. Test and binary-check steps run even if the other one fails. - Job timeouts, and a workflow_dispatch option to run one platform only. - MSVC: build with /EHs (the panic handler throws through Lua's C frames), and avoid two /W4 warnings that /WX would turn into errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows job failed in configure: vcpkg needed the old lua port tree for
the 5.4.8 override, and git tried to fetch it from the partial clone's
promisor remote ("Could not resolve host: github.com"). A full clone of
vcpkg is about 113 MB and needs no fetch while building.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 2 of V2_PLAN.md: the runtime core the bindings build on, with the B3 safety rules, covered by unit tests against an in-memory server. - Runtime owns the Lua state, EventBus, Scheduler, entity pools and FramePump, and shuts down in the B3.1 order: mark closing, release every Lua reference C++ holds, close the state (finalizers run while the subsystems still exist), destroy the subsystems. After a Lua panic the runtime is dead, every callback is a no-op, and the state is leaked. - plugin/callbacks.cpp: noexcept server callbacks that tolerate a missing, closing or dead runtime; callbacks are written only where structSize has room; server functions are read through ServerApi (VCMP_LUA_API raises "not supported by this server version"). A shutdown that arrives during a Lua call runs when the call returns; Server.reload() runs at the end of the frame, deletes what the old runtime created, and keeps the old scripts running if the config or the new state fails. - EventBus: events indexed by an enum plus custom events, stable handler storage, dispatch up to the size at its start, tombstones compacted only when no dispatch is active, Event.cancel() for the innermost dispatch. v1 names and arguments (Event.bind/unbind/cancel/create/trigger). - Scheduler: 64-bit steady_clock milliseconds, timers held by id, due timers collected before any is called, no catch-up bursts. v1's Timer.create/destroy and thisTimer. - EntityPool + EntityRef: one cached Lua handle per entity lifetime (== and table keys work), generations, idempotent adopt/release during the synchronous pool events, startup enumeration, "<kind> no longer exists". - Every Lua value C++ stores is a main_* reference, so handlers and timers created in coroutines survive them. - Config: log.file and log.daily now work. - At server shutdown the runtime dispatches onPlayerDisconnect for players still online (the open decision from phase 1; docs/internals.md). - tests/unit: doctest + FakeServer, 31 cases: handle identity and invalidation, adopt/release during synchronous pool events, timers across a simulated 72-minute clock, bind/unbind/cancel during nested dispatch, a kick inside a handler, handlers bound in coroutines, failing handlers, shutdown with live finalizers, deferred shutdown and reload, panics. - linux-asan preset and CI job: the unit tests under ASan + UBSan. - docs/internals.md describes the runtime; docs/MIGRATION-v2.md is started. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The phase 1 header (API 2.0) was one function and one callback short of the server (docs/internals.md). This is SqMod's module/VCMP/vcmp21.h at 4a6bfc0, whose PluginFuncs and PluginCallbacks are exactly the sizes the server reports. It appends GetNetworkStatistics and OnEntityStreamingChange, adds vcmpEntityPoolPlayer, and renames vcmpPlayerUpdateAiming to vcmpPlayerUpdateAimingDeprecated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The groundwork for porting the v1 classes.
- bindings/args.hpp: argument types for bindings (Int32, UInt8, Float,
Boolean, String, Colour, Vec3, Opt<T>, Live<K>, Ctx) with sol2
customization points. They convert with range checks (plan B3.9):
integers accept integral floats, refuse fractions and out-of-range
values instead of narrowing; booleans are only true/false; colours
accept [-2^31, 2^32); Vec3 is a table {x, y, z} or three numbers, read
with raw access. Live<K> is a handle of kind K whose entity still
exists, so every method that takes it as self validates its entity.
- Errors read like luaL_argerror and carry the script position:
"main.lua:12: bad argument #1 to 'setWeapon' (integer expected, got
string)", "bad value for 'health' (...)" for property assignments.
Bindings throw; an exception handler set in bindings::Register adds
the position.
- Check(): a server call that returns vcmpErrorRequestDenied gives
false; any other error raises "'<function>' failed: <error>".
- FakeServer: every PluginFuncs field is a recorder that logs
"Name(args)", fills out-parameters and buffers, and returns values a
test configures; players, entities and kicks keep their state. The
plugin's callbacks are now FakeServer::plugin.
- tests/lua: Lua test files run by the unit tests, each in a new
FakeServer, with helpers.lua and a fake.* table.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v1 constant tables (ServerOption, DisconnectReason, BodyPart, PlayerState, PlayerUpdate, PlayerOption, PlayerVehicle, VehicleUpdate, VehicleOption, VehicleSpeed, PickupOption, EntityType), read-only, with v1's names and values. Fixed: BodyPart.rightLeg was the right arm's value; ServerOption listed deathMessages twice. New: EntityType.player and NetworkStatistics (plugin API 2.1). PlayerUpdate.aiming keeps its value, which the SDK now calls deprecated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Player's v1 members and the player events, ported onto the B3/B4 core. - Every member takes a checked handle (Live<Player>) and checked arguments; properties use the same handle check through bindings::Property. - Fixed from v1: messages are sent with a "%s" format (v1 passed the text as the printf format); name is read from the server and set without strcpy into 24 bytes; ip/uid/uid2 check the server's result; vehicle = nil and spectateTarget = nil work; ammo returns the current weapon's ammo; getActive(true) filters; booleans are true/false. - New members: findByName, speed, addSpeed, away, onFire, gameKeys, standingOnVehicle, standingOnObject, giveMoney, getWeaponAtSlot, getAmmoAtSlot, getNetworkStatistics; valid for every entity kind. - Common entity members (type, getType, getID, findByID, count, getActive) live in entities.cpp for every kind. - callbacks.cpp: the player events with v1's names and arguments. Cancellable callbacks return 0 after Event.cancel(). onPlayerWasted runs every handler (v1 only the first); onPlayerCommand also passes the raw argument text; entities first seen in an event are adopted. - tests/lua/player.lua covers every member against the recording fake server; tests/unit/test_callbacks.cpp covers the events. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vehicle's v1 members and the vehicle events.
- The server owns vehicles (plan B4): Vehicle.create returns a handle and
the vehicle stays until vehicle:destroy() or until the server or another
plugin deletes it. v1's Vehicle(...) and Vehicle.new(...) are kept as
aliases. In v1 the Lua GC deleted the vehicle, then deleted again with
a reused id, and a throwing constructor left __gc deleting garbage.
- Fixed: rotation round-trips (the setter accepts the getter's
{euler, quaternion} table); booleans are true/false; getOccupant of an
empty seat is nil; setSpeed's add flag is optional.
- New: model, wrecked, lightsData, explode().
- ArgReader reads arguments by position for the bindings that accept
several forms (create, setSpeed, setRotation, resetHandlingRule).
- onVehicleUpdate, onVehicleExplode and onVehicleRespawn.
- tests/lua/vehicle.lua covers every member, including ownership: a
collected handle deletes nothing, and a vehicle deleted elsewhere is
noticed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Object's v1 members and the object events.
- The server owns objects: Object.create (and v1's Object(...) and
Object.new) returns a handle; the object stays until object:destroy()
or until the server deletes it.
- Fixed: object.angle = {x, y, z} rotates to the angle (v1 rotated by
it); rotateTo/rotateBy pick the quaternion form by the table's length
instead of treating w = -1 as "no w"; alpha is range-checked.
- New: model and alpha properties.
- onObjectShot and onObjectTouch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pickup's v1 members and the pickup events. - The server owns pickups: Pickup.create (and v1's Pickup(...) and Pickup.new) returns a handle; the pickup stays until pickup:destroy() or until the server deletes it. - alpha (default 255) and automatic (default true) are optional when creating; alpha is range-checked. - New: model and quantity properties. - onPickupPickAttempt (cancellable: Event.cancel() refuses the pickup), onPickupPicked and onPickupRespawn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Checkpoint's v1 members and the checkpoint events. - The server owns checkpoints: Checkpoint.create (and v1's Checkpoint(...) and Checkpoint.new) returns a handle; the checkpoint stays until checkpoint:destroy() or until the server deletes it. The owner may be nil (every player sees it). - Fixed: radius = r sets the radius (v1 compared r with the world id first); alpha = a no longer reads a destroyed temporary; colour channels are checked to be integers in [0, 255]. - New: owner and sphere properties. - onCheckpointEnter and onCheckpointExit. - TableInteger: checked integer elements of a table argument, with an error naming the element. Object and Vehicle use it too, instead of converting the element on the stack top (which reported a negative argument number). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v1's Bind class on the B4 entity pools. - EntityKind::Bind: 256 slots, adopted at start-up (GetKeyBindData per slot) and on first sight in onPlayerKeyDown/Up. Binds raise no pool events, so every use asks GetKeyBindData and a bind another plugin removed raises "bind no longer exists". - The server owns binds: Bind.create (and v1's Bind(...) and Bind.new) returns a handle; the bind stays until bind:destroy(). v1 removed it when the Lua object was collected. - Bind.clearAllBinds() removes only the binds this plugin created (v1 called RemoveAllKeyBinds). A reload removes ours too, never others'. - tag is kept in the pool slot and dropped with the bind. - FakeServer models key bind slots; plugin_host answers GetKeyBindData. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v1's Stream class and onClientData, rebuilt on a bounded vector (plan A0). - Incoming client data is copied into a Stream of its own size. v1 memcpy'd it into a fixed 4096-byte array on the stack without checking the client-controlled size: a remote stack overflow. - Reads check that the whole value is present (v1 checked one byte) and raise instead of returning 0; writes check room for the whole value (v1's writeString could write one byte past the end) and raise at 4096 bytes. A failed read or write changes nothing. - Same byte layout as v1: numbers and floats little-endian, strings with a big-endian 16-bit length. Stream() works as well as Stream.new(). - send() without a player sends to everyone; new size and remaining. - onClientData builds one Stream object shared by the handlers; a handler's read error is logged and the server carries on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v1's Server.cpp: the server-wide functions, properties and events.
- Server is the single instance of a usertype, so v1's static
properties work (Server.name, Server.hour = 12); the functions keep
their dot-call form. Server.reload() stays.
- Fixed: addClass reads the weapons from the right arguments (v1 was off
by one) and its {x, y, z, angle} form is no longer shadowed; the IP
ban functions accept strings; getSkinName of an unknown id returns nil
instead of NULL as a std::string; Map.hideObject rounds negative
coordinates correctly; wastedSettings keeps fields a table leaves out.
- names.cpp: v1's skin and weapon name lookups, unchanged except for
memory handling and tolower on unsigned chars.
- Blip.create returns a Blip handle (blips are in the B4 pools since
phase 2); destroy and getInfo take the handle or an id.
- Events: onPluginCommand (cancellable), onServerPerformanceReport with
arrays instead of raw pointers, and the new onEntityStreamingChange.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Logger keeps v1's functions. setLevel sets the least severe level that is logged, by name or by v1's numbers 0 (debug) to 4 (critical); v1 compared an unsigned level with >= 0 and worked backwards. New getLevel. - MySQL, SQLite (and SqLite, SQLiteDatabase), Remote, JSON, Thread and dbg are stubs (plan B5): any use raises "<name> was removed in v2: <replacement> (see docs/MIGRATION-v2.md#<anchor>)". The guide gets the matching sections. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/integration/bindings: bindings.lua creates, writes, reads back and destroys every class against the real VC:MP 0.4 Linux server (no players needed), reloads once, checks that the reload deleted the old runtime's entities and binds, and shuts the server down. run.sh runs it in ubuntu:24.04. All checks pass and the server exits with 0. It found three server quirks (docs/internals.md): - The server refuses a plugin that reports API 2.1 and never calls it. The plugin reports 2.0 again (with the 2.1 header, PLUGIN_API_MINOR is 1); later fields stay behind the structSize checks. plugin_host checks the reported version. - GetServerName and GetGameModeText return vcmpErrorBufferTooSmall even when the text fits. ReadText takes that error for success; its buffers are far larger than the server's limits. - GetKeyBindData succeeds for all 50 slots, used or not; a free slot has keys 0, 0, 0. Start-up enumeration adopted every free slot and a removed bind still looked alive. A slot now holds a bind when a key is not 0. FakeServer reproduces the last two. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Vehicle rotation tables and Server.wastedSettings were read with lua_getfield, which runs __index metamethods inside a binding (plan B3.3). They use RawField (lua_rawget) now; Lua tests check that no metamethod runs. - FakeServer's stubs use one tag type per PluginFuncs field instead of a partial specialization over a member-pointer template argument, which older MSVC versions handle poorly; the unit tests build on Windows too. - names.cpp silences MSVC's unreachable-code warning for v1's "return ...; break;" switches. - The phase 3 sources are clang-formatted (Google style, 4-space indent, 100 columns, as the phase 2 code). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/integration/client: client.lua logs every event with its arguments, checks the Player members automatically on the first spawn (PASS/FAIL in the chat and the console), and has commands for vehicles, checkpoints, pickups, objects, key binds, the camera, command arguments, renaming, reload and kick. README.md describes the Windows server setup. The script loads on the Linux server; it needs a real client to run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows build failed on C4702 inside sol2's headers (function_types_stateful.hpp, stack.hpp): a backend warning raised when a binding always throws, such as the stubs of the removed v1 globals. It is disabled for first-party targets, which also covers v1's ported switches in names.cpp (its local pragma is gone). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owner's run of tests/integration/client on a Windows server with a real client passed everything except two checks, both faults of the script, not the plugin: - getNetworkStatistics raised "not supported by this server version": that server's PluginFuncs ends before the API 2.1 functions. The script now accepts that. - player.weapon read 0 right after setWeapon: the server reports a weapon only after the client synced it. The script reads it 2 s later. docs/internals.md records the results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- lua/prelude.lua: the module sandbox (plan B6). load, loadfile, dofile and require accept text only; loadfile()/dofile() never read the console; package.cpath is empty, package.loadlib and the C searchers explain why C modules cannot load from disk. - require "http": non-blocking HTTP(S) over the libcurl multi interface, pumped once per frame, callbacks on the main thread after collecting the finished transfers. Peer and host name always verified; CA from http.cafile, else the Windows store, the distribution bundle or an embedded Mozilla bundle (cmake/deps/cacert.cmake). Pending requests are dropped without callbacks on reload and shutdown. - Hash: v1's digests unchanged, plus OpenSSL hmac, pbkdf2, scrypt, randomBytes, toHex and a constant-time equals; also require "hash". - require "sql": sql.format(conn, "...?...", ...) on top of conn:escape. - The frame pump runs timers, HTTP, then one copas.step(0) and sets copas.running. - Unit tests for all of the above (HTTP against a Copas server in the same state), extra host smoke checks, and tests/integration/smoke: the real VC:MP server against Postgres 17, MySQL 8.4, MariaDB 11 and a local TLS server (CRUD, 1,000 inserts in one frame per database, timers, entities, untrusted CA, custom CA via config, wrong host name, 100 concurrent requests, reload, clean shutdown). The MySQL gate services moved to the "gate" compose profile; CI runs the smoke test when the repository variables VCMP_SERVER_URL and VCMP_SERVER_SHA256 are set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- destroy() on a vehicle, object, pickup, checkpoint, blip or bind that the server refuses to delete keeps the handle alive and returns false; bind:destroy() now returns a boolean like the others. - Bind.create raises when the server refuses to register the bind, instead of returning a handle to an unregistered slot. - A received Stream larger than 4096 bytes no longer accepts writes (the free-space check wrapped around). - Vehicle.create rejects a non-number angle in the position table. - vehicle:repair() reports a refused step; player:playSound and playSound3D return the server's answer. - Server.wastedSettings range-checks its timer fields instead of reading them as colours. - LuaSQL SQLite: cursor:fetch(t, "a") on a query with no rows returned a table of NULLs; it now returns nil (patch, upstream candidate). The fake server now honours fake.error() for functions it replaces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docs/api: reference for every binding and built-in module, and docs/configuration.md for luaconfig.lua. - docs/MIGRATION-v2.md: installing, luaconfig.ini to luaconfig.lua, Lanes, and links to the reference. - examples/: a luaconfig.lua and three scripts (commands, accounts with PBKDF2 hashes in SQLite, a JSON webhook over http). tests/integration/examples runs them in the real server. - release.yml: a v* tag that matches the project version runs CI, packs one zip per platform (plugin, luaconfig.lua, LICENSE, notices) with SHA256SUMS, and drafts a GitHub release. ci.yml is now reusable. - README rewritten for users. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments only: removes references to the private rebuild plan, v1 bug history (now in docs/MIGRATION-v2.md), section banners and comments that restate the code. docs/internals.md reads as a standalone document. The real-client test no longer calls itself "phase 3" in game. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The repository already has releases 2.0 Beta to 2.8 Beta, so the rebuild is 3.0.0. User-facing text calls the old plugin 2.x and the new one 3.0: the migration guide is now docs/MIGRATION-v3.md, and the removed globals say "removed in 3.0". removed_v1_stubs.cpp is now removed_globals.cpp. THIRD_PARTY_LICENSES.txt holds the license texts of everything linked into the plugin, written by ci/collect-licenses.py from a build tree. Release zips include it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 of 3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
VCMP-Lua 3.0 is a rebuild of the plugin. Scripts can no longer crash the server, the libraries are built in, and each platform gets a single binary with nothing else to install.
Why
The 2.x plugin had problems that a patch could not fix:
.sohad unresolved symbols, and the Windows DLL needed extra DLLs.What changed
noexcept. Events use stable dispatch, and cancel applies to the innermost event. Timers use 64-bit time. Arguments are checked, and errors read like Lua's own."vehicle no longer exists"). The same entity is always the same handle, so==and table keys work.require:httpmodule over libcurl;Hash(the 2.x digests plus PBKDF2, scrypt, HMAC and random bytes);sql.format.luaconfig.luareplacesluaconfig.ini.VcmpPluginInitand needs only system libraries, which CI checks.docs/api),docs/configuration.md, the migration guide (docs/MIGRATION-v3.md),docs/internals.mdandexamples/.v*tag builds and tests both platforms and drafts a release with one zip per platform plusSHA256SUMS.Breaking changes
All of them are listed in
docs/MIGRATION-v3.md. The main ones:luaconfig.inibecomesluaconfig.lua.:destroy(), not until the garbage collector runs.MySQL,SQLite,Remote,JSON,Thread, Lanes anddbgare gone. Using one raises an error that names its replacement.Before merging
v3.0.0tag.v3.0.0release.🤖 Generated with Claude Code