diff --git a/client/js/compatibility/utils.js b/client/js/compatibility/utils.js
index bc8a4454c..9b69a2132 100644
--- a/client/js/compatibility/utils.js
+++ b/client/js/compatibility/utils.js
@@ -5,6 +5,8 @@
/** @type {typeof import("./../../../shared/js/utils.js")} */
const { assert, waitFor, assertRGBA, assertVector2, assertVector3 } = requireBinding("shared/utils.js");
+const { SharedUtils } = requireBinding("shared/compatibility/utils.js");
+
requireBinding("shared/timers.js");
requireBinding("shared/classes/rgba.js");
requireBinding("shared/classes/vector.js");
@@ -198,7 +200,7 @@ class Marker {
}
}
-class Utils {
+class Utils extends SharedUtils {
static async requestModel(model, timeout = 5000) {
assert(typeof model === "string" || typeof model === "number", "Expected a string or number as first argument");
assert(typeof timeout === "number", "Expected a number as second argument");
diff --git a/shared/js/compatibility/classes/resource.js b/shared/js/compatibility/classes/resource.js
new file mode 100644
index 000000000..a9bc596b4
--- /dev/null
+++ b/shared/js/compatibility/classes/resource.js
@@ -0,0 +1,18 @@
+///
+///
+///
+// import * as alt from "@altv/server";
+
+// requireBinding("shared/entity.js");
+
+class Resource extends alt.Resource {
+ get valid() {
+ return !!alt.Resource.get(super.name);
+ }
+
+ static getByName(name) {
+ return alt.Resource.get(name);
+ }
+}
+
+cppBindings.registerCompatibilityExport("Resource", Resource);
diff --git a/shared/js/compatibility/utils.js b/shared/js/compatibility/utils.js
new file mode 100644
index 000000000..4cca6c1c0
--- /dev/null
+++ b/shared/js/compatibility/utils.js
@@ -0,0 +1,84 @@
+///
+///
+///
+// import * as alt from "@altv/server";
+
+/** @type {typeof import("./../../../shared/js/utils.js")} */
+const { assertIsType } = requireBinding("shared/utils.js");
+
+requireBinding("shared/events/console.js");
+
+class Timer {
+ constructor(callback, ms, once) {
+ assertIsType(callback, "function");
+ assertIsType(ms, "number");
+
+ if (once) return new alt.Timers.setTimeout(callback, ms);
+ return new alt.Timers.setInterval(callback, ms);
+ }
+}
+
+class ConsoleCommand {
+ static registeredCommands = new Map();
+
+ #commandName;
+ #callback;
+
+ constructor(name, callback) {
+ assertIsType(name, "string");
+ assertIsType(callback, "function");
+
+ name = name.toLowerCase();
+
+ this.#commandName = name;
+ this.#callback = callback;
+
+ const handlers = ConsoleCommand.registeredCommands.get(name) ?? [];
+ handlers.push(callback);
+ ConsoleCommand.registeredCommands.set(name, handlers);
+ }
+
+ get callback() {
+ return this.#callback;
+ }
+
+ destroy() {
+ const registeredCommands = (ConsoleCommand.registeredCommands.get(this.#commandName) ?? []).filter((command) => command != this);
+
+ if (registeredCommands.length) ConsoleCommand.registeredCommands.set(this.#commandName, registeredCommands);
+ else Keybind.registeredHandlers.delete(this.keyCode);
+ }
+}
+
+alt.Events.onConsoleCommand(({ command, args }) => {
+ command = command.toLowerCase();
+
+ if (!ConsoleCommand.registeredCommands.has(command)) return;
+
+ const handlers = ConsoleCommand.registeredCommands.get(command);
+
+ for (const handler of handlers) {
+ handler?.(...args);
+ }
+});
+
+export class SharedUtils {
+ static wait = alt.Utils.wait;
+ static waitFor = alt.Utils.waitFor;
+
+ static inspect = alt.Utils.inspect;
+ static assert = alt.Utils.assert;
+
+ static ConsoleCommand = ConsoleCommand;
+
+ static Timer = Timer;
+ static Timeout = alt.Timers.Timeout;
+ static Interval = alt.Timers.Interval;
+ static NextTick = alt.Timers.NextTick;
+ static EveryTick = alt.Timers.EveryTick;
+}
+
+if (alt.isServer) {
+ // NOTE (xLuxy): Server has no Utilities
+ cppBindings.registerCompatibilityExport("Utils", SharedUtils);
+}