Skip to main content

Running a full RAGE:MP clientside (CEF UI + client JS)

Alongside the server, SYNC MP runs an unmodified RAGE:MP clientside — the browser-based UI (CEF), the client_packages JavaScript entry (index.js / a webpack main.js bundle), and the mp.* client API the gamemode expects. A large production clientside (a 1+ MB webpack bundle, 1000+ event handlers, dozens of CEF views) loads and its interface renders in-game with no source changes.

How it works​

The SYNC MP client is a DLL injected into GTA5.exe. It provides:

  1. A V8 JavaScript runtime that runs the gamemode's downloaded client_packages entry, exposing the RAGE:MP mp.* client surface (mp.events, mp.game.*, mp.players, mp.vehicles, mp.gui, mp.browsers, timers, promises, …).
  2. A CEF (Chromium) browser rendered off-screen and composited into the game frame, driving the gamemode's HTML/Svelte/React UI (package://interface/…).
  3. A native invoker (ScriptHookV-style) so mp.game.invoke(hash, …) and the generated mp.game.<namespace>.<fn>(…) wrappers call real GTA natives.

The client downloads the server's client_packages on connect (with a manifest diff so only changed files transfer), runs the entry, and the gamemode registers its handlers and opens its CEF views.

The client mp.* API​

The full RAGE:MP client mp.* surface is provided. Highlights:

  • mp.events — add / call / callRemote / callRemoteProc / addProc / addDataHandler (per-key synced-data handlers). The raw registry is also exposed as mp._events so frameworks that register via mp._events.add work.
  • mp.game.* — the full generated native table, grouped into RAGE:MP namespaces (mp.game.graphics, mp.game.ui, mp.game.controls, mp.game.gameplay, mp.game.cam, mp.game.streaming, mp.game.player, mp.game.entity, …). GTA's raw native namespaces are remapped to RAGE:MP names (HUD → ui, PAD → controls, CAMERA → cam/graphics, MISC → gameplay/system, CLOCK → time, …), and out-pointer natives (e.g. mp.game.graphics.getScreenResolution()) return their results.
  • Entity pools — mp.players / mp.vehicles / mp.peds / mp.objects / mp.blips / mp.markers / mp.checkpoints / mp.colshapes / mp.labels / mp.browsers / mp.cameras are iterable (forEach / at / atRemoteId / atHandle / exists / toArray / length) and have the RAGE:MP constructors (mp.colshapes.newSphere/newCircle/newTube/newRectangle/newCuboid, mp.cameras.new(...), …).
  • Entity objects — mp.players.local and remote players/peds carry the RAGE:MP method set (freezePosition, setAlpha, setVisible, setHeading, setComponentVariation, setRelationshipGroupHash, hasClearLosTo, getHeading/getArmour/getHealth/getCoords/ getModel/getVehicle, …). Spawned entities from mp.peds.new / mp.objects.new carry the common ped/object methods (setPropIndex, clearProp, setLodDist, …).
  • mp.gui — mp.gui.chat (push/clear/activate/show), mp.gui.cursor (.show(...) and the .visible property), and mp.gui.emmit(js) — the general "run this JS in the browser" bridge the gamemode uses to drive its UI (window.router.setView(...), window.FadeScreen(...), window.events.callEvent(...)).
  • mp.browsers.new(url) — opens the CEF view. package://interface/<page>.html resolves against the downloaded client_packages; external http(s):// URLs are kept off the main UI browser.

The CEF UI bridge​

Inside a CEF page the gamemode uses a window.mp object (window.mp.trigger, window.mp.events, window.mp.invoke) to talk back to the client. That bridge is available to your interface pages; messages route to the client's event system, and the client drives the page with mp.gui.emmit(...) (client → page) which executes JS in the browser.

When the mouse cursor is shown (mp.gui.cursor.visible = true) the CEF overlay takes input focus, so keyboard and mouse reach the page (login forms, menus) — matching RAGE:MP, where a visible cursor means the UI has focus.

Serving your interface​

Your interface files live under client_packages/interface/. A page that loads its bundle with a plain tag —

<link rel="stylesheet" href="build/bundle.css">
<script src="build/bundle.js"></script>

— loads reliably from the downloaded package. Pages that fetch their bundle from a remote CDN will only work if that CDN is reachable from the player; ship the built bundle with your client_packages for an offline-capable UI.

The join → UI-ready flow​

The client loads the world, spawns, and sends CLIENT_READY; the server then fires the gamemode's playerJoin, which drives the client into its UI (auth / character / spawn). Server → client events fire reliably even under a heavy clientside load, and a join event that arrives before the clientside finished registering its handlers is deferred and replayed once the entry has run — so the gamemode's first UI event (e.g. the login screen trigger) is never dropped to a race.

Debugging the CEF UI​

Set the client marker file next to the client (ALTV_CEF_DEVTOOLS) or the env var ALTV_CEF_DEVTOOLS=1 to open Chromium DevTools on http://localhost:9222. From a Chromium browser on the same machine, http://localhost:9222/json/list lists the inspectable pages; open the page's inspector to see its console, DOM, and network — the fastest way to tell whether your UI bundle mounted, errored, or is waiting on an event.

Known limitations (in progress)​

  • A mp.game.<namespace>.<fn> that maps to a native not present in the running table is a hard error, so the client only exposes wrappers for real natives; RAGE:MP-aliased names not covered by the generator are added as needed.
  • Native network session features (a real objMgr for CNetObjPed cloning) are not up, so remote peds animate via a velocity/move-blend drive rather than the engine's net blender.
  • Some UI flows that depend on gamemode-specific browser objects being registered in a particular order can log non-fatal window.<x> is not a function messages until that object exists; these do not block the interface.