diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/class.zig | 96 | ||||
-rw-r--r-- | src/client.zig | 12 | ||||
-rw-r--r-- | src/engine.zig | 11 | ||||
-rw-r--r-- | src/gameconsole.zig | 81 | ||||
-rw-r--r-- | src/hook.zig | 0 | ||||
-rw-r--r-- | src/interface.zig | 2 | ||||
-rw-r--r-- | src/interfaces/PluginCallbacks001.zig | 23 | ||||
-rw-r--r-- | src/main.zig | 8 | ||||
-rw-r--r-- | src/methods/execute_command.zig | 1 | ||||
-rw-r--r-- | src/methods/list_methods.zig | 4 | ||||
-rw-r--r-- | src/northstar.zig | 8 | ||||
-rw-r--r-- | src/rpc_server.zig (renamed from src/server.zig) | 94 | ||||
-rw-r--r-- | src/sys.zig | 12 |
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); } |