initial commit
This commit is contained in:
parent
50b67e7b33
commit
08d9e7680a
|
@ -0,0 +1,4 @@
|
||||||
|
.DS_Store
|
||||||
|
all.sh
|
||||||
|
zig-cache/
|
||||||
|
zig-out/
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue