From 08d9e7680ac538c73d4b0c1aef2feff658f2dd0e Mon Sep 17 00:00:00 2001 From: zongor Date: Mon, 15 May 2023 19:01:52 -0400 Subject: [PATCH] initial commit --- .gitignore | 4 ++ build.zig | 35 ++++++++++++ src/main.zig | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 src/main.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e5e097f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +all.sh +zig-cache/ +zig-out/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..d29630c --- /dev/null +++ b/build.zig @@ -0,0 +1,35 @@ +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("minecraft_server_zig", "src/main.zig"); + exe.use_stage1 = true; // needed for zig v0.10 + + exe.setTarget(target); + exe.setBuildMode(mode); + exe.install(); + + const run_cmd = exe.run(); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + const exe_tests = b.addTest("src/main.zig"); + exe_tests.setTarget(target); + exe_tests.setBuildMode(mode); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&exe_tests.step); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..2c460dd --- /dev/null +++ b/src/main.zig @@ -0,0 +1,158 @@ +const std = @import("std"); +const net = std.net; +const mem = std.mem; + +const Queue = std.atomic.Queue; +const ArenaAllocator = std.heap.ArenaAllocator; + +pub const io_mode = .evented; + +const json = "{'version':{'name':'The Shadow?','protocol':762},'players':{'max':25,'online':0,'sample':[]},'description':{'text':'Minecraft Server in Zig'}"; + +const SEGMENT_BITS: i32 = 0x7F; +const CONTINUE_BIT: i32 = 0x80; + +fn varint2int(buf: []u8) struct { val: i32, bytes: usize } { + var i: usize = 0; + var value: i32 = 0; + var position: u5 = 0; + var currentByte: u8 = undefined; + + while (true) { + currentByte = buf[i]; + value |= (currentByte & SEGMENT_BITS) << position; + + if ((currentByte & CONTINUE_BIT) == 0) break; + + i += 1; + position += 7; + + if (position >= 32) unreachable; + } + + return .{ .val = value, .bytes = i + 1 }; +} + +fn int2varint(stream: net.Stream, value: i32) void { + while (true) { + if ((value & ~SEGMENT_BITS) == 0) { + stream.write(value); + return; + } + + stream.write((value & @as(i32, SEGMENT_BITS)) | @as(i32, CONTINUE_BIT)); + + value >>= 7; + } +} + +pub fn main() !void { + var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; + var allocator = general_purpose_allocator.allocator(); + + var server = net.StreamServer.init(.{ .reuse_address = true }); + defer server.deinit(); + + var world = World{ .clients = std.AutoHashMap(*Client, void).init(allocator) }; + + try server.listen(net.Address.parseIp("0.0.0.0", 25565) catch unreachable); + std.log.info("minecraft server hosted at {}\n", .{server.listen_address}); + + var cleanup = &Queue(*ArenaAllocator).init(); + _ = async cleaner(cleanup); + + while (true) { + var client_arena = ArenaAllocator.init(allocator); + const client = try client_arena.allocator().create(Client); + client.* = Client{ + .stream = (try server.accept()).stream, + .handle_frame = async client.handle(&world, cleanup, &client_arena), + }; + try world.clients.putNoClobber(client, {}); + } +} + +const Client = struct { + stream: net.Stream, + handle_frame: @Frame(handle), + login_state: 0, + input_buffer: [2097151]u8, + output_buffer: [2097151]u8, + + fn handle(self: *Client, world: *World, cleanup: *Queue(*ArenaAllocator), arena: *ArenaAllocator) !void { + _ = world; + const stream = self.stream; + defer { + stream.close(); + var node = Queue(*ArenaAllocator).Node{ .data = arena, .next = undefined, .prev = undefined }; + cleanup.put(&node); + } + + while (true) { + var ptr: usize = 0; + const msg_size = try stream.read(self.input_buffer[0..]); + std.log.info("{d}", .{msg_size}); + const size = varint2int(self.input_buffer[0..msg_size]); + ptr = size.bytes; + std.log.info("{d}", .{size.val}); + const packet_id = varint2int(self.input_buffer[ptr..@intCast(usize, size.val)]); + std.log.info("{d}", .{packet_id.val}); + ptr += packet_id.bytes; + switch (packet_id.val) { + 0x00 => { + switch (self.login_state) { + 0 => { + const protocol_version = varint2int(self.input_buffer[ptr..msg_size]); + std.log.info("protocol_version={d}", .{protocol_version.val}); + ptr += protocol_version.bytes; + const server_address_len = varint2int(self.input_buffer[ptr..msg_size]); + ptr += server_address_len.bytes; + std.log.info("server_address={s}", .{self.input_buffer[ptr .. ptr + @intCast(usize, server_address_len.val)]}); + ptr += @intCast(u64, server_address_len.val); + const u16size = @intCast(usize, @sizeOf(u16)); + const server_port = mem.nativeToBig(i16, mem.bytesAsSlice(i16, self.input_buffer[ptr .. ptr + u16size])[0]); + std.log.info("server_port{d}", .{server_port}); + ptr += @sizeOf(u16); + const next_state = varint2int(self.input_buffer[ptr..msg_size]); + std.log.info("next_state={d}", .{next_state.val}); + self.login_state = next_state.val; + }, + 1 => { + var fbs = std.io.fixedBufferStream(&self.output_buffer); + _ = fbs; + }, + 2 => {}, + else => {}, + } + }, + 0x01 => {}, + else => {}, + } + + //world.broadcast(buf[0..n], self); + } + } +}; + +const World = struct { + clients: std.AutoHashMap(*Client, void), + + fn broadcast(world: *World, msg: []const u8, sender: *Client) void { + var it = world.clients.keyIterator(); + while (it.next()) |key_ptr| { + const client = key_ptr.*; + if (client == sender) continue; + _ = client.stream.write(msg) catch |e| std.log.warn("unable to send: {}\n", .{e}); + } + } +}; + +fn cleaner(cleanup: *Queue(*ArenaAllocator)) !void { + while (true) { + while (cleanup.get()) |node| { + node.data.deinit(); // the client arena allocator + } + // 5 seconds (in nano seconds) + std.time.sleep(5000000000); + } +}