aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/class.zig96
-rw-r--r--src/client.zig12
-rw-r--r--src/engine.zig11
-rw-r--r--src/gameconsole.zig81
-rw-r--r--src/hook.zig0
-rw-r--r--src/interface.zig2
-rw-r--r--src/interfaces/PluginCallbacks001.zig23
-rw-r--r--src/main.zig8
-rw-r--r--src/methods/execute_command.zig1
-rw-r--r--src/methods/list_methods.zig4
-rw-r--r--src/northstar.zig8
-rw-r--r--src/rpc_server.zig (renamed from src/server.zig)94
-rw-r--r--src/sys.zig12
13 files changed, 231 insertions, 121 deletions
diff --git a/src/class.zig b/src/class.zig
index de8002e..93a0304 100644
--- a/src/class.zig
+++ b/src/class.zig
@@ -21,7 +21,7 @@ pub fn Class(comptime base: anytype, comptime members: anytype) type {
const ClassStruct = @Type(.{
.Struct = .{
- .layout = .Extern,
+ .layout = .@"extern",
.fields = comptime blk: {
var class_fields = struct {
fields: []const StructField = &[_]StructField{},
@@ -31,7 +31,7 @@ pub fn Class(comptime base: anytype, comptime members: anytype) type {
.name = "vtable",
.type = @Type(.{
.Struct = .{
- .layout = .Extern,
+ .layout = .@"extern",
.fields = &[_]StructField{
field,
},
@@ -52,17 +52,23 @@ pub fn Class(comptime base: anytype, comptime members: anytype) type {
} else null;
if (vtable_index) |i| {
- const old_vtable_pointer = self.fields[i];
- const OldVTablePointerType = old_vtable_pointer.type;
+ const old_vtable = self.fields[i];
+ const OldVTableType = old_vtable.type;
+ const old_vtable_type_info = @typeInfo(OldVTableType);
+ if (old_vtable_type_info != .Optional) {
+ @compileError("expected vtable to be optional, found " ++ @typeName(OldVTableType));
+ }
+
+ const OldVTablePointerType = old_vtable_type_info.Optional.child;
const old_vtable_pointer_type_info = @typeInfo(OldVTablePointerType);
if (old_vtable_pointer_type_info != .Pointer) {
- @compileError("expected vtable to be pointer, found " ++ @typeName(OldVTablePointerType));
+ @compileError("expected vtable to be optional pointer, found " ++ @typeName(OldVTableType));
}
- const OldVTableType = old_vtable_pointer_type_info.Pointer.child;
- const old_vtable_type_info = @typeInfo(OldVTableType);
- if (old_vtable_type_info != .Struct or old_vtable_type_info.Struct.is_tuple != false) {
- @compileError("expected vtable to be pointer to struct, found pointer to " ++ @typeName(OldVTableType));
+ const OldVTableStructType = old_vtable_pointer_type_info.Pointer.child;
+ const old_vtable_struct_type_info = @typeInfo(OldVTableStructType);
+ if (old_vtable_struct_type_info != .Struct or old_vtable_struct_type_info.Struct.is_tuple != false) {
+ @compileError("expected vtable to be optional pointer to struct, found pointer to " ++ @typeName(OldVTableType));
}
const FieldType = field.type;
@@ -73,30 +79,34 @@ pub fn Class(comptime base: anytype, comptime members: anytype) type {
self.fields = &[_]StructField{
.{
- .name = old_vtable_pointer.name,
+ .name = old_vtable.name,
.type = @Type(.{
- .Pointer = .{
- .size = old_vtable_pointer_type_info.Pointer.size,
- .is_const = old_vtable_pointer_type_info.Pointer.is_const,
- .is_volatile = old_vtable_pointer_type_info.Pointer.is_volatile,
- .alignment = old_vtable_pointer_type_info.Pointer.alignment,
- .address_space = old_vtable_pointer_type_info.Pointer.address_space,
+ .Optional = .{
.child = @Type(.{
- .Struct = .{
- .layout = old_vtable_type_info.Struct.layout,
- .backing_integer = old_vtable_type_info.Struct.backing_integer,
- .fields = old_vtable_type_info.Struct.fields ++ field_type_info.Struct.fields,
- .decls = old_vtable_type_info.Struct.decls,
- .is_tuple = old_vtable_type_info.Struct.is_tuple,
+ .Pointer = .{
+ .size = old_vtable_pointer_type_info.Pointer.size,
+ .is_const = old_vtable_pointer_type_info.Pointer.is_const,
+ .is_volatile = old_vtable_pointer_type_info.Pointer.is_volatile,
+ .alignment = old_vtable_pointer_type_info.Pointer.alignment,
+ .address_space = old_vtable_pointer_type_info.Pointer.address_space,
+ .child = @Type(.{
+ .Struct = .{
+ .layout = old_vtable_struct_type_info.Struct.layout,
+ .backing_integer = old_vtable_struct_type_info.Struct.backing_integer,
+ .fields = old_vtable_struct_type_info.Struct.fields ++ field_type_info.Struct.fields,
+ .decls = old_vtable_struct_type_info.Struct.decls,
+ .is_tuple = old_vtable_struct_type_info.Struct.is_tuple,
+ },
+ }),
+ .is_allowzero = old_vtable_pointer_type_info.Pointer.is_allowzero,
+ .sentinel = old_vtable_pointer_type_info.Pointer.sentinel,
},
}),
- .is_allowzero = old_vtable_pointer_type_info.Pointer.is_allowzero,
- .sentinel = old_vtable_pointer_type_info.Pointer.sentinel,
},
}),
- .default_value = old_vtable_pointer.default_value,
- .is_comptime = old_vtable_pointer.is_comptime,
- .alignment = old_vtable_pointer.alignment,
+ .default_value = old_vtable.default_value,
+ .is_comptime = old_vtable.is_comptime,
+ .alignment = old_vtable.alignment,
},
} ++ self.fields[1..];
} else {
@@ -121,22 +131,26 @@ pub fn Class(comptime base: anytype, comptime members: anytype) type {
.{
.name = field.name,
.type = @Type(.{
- .Pointer = .{
- .size = .One,
- .is_const = true,
- .is_volatile = false,
- .alignment = 0,
- .address_space = .generic,
+ .Optional = .{
.child = @Type(.{
- .Struct = .{
- .layout = .Extern,
- .fields = field_type_info.Struct.fields,
- .decls = &.{},
- .is_tuple = false,
+ .Pointer = .{
+ .size = .One,
+ .is_const = true,
+ .is_volatile = false,
+ .alignment = 0,
+ .address_space = .generic,
+ .child = @Type(.{
+ .Struct = .{
+ .layout = .@"extern",
+ .fields = field_type_info.Struct.fields,
+ .decls = &.{},
+ .is_tuple = false,
+ },
+ }),
+ .is_allowzero = false,
+ .sentinel = null,
},
}),
- .is_allowzero = false,
- .sentinel = null,
},
}),
.default_value = null,
@@ -163,7 +177,7 @@ pub fn Class(comptime base: anytype, comptime members: anytype) type {
if (b_type_info != .Struct or b_type_info.Struct.is_tuple != false) {
@compileError("expected struct base, found " ++ @typeName(b));
- } else if (b_type_info.Struct.layout != .Extern) {
+ } else if (b_type_info.Struct.layout != .@"extern") {
@compileError("expected extern struct, found " ++ @tagName(b_type_info.Struct.layout));
}
diff --git a/src/client.zig b/src/client.zig
new file mode 100644
index 0000000..958e6ef
--- /dev/null
+++ b/src/client.zig
@@ -0,0 +1,12 @@
+const std = @import("std");
+const windows = std.os.windows;
+
+const interface = @import("interface.zig");
+
+pub var handle: ?windows.HMODULE = null;
+
+pub var create_interface: interface.GetInterfaceType = null;
+
+pub fn init(module: windows.HMODULE) void {
+ create_interface = @ptrCast(windows.kernel32.GetProcAddress(module, "CreateInterface"));
+}
diff --git a/src/engine.zig b/src/engine.zig
index e58c1f2..05caa11 100644
--- a/src/engine.zig
+++ b/src/engine.zig
@@ -1,3 +1,6 @@
+const std = @import("std");
+const windows = std.os.windows;
+
pub const ECommandTarget_t = enum(c_int) {
CBUF_FIRST_PLAYER = 0,
CBUF_LAST_PLAYER = 1, // MAX_SPLITSCREEN_CLIENTS - 1, MAX_SPLITSCREEN_CLIENTS = 2
@@ -48,3 +51,11 @@ pub const CbufType = struct {
};
pub var Cbuf: ?CbufType = null;
+
+pub fn init(module: windows.HMODULE) void {
+ Cbuf = .{
+ .GetCurrentPlayer = @ptrFromInt(@intFromPtr(module) + 0x120630),
+ .AddText = @ptrFromInt(@intFromPtr(module) + 0x1203B0),
+ .Execute = @ptrFromInt(@intFromPtr(module) + 0x1204B0),
+ };
+}
diff --git a/src/gameconsole.zig b/src/gameconsole.zig
new file mode 100644
index 0000000..1b7b8e8
--- /dev/null
+++ b/src/gameconsole.zig
@@ -0,0 +1,81 @@
+const std = @import("std");
+const windows = std.os.windows;
+
+const Class = @import("class.zig").Class;
+
+const interface = @import("interface.zig");
+const client = @import("client.zig");
+
+const CGameConsole = Class(.{}, .{
+ .unknown = .{ .type = void, .virtual = true },
+
+ .initialized = .{ .type = bool },
+ .console = .{ .type = ?*const CConsoleDialog },
+});
+
+const CConsoleDialog = Class(.{}, .{
+ .unknown = .{ .type = void, .virtual = true },
+
+ .padding = .{ .type = [0x398]u8 },
+ .console_panel = .{ .type = ?*const CConsolePanel },
+});
+
+const CConsolePanel = Class(.{}, .{
+ .editable_panel = .{ .type = EditablePanel },
+ .iconsole_display_func = .{ .type = IConsoleDisplayFunc },
+});
+
+const EditablePanel = Class(.{}, .{
+ .unknown = .{ .type = void, .virtual = true },
+
+ .padding = .{ .type = [0x2B0]u8 },
+});
+
+const IConsoleDisplayFunc = Class(.{}, .{
+ .color_print = .{ .type = *const fn (this: *anyopaque) callconv(.C) void, .virtual = true },
+ .print = .{ .type = *const fn (this: *anyopaque, message: [*:0]const u8) callconv(.C) void, .virtual = true },
+ .dprint = .{ .type = *const fn (this: *anyopaque, message: [*:0]const u8) callconv(.C) void, .virtual = true },
+});
+
+pub fn hook() void {
+ const client_create_interface = client.create_interface orelse {
+ std.log.err("Client CreateInterface not resolved, cannot hook game console", .{});
+ return;
+ };
+
+ var status: interface.InterfaceStatus = .IFACE_OK;
+ const pgame_console: ?*CGameConsole = @ptrCast(@alignCast(client_create_interface("GameConsole004", &status)));
+ if (pgame_console == null or status != .IFACE_OK) {
+ std.log.err("Failed to create GameConsole004 interface: {}", .{status});
+ return;
+ }
+ const game_console = pgame_console.?;
+
+ if (!game_console.initialized) {
+ std.log.warn("Game console is not initialized yet, waiting for it", .{});
+ while (!game_console.initialized) {}
+ std.log.info("Game console is now initialized", .{});
+ }
+
+ const display_func = blk: {
+ if (game_console.console) |console| {
+ if (console.console_panel) |console_panel| {
+ break :blk console_panel.iconsole_display_func;
+ }
+ }
+
+ break :blk null;
+ };
+
+ const print = blk: {
+ if (display_func) |func| {
+ if (func.vtable) |vtable| {
+ break :blk vtable.print;
+ }
+ }
+
+ break :blk null;
+ };
+
+ _ = print;
+}
diff --git a/src/hook.zig b/src/hook.zig
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/hook.zig
diff --git a/src/interface.zig b/src/interface.zig
index 530d8cf..a952562 100644
--- a/src/interface.zig
+++ b/src/interface.zig
@@ -3,6 +3,8 @@ const std = @import("std");
const PluginCallbacks001 = @import("interfaces/PluginCallbacks001.zig").plugin_interface;
const PluginId001 = @import("interfaces/PluginId001.zig").plugin_interface;
+pub const GetInterfaceType = ?*const fn ([*:0]const u8, ?*const InterfaceStatus) callconv(.C) *anyopaque;
+
pub const interfaces = .{
PluginCallbacks001,
PluginId001,
diff --git a/src/interfaces/PluginCallbacks001.zig b/src/interfaces/PluginCallbacks001.zig
index 17d687d..cd0f52c 100644
--- a/src/interfaces/PluginCallbacks001.zig
+++ b/src/interfaces/PluginCallbacks001.zig
@@ -1,14 +1,18 @@
const std = @import("std");
const windows = std.os.windows;
+const Thread = std.Thread;
+
const Class = @import("../class.zig").Class;
const interface = @import("../interface.zig");
const northstar = @import("../northstar.zig");
const squirrel = @import("../squirrel.zig");
const sys = @import("../sys.zig");
-const server = @import("../server.zig");
+const rpc_server = @import("../rpc_server.zig");
+const gameconsole = @import("../gameconsole.zig");
const engine = @import("../engine.zig");
+const client = @import("../client.zig");
const CSquirrelVM = squirrel.CSquirrelVM;
@@ -52,11 +56,11 @@ pub fn Init(self: *anyopaque, module: windows.HMODULE, data: *northstar.Northsta
sys.init();
if (reloaded != 0) {
- server.stop();
+ rpc_server.stop();
}
- if (!server.running) {
- server.start() catch std.log.err("Failed to start HTTP Server", .{});
+ if (!rpc_server.running) {
+ rpc_server.start() catch std.log.err("Failed to start HTTP Server", .{});
}
std.log.info("Loaded", .{});
@@ -69,7 +73,7 @@ pub fn Finalize(self: *anyopaque) callconv(.C) void {
pub fn Unload(self: *anyopaque) callconv(.C) bool {
_ = self;
- server.stop();
+ rpc_server.stop();
return true;
}
@@ -92,10 +96,11 @@ pub fn OnLibraryLoaded(self: *anyopaque, module: windows.HMODULE, name_ptr: [*:0
const name = std.mem.span(name_ptr);
if (std.mem.eql(u8, name, "engine.dll")) {
- engine.Cbuf = .{
- .GetCurrentPlayer = @ptrFromInt(@intFromPtr(module) + 0x120630),
- .AddText = @ptrFromInt(@intFromPtr(module) + 0x1203B0),
- .Execute = @ptrFromInt(@intFromPtr(module) + 0x1204B0),
+ engine.init(module);
+ } else if (std.mem.eql(u8, name, "client.dll")) {
+ client.init(module);
+ _ = Thread.spawn(.{}, gameconsole.hook, .{}) catch |err| {
+ std.log.err("Failed to hook GameConsole {}", .{err});
};
}
}
diff --git a/src/main.zig b/src/main.zig
index ef5efab..3a6a8c4 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -4,11 +4,9 @@ pub const PLUGIN_NAME = "Zig Plugin";
pub const LOG_NAME = "ZIGPLUGIN";
pub const DEPENDENCY_NAME = "ZigPlugin";
-pub const std_options = struct {
- // Set the log level to info
- pub const log_level = .info;
- // Define logFn to override the std implementation
- pub const logFn = @import("sys.zig").log;
+pub const std_options = .{
+ .logFn = @import("sys.zig").log,
+ .log_level = .info,
};
comptime {
diff --git a/src/methods/execute_command.zig b/src/methods/execute_command.zig
index c253738..add17e9 100644
--- a/src/methods/execute_command.zig
+++ b/src/methods/execute_command.zig
@@ -41,6 +41,7 @@ fn execute_command(allocator: Allocator, params: ?std.json.Value) !?std.json.Val
break :blk try allocator.dupeZ(u8, command_item.string);
};
+ defer allocator.free(command);
if (engine.Cbuf) |Cbuf| {
const cur_player = Cbuf.GetCurrentPlayer();
diff --git a/src/methods/list_methods.zig b/src/methods/list_methods.zig
index b396100..dfb8a95 100644
--- a/src/methods/list_methods.zig
+++ b/src/methods/list_methods.zig
@@ -1,7 +1,7 @@
const std = @import("std");
-const server = @import("../server.zig");
-const RpcMethods = server.RpcMethods;
+const rpc_server = @import("../rpc_server.zig");
+const RpcMethods = rpc_server.RpcMethods;
const Allocator = std.mem.Allocator;
diff --git a/src/northstar.zig b/src/northstar.zig
index 8c659a7..10fd21b 100644
--- a/src/northstar.zig
+++ b/src/northstar.zig
@@ -7,13 +7,11 @@ pub const NorthstarData = extern struct {
handle: ?windows.HMODULE,
};
-pub var data: NorthstarData = .{
- .handle = null,
-};
+pub var plugin_handle: ?windows.HMODULE = null;
-pub var create_interface: ?*const fn ([*:0]const u8, ?*const interface.InterfaceStatus) callconv(.C) *anyopaque = null;
+pub var create_interface: interface.GetInterfaceType = null;
pub fn init(ns_module: windows.HMODULE, init_data: *NorthstarData) void {
create_interface = @ptrCast(windows.kernel32.GetProcAddress(ns_module, "CreateInterface"));
- data.handle = init_data.*.handle;
+ plugin_handle = init_data.*.handle;
}
diff --git a/src/server.zig b/src/rpc_server.zig
index fe44e83..c1868f5 100644
--- a/src/server.zig
+++ b/src/rpc_server.zig
@@ -2,6 +2,7 @@ const std = @import("std");
const http = std.http;
const Server = http.Server;
+const NetServer = std.net.Server;
const Thread = std.Thread;
const max_header_size = 8192;
@@ -42,16 +43,21 @@ const JsonRpcResponse = struct {
id: ?std.json.Value = null,
};
-fn handleRequest(res: *Server.Response) !void {
- server_log.info("{s} {s} {s}", .{ @tagName(res.request.method), @tagName(res.request.version), res.request.target });
+fn handleRequest(request: *Server.Request) !void {
+ server_log.info("{s} {s} {s}", .{ @tagName(request.head.method), @tagName(request.head.version), request.head.target });
- if (!std.mem.startsWith(u8, res.request.target, "/rpc")) {
- res.status = .not_found;
- try res.do();
+ if (!std.mem.startsWith(u8, request.head.target, "/rpc")) {
+ try request.respond("not found", .{
+ .status = .not_found,
+ .extra_headers = &.{
+ .{ .name = "content-type", .value = "text/plain" },
+ },
+ });
return;
}
- const body = try res.reader().readAllAlloc(allocator, 8192);
+ const reader = try request.reader();
+ const body = try reader.readAllAlloc(allocator, 8192);
defer allocator.free(body);
const resp = blk: {
@@ -75,16 +81,16 @@ fn handleRequest(res: *Server.Response) !void {
};
defer parsed.deinit();
- var request = parsed.value;
+ var data = parsed.value;
- if (request.id) |request_id| {
+ if (data.id) |request_id| {
if (request_id != .integer and request_id != .string) {
- request.id = null;
+ data.id = null;
}
}
- response.id = request.id;
+ response.id = data.id;
- if (request.params) |request_params| {
+ if (data.params) |request_params| {
if (request_params != .object and request_params != .array) {
response.@"error" = .{
.code = -32602,
@@ -96,8 +102,8 @@ fn handleRequest(res: *Server.Response) !void {
}
inline for (RpcMethods) |method| {
- if (std.mem.eql(u8, method.name, request.method)) {
- response.result = method.func(allocator, request.params) catch |err| method_blk: {
+ if (std.mem.eql(u8, method.name, data.method)) {
+ response.result = method.func(allocator, data.params) catch |err| method_blk: {
response.@"error" = .{
.code = -32603,
.message = @errorName(err),
@@ -117,56 +123,36 @@ fn handleRequest(res: *Server.Response) !void {
};
const json_resp = try std.json.stringifyAlloc(allocator, resp, .{});
- res.transfer_encoding = .{ .content_length = json_resp.len };
+ defer allocator.free(json_resp);
- try res.do();
- try res.writeAll(json_resp);
- try res.finish();
+ try request.respond(json_resp, .{
+ .extra_headers = &.{
+ .{ .name = "content-type", .value = "application/json." },
+ },
+ });
}
-
-fn runServer(srv: *Server) !void {
- outer: while (running) {
- var res = try srv.accept(.{
- .allocator = allocator,
- .header_strategy = .{ .dynamic = max_header_size },
- });
- defer res.deinit();
-
- while (res.reset() != .closing) {
- res.wait() catch |err| switch (err) {
- error.HttpHeadersInvalid => continue :outer,
- error.EndOfStream => continue,
- else => return err,
+fn serverThread(addr: std.net.Address) !void {
+ var read_buffer: [8000]u8 = undefined;
+ var http_server = try addr.listen(.{});
+
+ accept: while (true) {
+ const connection = try http_server.accept();
+ defer connection.stream.close();
+
+ var server = std.http.Server.init(connection, &read_buffer);
+ while (server.state == .ready) {
+ var request = server.receiveHead() catch |err| {
+ std.debug.print("error: {s}\n", .{@errorName(err)});
+ continue :accept;
};
-
- try handleRequest(&res);
+ try handleRequest(&request);
}
}
}
-fn serverThread(addr: std.net.Address) !void {
- var server = Server.init(allocator, .{ .reuse_address = true });
-
- try server.listen(addr);
- defer server.deinit();
-
- defer _ = gpa.deinit();
-
- runServer(&server) catch |err| {
- server_log.err("server error: {}\n", .{err});
-
- if (@errorReturnTrace()) |trace| {
- std.debug.dumpStackTrace(trace.*);
- }
-
- _ = gpa.deinit();
- std.os.exit(1);
- };
-}
-
pub fn start() !void {
if (server_thread == null) {
- var addr = try std.net.Address.parseIp("127.0.0.1", 26505);
+ const addr = try std.net.Address.parseIp("127.0.0.1", 26505);
running = true;
server_thread = try Thread.spawn(.{}, serverThread, .{addr});
diff --git a/src/sys.zig b/src/sys.zig
index ea71ec2..6cb0864 100644
--- a/src/sys.zig
+++ b/src/sys.zig
@@ -37,7 +37,7 @@ pub fn log(
) void {
const scope_prefix = switch (scope) {
std.log.default_log_scope => "",
- else => "(" ++ @tagName(scope) ++ ")",
+ else => "(" ++ @tagName(scope) ++ ") ",
};
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
@@ -55,9 +55,11 @@ pub fn log(
const msg = std.fmt.allocPrintZ(allocator, scope_prefix ++ format, args) catch unreachable;
if (sys) |s| {
- s.vtable.log(s, northstar.data.handle, log_level, msg);
- } else {
- // Northstar log has not been established, fallback to default log
- std.log.defaultLog(level, scope, format, args);
+ if (s.vtable) |vtable| {
+ vtable.log(s, northstar.plugin_handle, log_level, msg);
+ return;
+ }
}
+
+ std.log.defaultLog(level, scope, format, args);
}