summaryrefslogtreecommitdiff
path: root/client.ha
diff options
context:
space:
mode:
authorLassi Pulkkinen <lassi@pulk.fi>2024-10-31 03:11:21 +0200
committerLassi Pulkkinen <lassi@pulk.fi>2024-10-31 03:51:35 +0200
commitae44478b30d890fe0fb04022f44d474dcdcc3f9d (patch)
tree5f462459ae4b47d22114eed717d1382d08cf4dfe /client.ha
Initial commit (import old repo)HEADmain
Diffstat (limited to 'client.ha')
-rw-r--r--client.ha239
1 files changed, 239 insertions, 0 deletions
diff --git a/client.ha b/client.ha
new file mode 100644
index 0000000..d5093b3
--- /dev/null
+++ b/client.ha
@@ -0,0 +1,239 @@
+use gl::*;
+use glm;
+use io;
+use mcproto;
+use net;
+use net::dial;
+use net::ip;
+use net::tcp;
+use strings;
+use time;
+use trace;
+use unix::poll;
+use rt;
+
+def TCP_NODELAY: int = 1; // XXX: linux
+
+def PROTO_VER = 761i32; // TODO
+
+type ClientState = enum u8 {
+ IDLE,
+ CONNECT,
+ LOGIN,
+ JOIN,
+ GAME,
+};
+
+let CLIENT_STATE = ClientState::IDLE;
+
+let CLIENT_ADDR = "";
+let CLIENT_USERNAME = "";
+
+let CLIENT_SOCK: net::socket = -1;
+// TODO: use tagged union once possible
+let CLIENT_RECV: nullable *Receiver = null;
+let CLIENT_RBUF: [0x1fffff]u8 = [0...];
+
+fn client_connect(addr: str, username: str) void = {
+ trace::info(&trace::root, "connecting to {}...", addr);
+
+ // TODO: async dns?
+ let (addrs, port) = match (dial::resolve("tcp", addr, "minecraft")) {
+ case let res: ([]ip::addr, u16) =>
+ yield res;
+ case let err: dial::error =>
+ die("resolve {}: {}", addr, dial::strerror(err));
+ };
+
+ // TODO: try secondary addresses
+ trace::info(&trace::root, "trying {}:{}...",
+ ip::string(addrs[0]), port);
+ match (tcp::connect(addrs[0], port, net::sockflag::NONBLOCK)) {
+ case let sock: net::socket =>
+ // TODO: error handling;
+ // maybe don't use rt module (if there was an option)
+ rt::setsockopt(sock, rt::IPPROTO_TCP, TCP_NODELAY, &1i,
+ size(int): u32)!;
+
+ CLIENT_STATE = ClientState::CONNECT;
+ CLIENT_SOCK = sock;
+ CLIENT_ADDR = strings::dup(addr);
+ CLIENT_USERNAME = strings::dup(username);
+ case let err: net::error =>
+ die("connect {}: {}", addr, net::strerror(err));
+ };
+};
+
+fn client_destroy() void = {
+ match (io::close(CLIENT_SOCK)) {
+ case void => void;
+ case let err: io::error =>
+ trace::error(&trace::root, "failed to close socket: {}",
+ io::strerror(err)): void;
+ };
+
+ match (CLIENT_RECV) {
+ case let recv: *Receiver =>
+ recv_finish(CLIENT_RECV as *Receiver);
+ free(CLIENT_RECV);
+ CLIENT_RECV = null;
+ case null => void;
+ };
+
+ if (CLIENT_STATE == ClientState::GAME) {
+ game_destroy();
+ };
+
+ free(CLIENT_ADDR);
+ CLIENT_ADDR = "";
+ free(CLIENT_USERNAME);
+ CLIENT_USERNAME = "";
+ CLIENT_STATE = ClientState::IDLE;
+};
+
+fn client_frame() void = {
+ const t0 = time::now(time::clock::MONOTONIC);
+ client_update_connection();
+ const t1 = time::now(time::clock::MONOTONIC);
+ //trace::debug(&trace::root, "client_update_connection took {} µs",
+ // time::diff(t0, t1) / 1000);
+
+ if (CLIENT_STATE == ClientState::GAME) {
+ game_frame();
+ };
+};
+
+fn client_update_connection() void = {
+ for (true) switch (CLIENT_STATE) {
+ case ClientState::IDLE =>
+ abort();
+ case ClientState::CONNECT =>
+ let fds = [poll::pollfd {
+ fd = CLIENT_SOCK,
+ events = poll::event::POLLOUT,
+ ...
+ }];
+ match (poll::poll(fds, 0)) {
+ case let n: uint =>
+ if (n == 0) {
+ return;
+ };
+ case let err: poll::error =>
+ die("poll: {}", poll::strerror(err));
+ };
+
+ let err: int = 0;
+ rt::getsockopt(CLIENT_SOCK, rt::SOL_SOCKET, rt::SO_ERROR,
+ &err, &(size(int): u32))!;
+ if (err != 0) {
+ die("connect {}: {}", CLIENT_ADDR, rt::strerror(err));
+ };
+
+ trace::info(&trace::root, "connection established");
+
+ CLIENT_RECV = alloc(newrecv(0x200000));
+
+ CLIENT_STATE = ClientState::LOGIN;
+ login_start();
+ case ClientState::LOGIN,
+ ClientState::JOIN,
+ ClientState::GAME =>
+ const recv = CLIENT_RECV as *Receiver;
+ match (recv_poll(recv, CLIENT_SOCK, CLIENT_RBUF)) {
+ case let length: size =>
+ let payload = CLIENT_RBUF[..length];
+ match (client_handle_packet(payload)) {
+ case void => void;
+ case trace::failed =>
+ die("invalid packet");
+ };
+ case void =>
+ return;
+ };
+ };
+};
+
+fn client_handle_packet(payload: []u8)
+ (void | trace::failed) = {
+ const tr = trace::ctx(&trace::root, "handle packet");
+ const dec = mcproto::Decoder {
+ input = payload,
+ pos = 0,
+ tracer = &tr,
+ };
+ const ctx = mcproto::root(&dec);
+ const ctx_ = mcproto::context(&ctx, "packet id");
+ const packet_id = mcproto::decode_varint(&ctx_)?;
+ switch (CLIENT_STATE) {
+ case ClientState::IDLE, ClientState::CONNECT =>
+ abort();
+ case ClientState::LOGIN =>
+ login_handle_packet(&ctx, packet_id)?;
+ case ClientState::JOIN, ClientState::GAME =>
+ switch (packet_id) {
+ case 0x1f =>
+ const ctx_ = mcproto::context(&ctx, "0x1f keep alive");
+ handle_keep_alive(&ctx_)?;
+ case 0x24 =>
+ if (CLIENT_STATE == ClientState::GAME) {
+ game_destroy();
+ CLIENT_STATE = ClientState::JOIN;
+ };
+ const ctx_ = mcproto::context(&ctx, "0x24 login");
+ handle_login(&ctx_)?;
+ CLIENT_STATE = ClientState::GAME;
+ case =>
+ if (CLIENT_STATE == ClientState::JOIN) {
+ return mcproto::error(&ctx, "Login packet expected");
+ };
+ game_handle_packet(&ctx, packet_id)?;
+ };
+ };
+};
+
+fn network_send(packet_id: i32, payload: []u8) void = {
+ match (mcproto::write_packet(CLIENT_SOCK, packet_id, payload)) {
+ case void => void;
+ case let err: io::error =>
+ die("network: error: write: {}", io::strerror(err));
+ };
+};
+
+const LAYER_CLIENT_WAITING = Layer {
+ blocks_input = &layer_client_waiting_blocks_input,
+ input = &layer_client_waiting_input,
+ is_opaque = &layer_client_waiting_is_opaque,
+ render = &layer_client_waiting_render,
+};
+
+fn client_waiting_status() (str | void) = {
+ switch (CLIENT_STATE) {
+ case ClientState::CONNECT =>
+ return "Connecting to the server...";
+ case ClientState::LOGIN =>
+ return "Logging in...";
+ case ClientState::JOIN =>
+ return "Joining world...";
+ case ClientState::IDLE, ClientState::GAME => void;
+ };
+};
+
+fn layer_client_waiting_blocks_input() bool = {
+ return client_waiting_status() is str;
+};
+
+fn layer_client_waiting_input(event: InputEvent) bool = {
+ return false;
+};
+
+fn layer_client_waiting_is_opaque() bool = {
+ return client_waiting_status() is str;
+};
+
+fn layer_client_waiting_render() void = {
+ match (client_waiting_status()) {
+ case let status: str =>
+ render_wait_screen(status);
+ case void => void;
+ };
+};