initial commit

This commit is contained in:
zongor 2023-05-15 19:01:52 -04:00
parent 50b67e7b33
commit 08d9e7680a
3 changed files with 197 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.DS_Store
all.sh
zig-cache/
zig-out/

35
build.zig Normal file
View File

@ -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);
}

158
src/main.zig Normal file
View File

@ -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);
}
}