From a0c85883fb541f4b4daab0cf30b4ed2fa7e23262 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sat, 28 May 2022 15:00:35 +0200 Subject: Initial commit --- .gitignore | 1 + auth.go | 254 +++++++++++++++ builtin/vector.lua | 126 ++++++++ callbacks.go | 85 ++++++ client.go | 172 +++++++++++ convert.go | 882 +++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 10 + go.sum | 8 + hydra.go | 74 +++++ mkconvert.lua | 201 ++++++++++++ poll.go | 89 ++++++ spec/casemap | 23 ++ spec/client/enum | 70 +++++ spec/client/flag | 19 ++ spec/client/pkt | 276 +++++++++++++++++ spec/client/struct | 24 ++ types.go | 81 +++++ 17 files changed, 2395 insertions(+) create mode 100644 .gitignore create mode 100644 auth.go create mode 100644 builtin/vector.lua create mode 100644 callbacks.go create mode 100644 client.go create mode 100644 convert.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hydra.go create mode 100755 mkconvert.lua create mode 100644 poll.go create mode 100644 spec/casemap create mode 100644 spec/client/enum create mode 100644 spec/client/flag create mode 100644 spec/client/pkt create mode 100644 spec/client/struct create mode 100644 types.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83f0206 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +hydra diff --git a/auth.go b/auth.go new file mode 100644 index 0000000..3e9cb9b --- /dev/null +++ b/auth.go @@ -0,0 +1,254 @@ +package main + +import ( + "github.com/HimbeerserverDE/srp" + "github.com/Shopify/go-lua" + "github.com/anon55555/mt" + "strings" + "time" +) + +type authState uint8 + +const ( + asInit authState = iota + asRequested + asVerified + asActive + asError +) + +type Auth struct { + client *Client + username string + password string + language string + state authState + err string + srpBytesA, bytesA []byte +} + +func getAuth(l *lua.State) *Auth { + return lua.CheckUserData(l, 1, "hydra.auth").(*Auth) +} + +func (auth *Auth) create(client *Client) { + auth.client = client + auth.language = "en_US" + auth.state = asInit +} + +func (auth *Auth) push(l *lua.State) { + l.PushUserData(auth) + + if lua.NewMetaTable(l, "hydra.auth") { + lua.NewLibrary(l, []lua.RegistryFunction{ + {Name: "username", Function: l_auth_username}, + {Name: "password", Function: l_auth_password}, + {Name: "language", Function: l_auth_language}, + {Name: "state", Function: l_auth_state}, + }) + l.SetField(-2, "__index") + } + l.SetMetaTable(-2) +} + +func (auth *Auth) canConnect() (bool, string) { + if auth.username == "" { + return false, "missing username" + } + + return true, "" +} + +func (auth *Auth) connect() { + go func() { + for auth.state == asInit && auth.client.state == csConnected { + auth.client.conn.SendCmd(&mt.ToSrvInit{ + SerializeVer: 28, + MinProtoVer: 39, + MaxProtoVer: 39, + PlayerName: auth.username, + }) + time.Sleep(500 * time.Millisecond) + } + }() +} + +func (auth *Auth) setError(err string) { + auth.state = asError + auth.err = err + auth.client.conn.Close() +} + +func (auth *Auth) checkState(state authState, pkt *mt.Pkt) bool { + if auth.state == state { + return true + } + + auth.setError("received " + pktToString(pkt) + " in invalid state") + return false +} + +func (auth *Auth) handle(pkt *mt.Pkt, l *lua.State, idx int) { + if pkt == nil { + return + } + + switch cmd := pkt.Cmd.(type) { + case *mt.ToCltHello: + if !auth.checkState(asInit, pkt) { + return + } + + if cmd.SerializeVer != 28 { + auth.setError("unsupported serialize_ver") + return + } + + if cmd.AuthMethods == mt.FirstSRP { + salt, verifier, err := srp.NewClient([]byte(strings.ToLower(auth.username)), []byte(auth.password)) + if err != nil { + auth.setError(err.Error()) + return + } + + auth.client.conn.SendCmd(&mt.ToSrvFirstSRP{ + Salt: salt, + Verifier: verifier, + EmptyPasswd: auth.password == "", + }) + auth.state = asVerified + } else if cmd.AuthMethods == mt.SRP { + var err error + auth.srpBytesA, auth.bytesA, err = srp.InitiateHandshake() + if err != nil { + auth.setError(err.Error()) + return + } + + auth.client.conn.SendCmd(&mt.ToSrvSRPBytesA{ + A: auth.srpBytesA, + NoSHA1: true, + }) + auth.state = asRequested + } else { + auth.setError("invalid auth methods") + return + } + + case *mt.ToCltSRPBytesSaltB: + if !auth.checkState(asRequested, pkt) { + return + } + + srpBytesK, err := srp.CompleteHandshake(auth.srpBytesA, auth.bytesA, []byte(strings.ToLower(auth.username)), []byte(auth.password), cmd.Salt, cmd.B) + if err != nil { + auth.setError(err.Error()) + return + } + + M := srp.ClientProof([]byte(auth.username), cmd.Salt, auth.srpBytesA, cmd.B, srpBytesK) + auth.srpBytesA = []byte{} + auth.bytesA = []byte{} + + if M == nil { + auth.setError("srp safety check fail") + return + } + + auth.client.conn.SendCmd(&mt.ToSrvSRPBytesM{ + M: M, + }) + auth.state = asVerified + + case *mt.ToCltAcceptAuth: + auth.client.conn.SendCmd(&mt.ToSrvInit2{Lang: auth.language}) + + case *mt.ToCltTimeOfDay: + if auth.state == asActive { + return + } + + if !auth.checkState(asVerified, pkt) { + return + } + + auth.client.conn.SendCmd(&mt.ToSrvCltReady{ + Major: 5, + Minor: 6, + Patch: 0, + Reserved: 0, + Formspec: 4, + // Version: "hydra-dragonfire", + Version: "astolfo", + }) + auth.state = asActive + } +} + +func l_auth_username(l *lua.State) int { + auth := getAuth(l) + + if l.IsString(2) { + if auth.client.state > csNew { + panic("can't change username after connecting") + } + auth.username = lua.CheckString(l, 2) + return 0 + } else { + l.PushString(auth.username) + return 1 + } +} + +func l_auth_password(l *lua.State) int { + auth := getAuth(l) + + if l.IsString(2) { + if auth.client.state > csNew { + panic("can't change password after connecting") + } + auth.password = lua.CheckString(l, 2) + return 0 + } else { + l.PushString(auth.password) + return 1 + } +} + +func l_auth_language(l *lua.State) int { + auth := getAuth(l) + + if l.IsString(2) { + if auth.client.state > csNew { + panic("can't change language after connecting") + } + auth.language = lua.CheckString(l, 2) + return 0 + } else { + l.PushString(auth.language) + return 1 + } +} + +func l_auth_state(l *lua.State) int { + auth := getAuth(l) + + switch auth.state { + case asInit: + l.PushString("init") + case asRequested: + l.PushString("requested") + case asVerified: + l.PushString("verified") + case asActive: + l.PushString("active") + case asError: + l.PushString("error") + l.PushString(auth.err) + return 2 + } + + return 1 +} diff --git a/builtin/vector.lua b/builtin/vector.lua new file mode 100644 index 0000000..2fd926a --- /dev/null +++ b/builtin/vector.lua @@ -0,0 +1,126 @@ +--[[ builtin/vector.lua ]]-- + +local function wrap(op, body_wrapper, ...) + return load("return function(a, b) " .. body_wrapper(op, ...) .. "end")() +end + +local function arith_mt(...) + return { + __add = wrap("+", ...), + __sub = wrap("-", ...), + __mul = wrap("*", ...), + __div = wrap("/", ...), + __mod = wrap("%", ...), + } +end + +-- vec2 + +local mt_vec2 = arith_mt(function(op) + return [[ + if type(b) == "number" then + return vec2(a.x ]] .. op.. [[ b, a.y ]] .. op .. [[ b) + else + return vec2(a.x ]] .. op.. [[ b.x, a.y ]] .. op.. [[ b.y) + end + ]] +end) + +function mt_vec2:__neg() + return vec2(-self.x, -self.y) +end + +function mt_vec2:__tostring() + return "(" .. self.x .. ", " .. self.y .. ")" +end + +function vec2(a, b) + local o = {} + + if type(a) == "number" then + o.x = a + o.y = b or a + else + o.x = a.x + o.y = a.y + end + + setmetatable(o, mt_vec2) + return o +end + +-- vec3 + +local mt_vec3 = arith_mt(function(op) + return [[ + if type(b) == "number" then + return vec3(a.x ]] .. op.. [[ b, a.y ]] .. op .. [[ b, a.z ]] .. op .. [[ b) + else + return vec3(a.x ]] .. op.. [[ b.x, a.y ]] .. op.. [[ b.y, a.z ]] .. op.. [[ b.z) + end + ]] +end) + +function mt_vec3:__neg() + return vec3(-self.x, -self.y, -self.z) +end + +function mt_vec3:__tostring() + return "(" .. self.x .. ", " .. self.y .. ", " .. self.z .. ")" +end + +function vec3(a, b, c) + local o = {} + + if type(a) == "number" then + o.x = a + o.y = b or a + o.z = c or a + else + o.x = a.x + o.y = a.y + o.z = a.z + end + + setmetatable(o, mt_vec3) + return o +end + +-- box + +local mt_box = arith_mt(function(op) + return "return box(a.min " .. op .. " b, a.max " .. op .. " b)" +end) + +function mt_box:__neg() + return box(-self.min, -self.max) +end + +function mt_box:__tostring() + return "[" .. self.min .. "; " .. self.max .. "]" +end + +mt_box.__index = { + contains = function(a, b) + if type(b) == "number" or b.x then + return a.min <= b and a.max >= b + else + return a.min <= b.min and a.max >= b.max + end + end, +} + +function box(a, b) + local o = {} + + if type(a) == "number" or a.x then + o.min = a + o.max = b + else + o.min = a.min + o.max = a.max + end + + setmetatable(o, mt_box) + return o +end diff --git a/callbacks.go b/callbacks.go new file mode 100644 index 0000000..e16aec7 --- /dev/null +++ b/callbacks.go @@ -0,0 +1,85 @@ +package main + +import ( + "github.com/Shopify/go-lua" + "github.com/anon55555/mt" +) + +type Callbacks struct { + wildcard bool + subscribed map[string]struct{} +} + +func getCallbacks(l *lua.State) *Callbacks { + return lua.CheckUserData(l, 1, "hydra.callbacks").(*Callbacks) +} + +func (handler *Callbacks) create(client *Client) { + handler.subscribed = map[string]struct{}{} +} + +func (handler *Callbacks) push(l *lua.State) { + l.PushUserData(handler) + + if lua.NewMetaTable(l, "hydra.callbacks") { + lua.NewLibrary(l, []lua.RegistryFunction{ + {Name: "wildcard", Function: l_callbacks_wildcard}, + {Name: "subscribe", Function: l_callbacks_subscribe}, + {Name: "unsubscribe", Function: l_callbacks_unsubscribe}, + }) + l.SetField(-2, "__index") + } + l.SetMetaTable(-2) +} + +func (handler *Callbacks) canConnect() (bool, string) { + return true, "" +} + +func (handler *Callbacks) connect() { +} + +func (handler *Callbacks) handle(pkt *mt.Pkt, l *lua.State, idx int) { + if !handler.wildcard && pkt != nil { + if _, exists := handler.subscribed[pktToString(pkt)]; !exists { + return + } + } + + if !l.IsFunction(2) { + return + } + + l.PushValue(2) // callback + l.RawGetInt(1, idx) // arg 1: client + pktToLua(l, pkt) // arg 2: pkt + l.Call(2, 0) +} + +func l_callbacks_wildcard(l *lua.State) int { + handler := getCallbacks(l) + handler.wildcard = l.ToBoolean(2) + return 0 +} + +func l_callbacks_subscribe(l *lua.State) int { + handler := getCallbacks(l) + + n := l.Top() + for i := 2; i <= n; i++ { + handler.subscribed[lua.CheckString(l, i)] = struct{}{} + } + + return 0 +} + +func l_callbacks_unsubscribe(l *lua.State) int { + handler := getCallbacks(l) + + n := l.Top() + for i := 2; i <= n; i++ { + delete(handler.subscribed, lua.CheckString(l, i)) + } + + return 0 +} diff --git a/client.go b/client.go new file mode 100644 index 0000000..a57e032 --- /dev/null +++ b/client.go @@ -0,0 +1,172 @@ +package main + +import ( + "errors" + "github.com/Shopify/go-lua" + "github.com/anon55555/mt" + "net" +) + +type clientState uint8 + +const ( + csNew clientState = iota + csConnected + csDisconnected +) + +type Handler interface { + create(client *Client) + push(l *lua.State) + canConnect() (bool, string) + connect() + handle(pkt *mt.Pkt, l *lua.State, idx int) +} + +type Client struct { + address string + state clientState + handlers map[string]Handler + conn mt.Peer + queue chan *mt.Pkt +} + +func getClient(l *lua.State) *Client { + return lua.CheckUserData(l, 1, "hydra.client").(*Client) +} + +func l_client(l *lua.State) int { + client := &Client{ + address: lua.CheckString(l, 1), + state: csNew, + handlers: map[string]Handler{}, + } + + l.PushUserData(client) + + if lua.NewMetaTable(l, "hydra.client") { + lua.NewLibrary(l, []lua.RegistryFunction{ + {Name: "address", Function: l_client_address}, + {Name: "state", Function: l_client_state}, + {Name: "handler", Function: l_client_handler}, + {Name: "connect", Function: l_client_connect}, + {Name: "disconnect", Function: l_client_disconnect}, + }) + l.SetField(-2, "__index") + } + l.SetMetaTable(-2) + + return 1 +} + +func l_client_address(l *lua.State) int { + client := getClient(l) + l.PushString(client.address) + return 1 +} + +func l_client_state(l *lua.State) int { + client := getClient(l) + switch client.state { + case csNew: + l.PushString("new") + case csConnected: + l.PushString("connected") + case csDisconnected: + l.PushString("disconnected") + } + return 1 +} + +func l_client_handler(l *lua.State) int { + client := getClient(l) + name := lua.CheckString(l, 2) + + handler, exists := client.handlers[name] + if !exists { + switch name { + case "callbacks": + handler = &Callbacks{} + + case "auth": + handler = &Auth{} + + default: + return 0 + } + + client.handlers[name] = handler + handler.create(client) + } + + handler.push(l) + return 1 +} + +func l_client_connect(l *lua.State) int { + client := getClient(l) + + if client.state != csNew { + l.PushBoolean(false) + l.PushString("invalid state") + return 2 + } + + for _, handler := range client.handlers { + ok, err := handler.canConnect() + + if !ok { + l.PushBoolean(false) + l.PushString(err) + return 2 + } + } + + addr, err := net.ResolveUDPAddr("udp", client.address) + if err != nil { + l.PushBoolean(false) + l.PushString(err.Error()) + return 2 + } + + conn, err := net.DialUDP("udp", nil, addr) + if err != nil { + l.PushBoolean(false) + l.PushString(err.Error()) + return 2 + } + + client.state = csConnected + client.conn = mt.Connect(conn) + client.queue = make(chan *mt.Pkt, 1024) + + for _, handler := range client.handlers { + handler.connect() + } + + go func() { + for { + pkt, err := client.conn.Recv() + + if err == nil { + client.queue <- &pkt + } else if errors.Is(err, net.ErrClosed) { + close(client.queue) + return + } + } + }() + + l.PushBoolean(true) + return 1 +} + +func l_client_disconnect(l *lua.State) int { + client := getClient(l) + + if client.state == csConnected { + client.conn.Close() + } + + return 0 +} diff --git a/convert.go b/convert.go new file mode 100644 index 0000000..c5e4daa --- /dev/null +++ b/convert.go @@ -0,0 +1,882 @@ +// generated by mkconvert.lua, DO NOT EDIT +package main + +import ( + "github.com/Shopify/go-lua" + "github.com/anon55555/mt" +) + +func luaPushHotbarParam(l *lua.State, val mt.HotbarParam) { + switch val { + case mt.HotbarSize: + l.PushString("size") + case mt.HotbarImg: + l.PushString("img") + case mt.HotbarSelImg: + l.PushString("sel_img") + } +} + +func luaPushChatMsgType(l *lua.State, val mt.ChatMsgType) { + switch val { + case mt.RawMsg: + l.PushString("raw") + case mt.NormalMsg: + l.PushString("normal") + case mt.AnnounceMsg: + l.PushString("announce") + case mt.SysMsg: + l.PushString("sys") + } +} + +func luaPushHUDType(l *lua.State, val mt.HUDType) { + switch val { + case mt.ImgHUD: + l.PushString("img") + case mt.TextHUD: + l.PushString("text") + case mt.StatbarHUD: + l.PushString("statbar") + case mt.InvHUD: + l.PushString("inv") + case mt.WaypointHUD: + l.PushString("waypoint") + case mt.ImgWaypointHUD: + l.PushString("img_waypoint") + } +} + +func luaPushPlayerListUpdateType(l *lua.State, val mt.PlayerListUpdateType) { + switch val { + case mt.InitPlayers: + l.PushString("init") + case mt.AddPlayers: + l.PushString("add") + case mt.RemovePlayers: + l.PushString("remove") + } +} + +func luaPushHUDField(l *lua.State, val mt.HUDField) { + switch val { + case mt.HUDPos: + l.PushString("pos") + case mt.HUDName: + l.PushString("name") + case mt.HUDScale: + l.PushString("scale") + case mt.HUDText: + l.PushString("text") + case mt.HUDNumber: + l.PushString("number") + case mt.HUDItem: + l.PushString("item") + case mt.HUDDir: + l.PushString("dir") + case mt.HUDAlign: + l.PushString("align") + case mt.HUDOffset: + l.PushString("offset") + case mt.HUDWorldPos: + l.PushString("world_pos") + case mt.HUDSize: + l.PushString("size") + case mt.HUDZIndex: + l.PushString("z_index") + case mt.HUDText2: + l.PushString("text_2") + } +} + +func luaPushModChanSig(l *lua.State, val mt.ModChanSig) { + switch val { + case mt.JoinOK: + l.PushString("join_ok") + case mt.JoinFail: + l.PushString("join_fail") + case mt.LeaveOK: + l.PushString("leave_ok") + case mt.LeaveFail: + l.PushString("leave_fail") + case mt.NotRegistered: + l.PushString("not_registered") + case mt.SetState: + l.PushString("set_state") + } +} + +func luaPushKickReason(l *lua.State, val mt.KickReason) { + switch val { + case mt.WrongPasswd: + l.PushString("wrong_passwd") + case mt.UnexpectedData: + l.PushString("unexpected_data") + case mt.SrvIsSingleplayer: + l.PushString("srv_is_singleplayer") + case mt.UnsupportedVer: + l.PushString("unsupported_ver") + case mt.BadNameChars: + l.PushString("bad_name_chars") + case mt.BadName: + l.PushString("bad_name") + case mt.TooManyClts: + l.PushString("too_many_clts") + case mt.EmptyPasswd: + l.PushString("empty_passwd") + case mt.AlreadyConnected: + l.PushString("already_connected") + case mt.SrvErr: + l.PushString("srv_err") + case mt.Custom: + l.PushString("custom") + case mt.Shutdown: + l.PushString("shutdown") + case mt.Crash: + l.PushString("crash") + } +} + +func luaPushSoundSrcType(l *lua.State, val mt.SoundSrcType) { + switch val { + case mt.NoSrc: + l.PushNil() + case mt.PosSrc: + l.PushString("pos") + case mt.AOSrc: + l.PushString("ao") + } +} + +func luaPushAnimType(l *lua.State, val mt.AnimType) { + switch val { + case mt.NoAnim: + l.PushNil() + case mt.VerticalFrameAnim: + l.PushString("vertical_frame") + case mt.SpriteSheetAnim: + l.PushString("sprite_sheet") + } +} + +func luaPushAuthMethods(l *lua.State, val mt.AuthMethods) { + l.NewTable() + if val&mt.LegacyPasswd != 0 { + l.PushBoolean(true) + l.SetField(-2, "legacy_passwd") + } + if val&mt.SRP != 0 { + l.PushBoolean(true) + l.SetField(-2, "srp") + } + if val&mt.FirstSRP != 0 { + l.PushBoolean(true) + l.SetField(-2, "first_srp") + } +} + +func luaPushHUDFlags(l *lua.State, val mt.HUDFlags) { + l.NewTable() + if val&mt.ShowHotbar != 0 { + l.PushBoolean(true) + l.SetField(-2, "hotbar") + } + if val&mt.ShowHealthBar != 0 { + l.PushBoolean(true) + l.SetField(-2, "health_bar") + } + if val&mt.ShowCrosshair != 0 { + l.PushBoolean(true) + l.SetField(-2, "crosshair") + } + if val&mt.ShowWieldedItem != 0 { + l.PushBoolean(true) + l.SetField(-2, "wielded_item") + } + if val&mt.ShowBreathBar != 0 { + l.PushBoolean(true) + l.SetField(-2, "breath_bar") + } + if val&mt.ShowMinimap != 0 { + l.PushBoolean(true) + l.SetField(-2, "minimap") + } + if val&mt.ShowRadarMinimap != 0 { + l.PushBoolean(true) + l.SetField(-2, "radar_minimap") + } +} + +func luaPushCSMRestrictionFlags(l *lua.State, val mt.CSMRestrictionFlags) { + l.NewTable() + if val&mt.NoCSMs != 0 { + l.PushBoolean(true) + l.SetField(-2, "no_csms") + } + if val&mt.NoChatMsgs != 0 { + l.PushBoolean(true) + l.SetField(-2, "no_chat_msgs") + } + if val&mt.NoNodeDefs != 0 { + l.PushBoolean(true) + l.SetField(-2, "no_node_defs") + } + if val&mt.LimitMapRange != 0 { + l.PushBoolean(true) + l.SetField(-2, "limit_map_range") + } + if val&mt.NoPlayerList != 0 { + l.PushBoolean(true) + l.SetField(-2, "no_player_list") + } +} + +func luaPushTileAnim(l *lua.State, val mt.TileAnim) { + l.NewTable() + luaPushAnimType(l, val.Type) + l.SetField(-2, "type") + luaPushVec2(l, [2]float64{float64(val.NFrames[0]), float64(val.NFrames[1])}) + l.SetField(-2, "n_frames") + l.PushNumber(float64(val.Duration)) + l.SetField(-2, "duration") + luaPushVec2(l, [2]float64{float64(val.AspectRatio[0]), float64(val.AspectRatio[1])}) + l.SetField(-2, "aspect_ratio") +} + +func luaPushNode(l *lua.State, val mt.Node) { + l.NewTable() + l.PushInteger(int(val.Param0)) + l.SetField(-2, "param0") + l.PushInteger(int(val.Param1)) + l.SetField(-2, "param1") + l.PushInteger(int(val.Param2)) + l.SetField(-2, "param2") +} + +func luaPushHUD(l *lua.State, val mt.HUD) { + l.NewTable() + luaPushHUDType(l, val.Type) + l.SetField(-2, "type") + l.PushInteger(int(val.ZIndex)) + l.SetField(-2, "z_index") + luaPushVec2(l, [2]float64{float64(val.Scale[0]), float64(val.Scale[1])}) + l.SetField(-2, "scale") + l.PushString(string(val.Name)) + l.SetField(-2, "name") + luaPushVec3(l, [3]float64{float64(val.WorldPos[0]), float64(val.WorldPos[1]), float64(val.WorldPos[2])}) + l.SetField(-2, "world_pos") + l.PushString(string(val.Text)) + l.SetField(-2, "text") + l.PushString(string(val.Text2)) + l.SetField(-2, "text_2") + luaPushVec2(l, [2]float64{float64(val.Size[0]), float64(val.Size[1])}) + l.SetField(-2, "size") + luaPushVec2(l, [2]float64{float64(val.Align[0]), float64(val.Align[1])}) + l.SetField(-2, "align") + luaPushVec2(l, [2]float64{float64(val.Pos[0]), float64(val.Pos[1])}) + l.SetField(-2, "pos") + l.PushInteger(int(val.Dir)) + l.SetField(-2, "dir") + luaPushVec2(l, [2]float64{float64(val.Offset[0]), float64(val.Offset[1])}) + l.SetField(-2, "offset") + l.PushInteger(int(val.Number)) + l.SetField(-2, "number") + l.PushInteger(int(val.Item)) + l.SetField(-2, "item") +} + +func pktToString(pkt *mt.Pkt) string { + switch pkt.Cmd.(type) { + case *mt.ToCltPlaySound: + return "play_sound" + case *mt.ToCltLegacyKick: + return "legacy_kick" + case *mt.ToCltFOV: + return "fov" + case *mt.ToCltNodeMetasChanged: + return "node_metas_changed" + case *mt.ToCltHello: + return "hello" + case *mt.ToCltAcceptSudoMode: + return "accept_sudo_mode" + case *mt.ToCltPrivs: + return "privs" + case *mt.ToCltDetachedInv: + return "detached_inv" + case *mt.ToCltSpawnParticle: + return "spawn_particle" + case *mt.ToCltAcceptAuth: + return "accept_auth" + case *mt.ToCltOverrideDayNightRatio: + return "override_day_night_ratio" + case *mt.ToCltMinimapModes: + return "minimap_modes" + case *mt.ToCltAddHUD: + return "add_hud" + case *mt.ToCltHP: + return "hp" + case *mt.ToCltChangeHUD: + return "change_hud" + case *mt.ToCltFormspecPrepend: + return "formspec_prepend" + case *mt.ToCltSRPBytesSaltB: + return "srp_bytes_salt_b" + case *mt.ToCltSetHotbarParam: + return "set_hotbar_param" + case *mt.ToCltMovePlayer: + return "move_player" + case *mt.ToCltAddParticleSpawner: + return "add_particle_spawner" + case *mt.ToCltKick: + return "kick" + case *mt.ToCltDelParticleSpawner: + return "del_particle_spawner" + case *mt.ToCltModChanSig: + return "mod_chan_sig" + case *mt.ToCltMoonParams: + return "moon_params" + case *mt.ToCltModChanMsg: + return "mod_chan_msg" + case *mt.ToCltSunParams: + return "sun_params" + case *mt.ToCltInv: + return "inv" + case *mt.ToCltRemoveNode: + return "remove_node" + case *mt.ToCltNodeDefs: + return "node_defs" + case *mt.ToCltMediaPush: + return "media_push" + case *mt.ToCltLocalPlayerAnim: + return "local_player_anim" + case *mt.ToCltFadeSound: + return "fade_sound" + case *mt.ToCltItemDefs: + return "item_defs" + case *mt.ToCltUpdatePlayerList: + return "update_player_list" + case *mt.ToCltEyeOffset: + return "eye_offset" + case *mt.ToCltMedia: + return "media" + case *mt.ToCltDisco: + return "disco" + case *mt.ToCltBlkData: + return "blk_data" + case *mt.ToCltSkyParams: + return "sky_params" + case *mt.ToCltBreath: + return "breath" + case *mt.ToCltChatMsg: + return "chat_msg" + case *mt.ToCltHUDFlags: + return "hud_flags" + case *mt.ToCltAOMsgs: + return "ao_msgs" + case *mt.ToCltRmHUD: + return "rm_hud" + case *mt.ToCltStarParams: + return "star_params" + case *mt.ToCltDeathScreen: + return "death_screen" + case *mt.ToCltAORmAdd: + return "ao_rm_add" + case *mt.ToCltAddPlayerVel: + return "add_player_vel" + case *mt.ToCltMovement: + return "movement" + case *mt.ToCltCloudParams: + return "cloud_params" + case *mt.ToCltDenySudoMode: + return "deny_sudo_mode" + case *mt.ToCltCSMRestrictionFlags: + return "csm_restriction_flags" + case *mt.ToCltAddNode: + return "add_node" + case *mt.ToCltStopSound: + return "stop_sound" + case *mt.ToCltInvFormspec: + return "inv_formspec" + case *mt.ToCltAnnounceMedia: + return "announce_media" + case *mt.ToCltShowFormspec: + return "show_formspec" + case *mt.ToCltTimeOfDay: + return "time_of_day" + } + panic("impossible") + return "" +} + +func pktToLua(l *lua.State, pkt *mt.Pkt) { + if pkt == nil { + l.PushNil() + return + } + l.NewTable() + l.PushString(pktToString(pkt)) + l.SetField(-2, "_type") + switch val := pkt.Cmd.(type) { + case *mt.ToCltPlaySound: + l.PushNumber(float64(val.Gain)) + l.SetField(-2, "gain") + l.PushInteger(int(val.ID)) + l.SetField(-2, "id") + l.PushNumber(float64(val.Pitch)) + l.SetField(-2, "pitch") + luaPushSoundSrcType(l, val.SrcType) + l.SetField(-2, "src_type") + l.PushInteger(int(val.SrcAOID)) + l.SetField(-2, "src_aoid") + l.PushNumber(float64(val.Fade)) + l.SetField(-2, "fade") + l.PushBoolean(bool(val.Ephemeral)) + l.SetField(-2, "ephemeral") + l.PushBoolean(bool(val.Loop)) + l.SetField(-2, "loop") + l.PushString(string(val.Name)) + l.SetField(-2, "name") + luaPushVec3(l, [3]float64{float64(val.Pos[0]), float64(val.Pos[1]), float64(val.Pos[2])}) + l.SetField(-2, "pos") + case *mt.ToCltLegacyKick: + l.PushString(string(val.Reason)) + l.SetField(-2, "reason") + case *mt.ToCltFOV: + l.PushNumber(float64(val.TransitionTime)) + l.SetField(-2, "transition_time") + l.PushBoolean(bool(val.Multiplier)) + l.SetField(-2, "multiplier") + l.PushNumber(float64(val.FOV)) + l.SetField(-2, "fov") + case *mt.ToCltHello: + luaPushAuthMethods(l, val.AuthMethods) + l.SetField(-2, "auth_methods") + l.PushString(string(val.Username)) + l.SetField(-2, "username") + l.PushInteger(int(val.Compression)) + l.SetField(-2, "compression") + l.PushInteger(int(val.ProtoVer)) + l.SetField(-2, "proto_ver") + l.PushInteger(int(val.SerializeVer)) + l.SetField(-2, "serialize_ver") + case *mt.ToCltPrivs: + luaPushStringSet(l, val.Privs) + l.SetField(-2, "privs") + case *mt.ToCltDetachedInv: + l.PushString(string(val.Inv)) + l.SetField(-2, "inv") + l.PushBoolean(bool(val.Keep)) + l.SetField(-2, "keep") + l.PushInteger(int(val.Len)) + l.SetField(-2, "len") + l.PushString(string(val.Name)) + l.SetField(-2, "name") + case *mt.ToCltSpawnParticle: + l.PushBoolean(bool(val.Collide)) + l.SetField(-2, "collide") + l.PushString(string(val.Texture)) + l.SetField(-2, "texture") + luaPushVec3(l, [3]float64{float64(val.Pos[0]), float64(val.Pos[1]), float64(val.Pos[2])}) + l.SetField(-2, "pos") + l.PushInteger(int(val.NodeTile)) + l.SetField(-2, "node_tile") + l.PushBoolean(bool(val.Vertical)) + l.SetField(-2, "vertical") + l.PushInteger(int(val.Glow)) + l.SetField(-2, "glow") + l.PushInteger(int(val.NodeParam2)) + l.SetField(-2, "node_param2") + l.PushInteger(int(val.NodeParam0)) + l.SetField(-2, "node_param0") + l.PushBoolean(bool(val.AOCollision)) + l.SetField(-2, "ao_collision") + l.PushNumber(float64(val.Size)) + l.SetField(-2, "size") + l.PushNumber(float64(val.ExpirationTime)) + l.SetField(-2, "expiration_time") + l.PushBoolean(bool(val.CollisionRm)) + l.SetField(-2, "collision_rm") + luaPushTileAnim(l, val.AnimParams) + l.SetField(-2, "anim_params") + luaPushVec3(l, [3]float64{float64(val.Acc[0]), float64(val.Acc[1]), float64(val.Acc[2])}) + l.SetField(-2, "acc") + luaPushVec3(l, [3]float64{float64(val.Vel[0]), float64(val.Vel[1]), float64(val.Vel[2])}) + l.SetField(-2, "vel") + case *mt.ToCltAcceptAuth: + l.PushNumber(float64(val.MapSeed)) + l.SetField(-2, "map_seed") + luaPushAuthMethods(l, val.SudoAuthMethods) + l.SetField(-2, "sudo_auth_methods") + l.PushNumber(float64(val.SendInterval)) + l.SetField(-2, "send_interval") + luaPushVec3(l, [3]float64{float64(val.PlayerPos[0]), float64(val.PlayerPos[1]), float64(val.PlayerPos[2])}) + l.SetField(-2, "player_pos") + case *mt.ToCltOverrideDayNightRatio: + l.PushInteger(int(val.Ratio)) + l.SetField(-2, "ratio") + l.PushBoolean(bool(val.Override)) + l.SetField(-2, "override") + case *mt.ToCltAddHUD: + luaPushHUD(l, val.HUD) + l.SetField(-2, "hud") + l.PushInteger(int(val.ID)) + l.SetField(-2, "id") + case *mt.ToCltHP: + l.PushInteger(int(val.HP)) + l.SetField(-2, "hp") + case *mt.ToCltChangeHUD: + if val.Field == mt.HUDWorldPos { + luaPushVec3(l, [3]float64{float64(val.WorldPos[0]), float64(val.WorldPos[1]), float64(val.WorldPos[2])}) + l.SetField(-2, "world_pos") + } + if val.Field == mt.HUDText2 { + l.PushString(string(val.Text2)) + l.SetField(-2, "text_2") + } + if val.Field == mt.HUDItem { + l.PushInteger(int(val.Item)) + l.SetField(-2, "item") + } + if val.Field == mt.HUDZIndex { + l.PushInteger(int(val.ZIndex)) + l.SetField(-2, "z_index") + } + if val.Field == mt.HUDPos { + luaPushVec2(l, [2]float64{float64(val.Pos[0]), float64(val.Pos[1])}) + l.SetField(-2, "pos") + } + if val.Field == mt.HUDSize { + luaPushVec2(l, [2]float64{float64(val.Size[0]), float64(val.Size[1])}) + l.SetField(-2, "size") + } + if val.Field == mt.HUDName { + l.PushString(string(val.Name)) + l.SetField(-2, "name") + } + if val.Field == mt.HUDDir { + l.PushInteger(int(val.Dir)) + l.SetField(-2, "dir") + } + if val.Field == mt.HUDAlign { + luaPushVec2(l, [2]float64{float64(val.Align[0]), float64(val.Align[1])}) + l.SetField(-2, "align") + } + if val.Field == mt.HUDNumber { + l.PushInteger(int(val.Number)) + l.SetField(-2, "number") + } + if val.Field == mt.HUDText { + l.PushString(string(val.Text)) + l.SetField(-2, "text") + } + if val.Field == mt.HUDOffset { + luaPushVec2(l, [2]float64{float64(val.Offset[0]), float64(val.Offset[1])}) + l.SetField(-2, "offset") + } + luaPushHUDField(l, val.Field) + l.SetField(-2, "field") + l.PushInteger(int(val.ID)) + l.SetField(-2, "id") + case *mt.ToCltFormspecPrepend: + l.PushString(string(val.Prepend)) + l.SetField(-2, "prepend") + case *mt.ToCltSRPBytesSaltB: + l.PushString(string(val.B)) + l.SetField(-2, "b") + l.PushString(string(val.Salt)) + l.SetField(-2, "salt") + case *mt.ToCltSetHotbarParam: + luaPushHotbarParam(l, val.Param) + l.SetField(-2, "param") + l.PushInteger(int(val.Size)) + l.SetField(-2, "size") + l.PushString(string(val.Img)) + l.SetField(-2, "img") + case *mt.ToCltMovePlayer: + l.PushNumber(float64(val.Yaw)) + l.SetField(-2, "yaw") + l.PushNumber(float64(val.Pitch)) + l.SetField(-2, "pitch") + luaPushVec3(l, [3]float64{float64(val.Pos[0]), float64(val.Pos[1]), float64(val.Pos[2])}) + l.SetField(-2, "pos") + case *mt.ToCltAddParticleSpawner: + luaPushBox3(l, [2][3]float64{{float64(val.Acc[0][0]), float64(val.Acc[0][1]), float64(val.Acc[0][2])}, {float64(val.Acc[1][0]), float64(val.Acc[1][1]), float64(val.Acc[1][2])}}) + l.SetField(-2, "acc") + l.PushInteger(int(val.ID)) + l.SetField(-2, "id") + l.PushString(string(val.Texture)) + l.SetField(-2, "texture") + l.PushBoolean(bool(val.Vertical)) + l.SetField(-2, "vertical") + luaPushBox1(l, [2]float64{float64(val.ExpirationTime[0]), float64(val.ExpirationTime[1])}) + l.SetField(-2, "expiration_time") + luaPushTileAnim(l, val.AnimParams) + l.SetField(-2, "anim_params") + l.PushBoolean(bool(val.AOCollision)) + l.SetField(-2, "ao_collision") + luaPushBox3(l, [2][3]float64{{float64(val.Pos[0][0]), float64(val.Pos[0][1]), float64(val.Pos[0][2])}, {float64(val.Pos[1][0]), float64(val.Pos[1][1]), float64(val.Pos[1][2])}}) + l.SetField(-2, "pos") + l.PushInteger(int(val.Glow)) + l.SetField(-2, "glow") + l.PushInteger(int(val.NodeParam0)) + l.SetField(-2, "node_param0") + luaPushBox3(l, [2][3]float64{{float64(val.Vel[0][0]), float64(val.Vel[0][1]), float64(val.Vel[0][2])}, {float64(val.Vel[1][0]), float64(val.Vel[1][1]), float64(val.Vel[1][2])}}) + l.SetField(-2, "vel") + l.PushBoolean(bool(val.Collide)) + l.SetField(-2, "collide") + luaPushBox1(l, [2]float64{float64(val.Size[0]), float64(val.Size[1])}) + l.SetField(-2, "size") + l.PushInteger(int(val.NodeParam2)) + l.SetField(-2, "node_param2") + l.PushNumber(float64(val.Duration)) + l.SetField(-2, "duration") + l.PushInteger(int(val.NodeTile)) + l.SetField(-2, "node_tile") + l.PushInteger(int(val.Amount)) + l.SetField(-2, "amount") + l.PushBoolean(bool(val.CollisionRm)) + l.SetField(-2, "collision_rm") + case *mt.ToCltKick: + luaPushKickReason(l, val.Reason) + l.SetField(-2, "reason") + if dr := val.Reason; dr == mt.Custom || dr == mt.Shutdown || dr == mt.Crash { + l.PushString(string(val.Custom)) + l.SetField(-2, "custom") + } + if dr := val.Reason; dr == mt.Shutdown || dr == mt.Crash { + l.PushBoolean(bool(val.Reconnect)) + l.SetField(-2, "reconnect") + } + case *mt.ToCltDelParticleSpawner: + l.PushInteger(int(val.ID)) + l.SetField(-2, "id") + case *mt.ToCltModChanSig: + l.PushString(string(val.Channel)) + l.SetField(-2, "channel") + luaPushModChanSig(l, val.Signal) + l.SetField(-2, "signal") + case *mt.ToCltMoonParams: + l.PushNumber(float64(val.Size)) + l.SetField(-2, "size") + l.PushString(string(val.ToneMap)) + l.SetField(-2, "tone_map") + l.PushString(string(val.Texture)) + l.SetField(-2, "texture") + l.PushBoolean(bool(val.Visible)) + l.SetField(-2, "visible") + case *mt.ToCltModChanMsg: + l.PushString(string(val.Channel)) + l.SetField(-2, "channel") + l.PushString(string(val.Sender)) + l.SetField(-2, "sender") + l.PushString(string(val.Msg)) + l.SetField(-2, "msg") + case *mt.ToCltSunParams: + l.PushString(string(val.Texture)) + l.SetField(-2, "texture") + l.PushBoolean(bool(val.Visible)) + l.SetField(-2, "visible") + l.PushNumber(float64(val.Size)) + l.SetField(-2, "size") + l.PushString(string(val.ToneMap)) + l.SetField(-2, "tone_map") + l.PushBoolean(bool(val.Rising)) + l.SetField(-2, "rising") + l.PushString(string(val.Rise)) + l.SetField(-2, "rise") + case *mt.ToCltInv: + l.PushString(string(val.Inv)) + l.SetField(-2, "inv") + case *mt.ToCltRemoveNode: + luaPushVec3(l, [3]float64{float64(val.Pos[0]), float64(val.Pos[1]), float64(val.Pos[2])}) + l.SetField(-2, "pos") + case *mt.ToCltMediaPush: + l.PushString(string(val.SHA1[:])) + l.SetField(-2, "sha1") + l.PushString(string(val.Data)) + l.SetField(-2, "data") + l.PushBoolean(bool(val.ShouldCache)) + l.SetField(-2, "should_cache") + l.PushString(string(val.Filename)) + l.SetField(-2, "filename") + case *mt.ToCltLocalPlayerAnim: + luaPushBox1(l, [2]float64{float64(val.Walk[0]), float64(val.Walk[1])}) + l.SetField(-2, "walk") + luaPushBox1(l, [2]float64{float64(val.Idle[0]), float64(val.Idle[1])}) + l.SetField(-2, "idle") + l.PushNumber(float64(val.Speed)) + l.SetField(-2, "speed") + luaPushBox1(l, [2]float64{float64(val.Dig[0]), float64(val.Dig[1])}) + l.SetField(-2, "dig") + luaPushBox1(l, [2]float64{float64(val.WalkDig[0]), float64(val.WalkDig[1])}) + l.SetField(-2, "walk_dig") + case *mt.ToCltFadeSound: + l.PushNumber(float64(val.Step)) + l.SetField(-2, "step") + l.PushInteger(int(val.ID)) + l.SetField(-2, "id") + l.PushNumber(float64(val.Gain)) + l.SetField(-2, "gain") + case *mt.ToCltUpdatePlayerList: + luaPushPlayerListUpdateType(l, val.Type) + l.SetField(-2, "type") + luaPushStringList(l, val.Players) + l.SetField(-2, "players") + case *mt.ToCltEyeOffset: + luaPushVec3(l, [3]float64{float64(val.First[0]), float64(val.First[1]), float64(val.First[2])}) + l.SetField(-2, "first") + luaPushVec3(l, [3]float64{float64(val.Third[0]), float64(val.Third[1]), float64(val.Third[2])}) + l.SetField(-2, "third") + case *mt.ToCltBlkData: + luaPushVec3(l, [3]float64{float64(val.Blkpos[0]), float64(val.Blkpos[1]), float64(val.Blkpos[2])}) + l.SetField(-2, "blkpos") + case *mt.ToCltSkyParams: + luaPushColor(l, val.SunFogTint) + l.SetField(-2, "sun_fog_tint") + l.PushString(string(val.FogTintType)) + l.SetField(-2, "fog_tint_type") + if val.Type == "regular" { + luaPushColor(l, val.DawnHorizon) + l.SetField(-2, "dawn_horizon") + } + if val.Type == "regular" { + luaPushColor(l, val.DaySky) + l.SetField(-2, "day_sky") + } + l.PushBoolean(bool(val.Clouds)) + l.SetField(-2, "clouds") + l.PushString(string(val.Type)) + l.SetField(-2, "type") + luaPushColor(l, val.BgColor) + l.SetField(-2, "bg_color") + if val.Type == "regular" { + luaPushColor(l, val.DawnSky) + l.SetField(-2, "dawn_sky") + } + if val.Type == "regular" { + luaPushColor(l, val.NightHorizon) + l.SetField(-2, "night_horizon") + } + if val.Type == "regular" { + luaPushColor(l, val.NightSky) + l.SetField(-2, "night_sky") + } + if val.Type == "regular" { + luaPushColor(l, val.DayHorizon) + l.SetField(-2, "day_horizon") + } + if val.Type == "skybox" { + luaPushTextureList(l, val.Textures) + l.SetField(-2, "textures") + } + luaPushColor(l, val.MoonFogTint) + l.SetField(-2, "moon_fog_tint") + if val.Type == "regular" { + luaPushColor(l, val.Indoor) + l.SetField(-2, "indoor") + } + case *mt.ToCltBreath: + l.PushInteger(int(val.Breath)) + l.SetField(-2, "breath") + case *mt.ToCltChatMsg: + luaPushChatMsgType(l, val.Type) + l.SetField(-2, "type") + l.PushString(string(val.Sender)) + l.SetField(-2, "sender") + l.PushNumber(float64(val.Timestamp)) + l.SetField(-2, "timestamp") + l.PushString(string(val.Text)) + l.SetField(-2, "text") + case *mt.ToCltHUDFlags: + luaPushHUDFlags(l, val.Flags) + l.SetField(-2, "flags") + luaPushHUDFlags(l, val.Mask) + l.SetField(-2, "mask") + case *mt.ToCltRmHUD: + l.PushInteger(int(val.ID)) + l.SetField(-2, "id") + case *mt.ToCltStarParams: + l.PushInteger(int(val.Count)) + l.SetField(-2, "count") + l.PushNumber(float64(val.Size)) + l.SetField(-2, "size") + luaPushColor(l, val.Color) + l.SetField(-2, "color") + l.PushBoolean(bool(val.Visible)) + l.SetField(-2, "visible") + case *mt.ToCltDeathScreen: + l.PushBoolean(bool(val.PointCam)) + l.SetField(-2, "point_cam") + luaPushVec3(l, [3]float64{float64(val.PointAt[0]), float64(val.PointAt[1]), float64(val.PointAt[2])}) + l.SetField(-2, "point_at") + case *mt.ToCltAddPlayerVel: + luaPushVec3(l, [3]float64{float64(val.Vel[0]), float64(val.Vel[1]), float64(val.Vel[2])}) + l.SetField(-2, "vel") + case *mt.ToCltMovement: + l.PushNumber(float64(val.JumpSpeed)) + l.SetField(-2, "jump_speed") + l.PushNumber(float64(val.FastAccel)) + l.SetField(-2, "fast_accel") + l.PushNumber(float64(val.FastSpeed)) + l.SetField(-2, "fast_speed") + l.PushNumber(float64(val.Sink)) + l.SetField(-2, "sink") + l.PushNumber(float64(val.AirAccel)) + l.SetField(-2, "air_accel") + l.PushNumber(float64(val.Gravity)) + l.SetField(-2, "gravity") + l.PushNumber(float64(val.CrouchSpeed)) + l.SetField(-2, "crouch_speed") + l.PushNumber(float64(val.Smoothing)) + l.SetField(-2, "smoothing") + l.PushNumber(float64(val.WalkSpeed)) + l.SetField(-2, "walk_speed") + l.PushNumber(float64(val.Fluidity)) + l.SetField(-2, "fluidity") + l.PushNumber(float64(val.ClimbSpeed)) + l.SetField(-2, "climb_speed") + l.PushNumber(float64(val.DefaultAccel)) + l.SetField(-2, "default_accel") + case *mt.ToCltCloudParams: + luaPushColor(l, val.AmbientColor) + l.SetField(-2, "ambient_color") + l.PushNumber(float64(val.Density)) + l.SetField(-2, "density") + luaPushColor(l, val.DiffuseColor) + l.SetField(-2, "diffuse_color") + l.PushNumber(float64(val.Height)) + l.SetField(-2, "height") + luaPushVec2(l, [2]float64{float64(val.Speed[0]), float64(val.Speed[1])}) + l.SetField(-2, "speed") + l.PushNumber(float64(val.Thickness)) + l.SetField(-2, "thickness") + case *mt.ToCltCSMRestrictionFlags: + luaPushCSMRestrictionFlags(l, val.Flags) + l.SetField(-2, "flags") + l.PushInteger(int(val.MapRange)) + l.SetField(-2, "map_range") + case *mt.ToCltAddNode: + luaPushNode(l, val.Node) + l.SetField(-2, "node") + l.PushBoolean(bool(val.KeepMeta)) + l.SetField(-2, "keep_meta") + luaPushVec3(l, [3]float64{float64(val.Pos[0]), float64(val.Pos[1]), float64(val.Pos[2])}) + l.SetField(-2, "pos") + case *mt.ToCltStopSound: + l.PushInteger(int(val.ID)) + l.SetField(-2, "id") + case *mt.ToCltInvFormspec: + l.PushString(string(val.Formspec)) + l.SetField(-2, "formspec") + case *mt.ToCltShowFormspec: + l.PushString(string(val.Formspec)) + l.SetField(-2, "formspec") + l.PushString(string(val.Formname)) + l.SetField(-2, "formname") + case *mt.ToCltTimeOfDay: + l.PushNumber(float64(val.Speed)) + l.SetField(-2, "speed") + l.PushInteger(int(val.Time)) + l.SetField(-2, "time") + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9a3d9b0 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/dragonfireclient/hydra + +go 1.17 + +require ( + github.com/HimbeerserverDE/srp v0.0.0 // indirect + github.com/Shopify/go-lua v0.0.0-20220120202609-9ab779377807 // indirect + github.com/anon55555/mt v0.0.0-20210919124550-bcc58cb3048f // indirect + github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..42ec0ba --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +github.com/HimbeerserverDE/srp v0.0.0 h1:Iy2GIF7DJphXXO9NjncLEBO6VsZd8Yhrlxl/qTr09eE= +github.com/HimbeerserverDE/srp v0.0.0/go.mod h1:pxNH8S2nh4n2DWE0ToX5GnnDr/uEAuaAhJsCpkDLIWw= +github.com/Shopify/go-lua v0.0.0-20220120202609-9ab779377807 h1:b10jUZ94GuJk5GBl0iElM5aGIPPHi7FTRvqOKA7Ku+s= +github.com/Shopify/go-lua v0.0.0-20220120202609-9ab779377807/go.mod h1:1cxA/QL5xgRGP7Crq6tXSOY4eo//me8GHGMyypHynM8= +github.com/anon55555/mt v0.0.0-20210919124550-bcc58cb3048f h1:tZU8VPYLyRrG3Lj9zBZvTVF5tUGciC/2aUIgTcU4WaM= +github.com/anon55555/mt v0.0.0-20210919124550-bcc58cb3048f/go.mod h1:jH4ER+ahjl7H6TczzK+q4V9sXY++U2Geh6/vt3r4Xvs= +github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ= +github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= diff --git a/hydra.go b/hydra.go new file mode 100644 index 0000000..77d7e05 --- /dev/null +++ b/hydra.go @@ -0,0 +1,74 @@ +package main + +import ( + _ "embed" + "github.com/Shopify/go-lua" + "os" + "os/signal" + "syscall" + "time" +) + +var lastTime = time.Now() +var canceled = false + +//go:embed builtin/vector.lua +var vectorLibrary string + +func l_dtime(l *lua.State) int { + l.PushNumber(time.Since(lastTime).Seconds()) + lastTime = time.Now() + return 1 +} + +func l_canceled(l *lua.State) int { + l.PushBoolean(canceled) + return 1 +} + +func signalChannel() chan os.Signal { + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) + return sig +} + +func main() { + if len(os.Args) < 2 { + panic("missing filename") + } + + go func() { + <-signalChannel() + canceled = true + }() + + l := lua.NewState() + lua.OpenLibraries(l) + + lua.NewLibrary(l, []lua.RegistryFunction{ + {Name: "client", Function: l_client}, + {Name: "dtime", Function: l_dtime}, + {Name: "canceled", Function: l_canceled}, + {Name: "poll", Function: l_poll}, + }) + + l.PushNumber(10.0) + l.SetField(-2, "BS") + + l.SetGlobal("hydra") + + l.NewTable() + for i, arg := range os.Args { + l.PushString(arg) + l.RawSetInt(-2, i - 1) + } + l.SetGlobal("arg") + + if err := lua.DoString(l, vectorLibrary); err != nil { + panic(err) + } + + if err := lua.DoFile(l, os.Args[1]); err != nil { + panic(err) + } +} diff --git a/mkconvert.lua b/mkconvert.lua new file mode 100755 index 0000000..d49d46d --- /dev/null +++ b/mkconvert.lua @@ -0,0 +1,201 @@ +#!/usr/bin/env lua +local function parse_pair(pair, value_first) + if pair:sub(1, 1) == "#" then + return + end + + local idx = pair:find(" ") + + if idx then + local first, second = pair:sub(1, idx - 1), pair:sub(idx + 1) + + if value_first and first:sub(1, 1) ~= "[" then + return second, first + else + return first, second + end + else + return pair + end +end + +local function parse_spec(name, value_first) + local f = io.open("spec/" .. name, "r") + local spec = {} + local top + + for l in f:lines() do + if l:sub(1, 1) == "\t" then + local key, val = parse_pair(l:sub(2), value_first) + + if val then + top[key] = val + elseif key then + table.insert(top, key) + end + else + local key, val = parse_pair(l, value_first) + + if val then + spec[key] = val + elseif key then + top = {} + spec[key] = top + end + end + end + + f:close() + return spec +end + +local casemap = parse_spec("casemap") + +local function camel_case(snake) + if casemap[snake] then + return casemap[snake] + end + + local camel = "" + + while #snake > 0 do + local idx = snake:find("_") or #snake + 1 + + camel = camel + .. snake:sub(1, 1):upper() + .. snake:sub(2, idx - 1) + + snake = snake:sub(idx + 1) + end + + return camel +end + +local funcs = "" + +for name, fields in pairs(parse_spec("client/enum")) do + local camel = camel_case(name) + funcs = funcs .. "func luaPush" .. camel .. "(l *lua.State, val mt." .. camel .. ") {\n\tswitch val {\n" + + for _, var in ipairs(fields) do + funcs = funcs .. "\tcase mt." + .. (fields.prefix or "") .. camel_case(var) .. (fields.postfix or "") + .. ":\n\t\t" .. (var == "no" and "l.PushNil()" or "l.PushString(\"" .. var .. "\")") .. "\n" + end + + funcs = funcs .. "\t}\n}\n\n" +end + +for name, fields in pairs(parse_spec("client/flag")) do + local camel = camel_case(name) + funcs = funcs .. "func luaPush" .. camel .. "(l *lua.State, val mt." .. camel .. ") {\n\tl.NewTable()\n" + + for _, var in ipairs(fields) do + funcs = funcs .. "\tif val&mt." + .. (fields.prefix or "") .. camel_case(var) .. (fields.postfix or "") + .. " != 0 {\n\t\tl.PushBoolean(true)\n\t\tl.SetField(-2, \"" .. var .. "\")\n\t}\n" + end + + funcs = funcs .. "}\n\n" +end + +local push_type = { + string = "l.PushString(string(VAL))", + fixed_string = "l.PushString(string(VAL[:]))", + boolean = "l.PushBoolean(bool(VAL))", + integer = "l.PushInteger(int(VAL))", + number = "l.PushNumber(float64(VAL))", + vec2 = "luaPushVec2(l, [2]float64{float64(VAL[0]), float64(VAL[1])})", + vec3 = "luaPushVec3(l, [3]float64{float64(VAL[0]), float64(VAL[1]), float64(VAL[2])})", + box1 = "luaPushBox1(l, [2]float64{float64(VAL[0]), float64(VAL[1])})", + box2 = "luaPushBox2(l, [2][2]float64{{float64(VAL[0][0]), float64(VAL[0][1])}, {float64(VAL[1][0]), float64(VAL[1][1])}})", + box3 = "luaPushBox3(l, [2][3]float64{{float64(VAL[0][0]), float64(VAL[0][1]), float64(VAL[0][2])}, {float64(VAL[1][0]), float64(VAL[1][1]), float64(VAL[1][2])}})", +} + +local function push_fields(fields, indent) + local impl = "" + + for name, type in pairs(fields) do + if name:sub(1, 1) ~= "[" then + local camel = "val." .. camel_case(name) + + local idt = indent + local condition = fields["[" .. name .. "]"] + + if condition then + impl = impl .. indent .. "if " .. condition .. " {\n" + idt = idt .. "\t" + end + + if push_type[type] then + impl = impl .. idt .. push_type[type]:gsub("VAL", camel) .. "\n" + else + impl = impl .. idt .. "luaPush" .. camel_case(type) .. "(l, " .. camel .. ")\n" + end + + impl = impl .. idt .. "l.SetField(-2, \"" .. name .. "\")\n" + + if condition then + impl = impl .. indent .. "}\n" + end + end + end + + return impl +end + +for name, fields in pairs(parse_spec("client/struct", true)) do + local camel = camel_case(name) + funcs = funcs + .. "func luaPush" .. camel .. "(l *lua.State, val mt." .. camel .. ") {\n\tl.NewTable()\n" + .. push_fields(fields, "\t") + .. "}\n\n" +end + +local to_string_impl = "" +local to_lua_impl = "" + +for name, fields in pairs(parse_spec("client/pkt", true)) do + local case = "\tcase *mt.ToClt" .. camel_case(name) .. ":\n" + + to_string_impl = to_string_impl + .. case .. "\t\treturn \"" .. name .. "\"\n" + + if next(fields) then + to_lua_impl = to_lua_impl .. case .. push_fields(fields, "\t\t") + end +end + +local f = io.open("convert.go", "w") +f:write([[ +// generated by mkconvert.lua, DO NOT EDIT +package main + +import ( + "github.com/Shopify/go-lua" + "github.com/anon55555/mt" +) + +]] .. funcs .. [[ +func pktToString(pkt *mt.Pkt) string { + switch pkt.Cmd.(type) { +]] .. to_string_impl .. [[ + } + panic("impossible") + return "" +} + +func pktToLua(l *lua.State, pkt *mt.Pkt) { + if pkt == nil { + l.PushNil() + return + } + l.NewTable() + l.PushString(pktToString(pkt)) + l.SetField(-2, "_type") + switch val := pkt.Cmd.(type) { +]] .. to_lua_impl .. [[ + } +} +]]) +f:close() diff --git a/poll.go b/poll.go new file mode 100644 index 0000000..bfbe298 --- /dev/null +++ b/poll.go @@ -0,0 +1,89 @@ +package main + +import ( + "github.com/Shopify/go-lua" + "github.com/anon55555/mt" + "reflect" + "time" +) + +func l_poll(l *lua.State) int { + clients := make([]*Client, 0) + + lua.CheckType(l, 1, lua.TypeTable) + i := 1 + for { + l.RawGetInt(1, i) + if l.IsNil(-1) { + l.Pop(1) + break + } + + clients = append(clients, l.ToUserData(-1).(*Client)) + i++ + } + + var timeout time.Duration + hasTimeout := false + if l.IsNumber(3) { + timeout = time.Duration(lua.CheckNumber(l, 3) * float64(time.Second)) + hasTimeout = true + } + + for { + cases := make([]reflect.SelectCase, 0, len(clients)+2) + + for _, client := range clients { + if client.state != csConnected { + continue + } + + cases = append(cases, reflect.SelectCase{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(client.queue), + }) + } + + offset := len(cases) + + if offset < 1 { + l.PushBoolean(false) + return 1 + } + + cases = append(cases, reflect.SelectCase{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(signalChannel()), + }) + + if hasTimeout { + cases = append(cases, reflect.SelectCase{ + Dir: reflect.SelectRecv, + Chan: reflect.ValueOf(time.After(timeout)), + }) + } + + idx, value, ok := reflect.Select(cases) + + if idx >= offset { + l.PushBoolean(true) + return 1 + } + + client := clients[idx] + + var pkt *mt.Pkt = nil + if ok { + pkt = value.Interface().(*mt.Pkt) + } else { + client.state = csDisconnected + } + + for _, handler := range client.handlers { + handler.handle(pkt, l, idx+1) + } + } + + panic("impossible") + return 0 +} diff --git a/spec/casemap b/spec/casemap new file mode 100644 index 0000000..c72df40 --- /dev/null +++ b/spec/casemap @@ -0,0 +1,23 @@ +id ID +ao AO +hud HUD +hp HP +fov FOV +srp SRP +sha1 SHA1 +ao_rm_add AORmAdd +ao_msgs AOMsgs +src_aoid SrcAOID +ao_collision AOCollision +add_hud AddHUD +rm_hud RmHUD +change_hud ChangeHUD +hud_flags HUDFlags +hud_type HUDType +hud_field HUDField +first_srp FirstSRP +csm_restriction_flags CSMRestrictionFlags +srp_bytes_salt_b SRPBytesSaltB +no_csms NoCSMs +join_ok JoinOK +leave_ok LeaveOK diff --git a/spec/client/enum b/spec/client/enum new file mode 100644 index 0000000..cd5f166 --- /dev/null +++ b/spec/client/enum @@ -0,0 +1,70 @@ +kick_reason + wrong_passwd + unexpected_data + srv_is_singleplayer + unsupported_ver + bad_name_chars + bad_name + too_many_clts + empty_passwd + already_connected + srv_err + custom + shutdown + crash +chat_msg_type + postfix Msg + raw + normal + announce + sys +sound_src_type + postfix Src + no + pos + ao +anim_type + postfix Anim + no + vertical_frame + sprite_sheet +hud_type + postfix HUD + img + text + statbar + inv + waypoint + img_waypoint +hud_field + prefix HUD + pos + name + scale + text + number + item + dir + align + offset + world_pos + size + z_index + text_2 +hotbar_param + prefix Hotbar + size + img + sel_img +mod_chan_sig + join_ok + join_fail + leave_ok + leave_fail + not_registered + set_state +player_list_update_type + postfix Players + init + add + remove diff --git a/spec/client/flag b/spec/client/flag new file mode 100644 index 0000000..e13f4da --- /dev/null +++ b/spec/client/flag @@ -0,0 +1,19 @@ +auth_methods + legacy_passwd + srp + first_srp +csm_restriction_flags + no_csms + no_chat_msgs + no_node_defs + limit_map_range + no_player_list +hud_flags + prefix Show + hotbar + health_bar + crosshair + wielded_item + breath_bar + minimap + radar_minimap diff --git a/spec/client/pkt b/spec/client/pkt new file mode 100644 index 0000000..bc04127 --- /dev/null +++ b/spec/client/pkt @@ -0,0 +1,276 @@ +hello + integer serialize_ver + integer compression + integer proto_ver + auth_methods auth_methods + string username +accept_auth + vec3 player_pos + # int64 + number map_seed + number send_interval + auth_methods sudo_auth_methods +accept_sudo_mode +deny_sudo_mode +kick + kick_reason reason + [custom] dr := val.Reason; dr == mt.Custom || dr == mt.Shutdown || dr == mt.Crash + string custom + [reconnect] dr := val.Reason; dr == mt.Shutdown || dr == mt.Crash + boolean reconnect +blk_data + vec3 blkpos + # TODO +add_node + vec3 pos + node node + boolean keep_meta +remove_node + vec3 pos +inv + string inv +time_of_day + integer time + number speed +csm_restriction_flags + csm_restriction_flags flags + integer map_range +add_player_vel + vec3 vel +media_push + fixed_string sha1 + string filename + boolean should_cache + string data +chat_msg + chat_msg_type type + string sender + string text + # int64 + number timestamp +ao_rm_add + # TODO +ao_msgs + # TODO +hp + integer hp +move_player + vec3 pos + number pitch + number yaw +legacy_kick + string reason +fov + number fov + boolean multiplier + number transition_time +death_screen + boolean point_cam + vec3 point_at +media + # TODO +node_defs + # TODO +announce_media + # TODO +item_defs + # TODO +play_sound + integer id + string name + number gain + sound_src_type src_type + vec3 pos + integer src_aoid + boolean loop + number fade + number pitch + boolean ephemeral +stop_sound + integer id +privs + string_set privs +inv_formspec + string formspec +detached_inv + string name + boolean keep + integer len + string inv +show_formspec + string formspec + string formname +movement + number default_accel + number air_accel + number fast_accel + number walk_speed + number crouch_speed + number fast_speed + number climb_speed + number jump_speed + number fluidity + number smoothing + number sink + number gravity +spawn_particle + vec3 pos + vec3 vel + vec3 acc + number expiration_time + number size + boolean collide + string texture + boolean vertical + boolean collision_rm + tile_anim anim_params + integer glow + boolean ao_collision + integer node_param0 + integer node_param2 + integer node_tile +add_particle_spawner + integer amount + number duration + box3 pos + box3 vel + box3 acc + box1 expiration_time + box1 size + boolean collide + string texture + integer id + boolean vertical + boolean collision_rm + tile_anim anim_params + integer glow + boolean ao_collision + integer node_param0 + integer node_param2 + integer node_tile +add_hud + integer id + hud hud +rm_hud + integer id +change_hud + integer id + hud_field field + [pos] val.Field == mt.HUDPos + [name] val.Field == mt.HUDName + [text] val.Field == mt.HUDText + [number] val.Field == mt.HUDNumber + [item] val.Field == mt.HUDItem + [dir] val.Field == mt.HUDDir + [align] val.Field == mt.HUDAlign + [offset] val.Field == mt.HUDOffset + [world_pos] val.Field == mt.HUDWorldPos + [size] val.Field == mt.HUDSize + [z_index] val.Field == mt.HUDZIndex + [text_2] val.Field == mt.HUDText2 + vec2 pos + string name + string text + integer number + integer item + integer dir + vec2 align + vec2 offset + vec3 world_pos + vec2 size + integer z_index + string text_2 +hud_flags + hud_flags flags + hud_flags mask +set_hotbar_param + hotbar_param param + integer size + string img +breath + integer breath +sky_params + color bg_color + string type + boolean clouds + color sun_fog_tint + color moon_fog_tint + string fog_tint_type + [textures] val.Type == "skybox" + texture_list textures + [day_sky] val.Type == "regular" + [day_horizon] val.Type == "regular" + [dawn_sky] val.Type == "regular" + [dawn_horizon] val.Type == "regular" + [night_sky] val.Type == "regular" + [night_horizon] val.Type == "regular" + [indoor] val.Type == "regular" + color day_sky + color day_horizon + color dawn_sky + color dawn_horizon + color night_sky + color night_horizon + color indoor +override_day_night_ratio + boolean override + integer ratio +local_player_anim + box1 idle + box1 walk + box1 dig + box1 walk_dig + number speed +eye_offset + vec3 first + vec3 third +del_particle_spawner + integer id +cloud_params + number density + color diffuse_color + color ambient_color + number height + number thickness + vec2 speed +fade_sound + integer id + number step + number gain +update_player_list + player_list_update_type type + string_list players +mod_chan_msg + string channel + string sender + string msg +mod_chan_sig + mod_chan_sig signal + string channel +node_metas_changed + # TODO +sun_params + boolean visible + string texture + string tone_map + string rise + boolean rising + number size +moon_params + boolean visible + string texture + string tone_map + number size +star_params + boolean visible + integer count + color color + number size +srp_bytes_salt_b + string salt + string b +formspec_prepend + string prepend +minimap_modes + # TODO +disco diff --git a/spec/client/struct b/spec/client/struct new file mode 100644 index 0000000..1fa149c --- /dev/null +++ b/spec/client/struct @@ -0,0 +1,24 @@ +node + integer param0 + integer param1 + integer param2 +tile_anim + anim_type type + vec2 aspect_ratio + vec2 n_frames + number duration +hud + hud_type type + vec2 pos + string name + vec2 scale + string text + integer number + integer item + integer dir + vec2 align + vec2 offset + vec3 world_pos + vec2 size + integer z_index + string text_2 diff --git a/types.go b/types.go new file mode 100644 index 0000000..6410457 --- /dev/null +++ b/types.go @@ -0,0 +1,81 @@ +package main + +import ( + "github.com/Shopify/go-lua" + "github.com/anon55555/mt" + "image/color" +) + +func luaPushVec2(l *lua.State, val [2]float64) { + l.Global("vec2") + l.PushNumber(val[0]) + l.PushNumber(val[1]) + l.Call(2, 1) +} + +func luaPushVec3(l *lua.State, val [3]float64) { + l.Global("vec3") + l.PushNumber(val[0]) + l.PushNumber(val[1]) + l.PushNumber(val[2]) + l.Call(3, 1) +} + +func luaPushBox1(l *lua.State, val [2]float64) { + l.Global("box") + l.PushNumber(val[0]) + l.PushNumber(val[1]) + l.Call(2, 1) +} + +func luaPushBox2(l *lua.State, val [2][2]float64) { + l.Global("box") + luaPushVec2(l, val[0]) + luaPushVec2(l, val[1]) + l.Call(2, 1) +} + +func luaPushBox3(l *lua.State, val [2][3]float64) { + l.Global("box") + luaPushVec3(l, val[0]) + luaPushVec3(l, val[1]) + l.Call(2, 1) +} + +func luaPushColor(l *lua.State, val color.NRGBA) { + l.NewTable() + l.PushInteger(int(val.R)) + l.SetField(-2, "r") + l.PushInteger(int(val.G)) + l.SetField(-2, "g") + l.PushInteger(int(val.B)) + l.SetField(-2, "b") + l.PushInteger(int(val.A)) + l.SetField(-2, "a") +} + +func luaPushStringSet(l *lua.State, val []string) { + l.NewTable() + for _, str := range val { + l.PushBoolean(true) + l.SetField(-2, str) + } +} + +func luaPushStringList(l *lua.State, val []string) { + l.NewTable() + for i, str := range val { + l.PushString(str) + l.RawSetInt(-2, i+1) + } +} + +// i hate go for making me do this instead of just using luaPushStringList +// but i dont want to make an unsafe cast either +func luaPushTextureList(l *lua.State, val []mt.Texture) { + l.NewTable() + for i, str := range val { + l.PushString(string(str)) + l.RawSetInt(-2, i+1) + } +} -- cgit v1.2.3