From e8c35eb2780cf17890f2905f44d1a1d170c40b37 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Sun, 29 May 2022 23:09:53 +0200 Subject: [PATCH] Implement sending of packets --- builtin/client.lua | 11 ++ builtin/vector.lua | 34 ++++- client.go | 18 ++- example/chat-client.lua | 13 +- example/dump-traffic.lua | 9 +- fromlua/generate.lua | 198 ++++++++++++++++++++++++ fromlua/generated.go | 320 +++++++++++++++++++++++++++++++++++++++ fromlua/static.go | 59 ++++++++ hydra.go | 4 + parse_spec.lua | 7 +- spec/casemap | 5 + spec/client/pkt | 46 +++--- spec/server/enum | 7 + spec/server/flag | 12 ++ spec/server/pkt | 63 ++++++++ spec/server/struct | 8 + tolua/generate.lua | 38 +++-- 17 files changed, 781 insertions(+), 71 deletions(-) create mode 100644 builtin/client.lua create mode 100755 fromlua/generate.lua create mode 100644 fromlua/generated.go create mode 100644 fromlua/static.go create mode 100644 spec/server/enum create mode 100644 spec/server/flag create mode 100644 spec/server/pkt create mode 100644 spec/server/struct diff --git a/builtin/client.lua b/builtin/client.lua new file mode 100644 index 0000000..b4ed556 --- /dev/null +++ b/builtin/client.lua @@ -0,0 +1,11 @@ +--[[ builtin/client.lua ]]-- +function package.loaded.client() + local address, name, password = unpack(arg) + local client = hydra.client(address) + + client:enable("auth") + client.auth:username(name) + client.auth:password(password or "") + + return client +end diff --git a/builtin/vector.lua b/builtin/vector.lua index 95e1ebd..4991c1e 100644 --- a/builtin/vector.lua +++ b/builtin/vector.lua @@ -34,6 +34,14 @@ function mt_vec2:__tostring() return "(" .. self.x .. ", " .. self.y .. ")" end +mt_vec2.__index = { + validate = function(self) + assert(type(self.x) == "number") + assert(type(self.y) == "number") + return self + end +} + function vec2(a, b) local o = {} @@ -46,7 +54,7 @@ function vec2(a, b) end setmetatable(o, mt_vec2) - return o + return o:validate() end -- vec3 @@ -57,7 +65,7 @@ local mt_vec3 = arith_mt(function(op) 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 ]] end) @@ -69,6 +77,15 @@ function mt_vec3:__tostring() return "(" .. self.x .. ", " .. self.y .. ", " .. self.z .. ")" end +mt_vec3.__index = { + validate = function(self) + assert(type(self.x) == "number") + assert(type(self.y) == "number") + assert(type(self.z) == "number") + return self + end +} + function vec3(a, b, c) local o = {} @@ -83,7 +100,7 @@ function vec3(a, b, c) end setmetatable(o, mt_vec3) - return o + return o:validate() end -- box @@ -108,6 +125,15 @@ mt_box.__index = { return a.min <= b.min and a.max >= b.max end end, + validate = function(self) + if type(self.min) == "number" then + assert(type(self.max) == "number") + else + assert(not self.min.z == not self.max.z) + self.min:validate() + self.max:validate() + end + end, } function box(a, b) @@ -122,5 +148,5 @@ function box(a, b) end setmetatable(o, mt_box) - return o + return o:validate() end diff --git a/client.go b/client.go index 69a4c29..341f564 100644 --- a/client.go +++ b/client.go @@ -3,6 +3,7 @@ package main import ( "errors" "github.com/anon55555/mt" + "github.com/dragonfireclient/hydra-dragonfire/fromlua" "github.com/dragonfireclient/hydra-dragonfire/tolua" "github.com/yuin/gopher-lua" "net" @@ -46,6 +47,7 @@ var clientFuncs = map[string]lua.LGFunction{ "subscribe": l_client_subscribe, "unsubscribe": l_client_unsubscribe, "wildcard": l_client_wildcard, + "send": l_client_send, } func getClient(l *lua.LState) *Client { @@ -254,20 +256,24 @@ func l_client_wildcard(l *lua.LState) int { return 0 } -/* - func l_client_send(l *lua.LState) int { client := getClient(l) - pkt := fromlua.Pkt(l.CheckTable(2)) + cmd := fromlua.Cmd(l) + doAck := l.ToBool(4) client.mu.Lock() defer client.mu.Unlock() if client.state == csConnected { - client.conn.Send(pkt) + ack, err := client.conn.SendCmd(cmd) + if err != nil { + panic(err) + } + + if doAck && !cmd.DefaultPktInfo().Unrel { + <-ack + } } return 0 } - -*/ diff --git a/example/chat-client.lua b/example/chat-client.lua index 7acb9a6..b1bd505 100755 --- a/example/chat-client.lua +++ b/example/chat-client.lua @@ -1,21 +1,18 @@ #!/usr/bin/env hydra-dragonfire local escapes = require("escapes") -local address, name, password = unpack(arg) -local client = hydra.client(address) - -client:enable("auth") -client.auth:username(name) -client.auth:password(password or "") +local client = require("client")() client:subscribe("chat_msg") client:connect() while not hydra.canceled() do - local pkt, interrupt = client:poll() + local pkt, interrupt = client:poll(1) if pkt then print(escapes.strip_all(pkt.text)) - elseif not interrupt then + elseif interrupt then + client:send("chat_msg", {msg = "test"}) + else print("disconnected") break end diff --git a/example/dump-traffic.lua b/example/dump-traffic.lua index 1fd4316..648aa4d 100755 --- a/example/dump-traffic.lua +++ b/example/dump-traffic.lua @@ -1,10 +1,5 @@ #!/usr/bin/env hydra-dragonfire -local address, name, password = unpack(arg) -local client = hydra.client(address) - -client:enable("auth") -client.auth:username(name) -client.auth:password(password or "") +local client = require("client")() client:wildcard(true) client:connect() @@ -21,7 +16,7 @@ while not hydra.canceled() do end elseif not interrupt then print("disconnected") - break + break end end diff --git a/fromlua/generate.lua b/fromlua/generate.lua new file mode 100755 index 0000000..75899b7 --- /dev/null +++ b/fromlua/generate.lua @@ -0,0 +1,198 @@ +#!/usr/bin/env lua +dofile("../parse_spec.lua") + +local readers = { + SliceByte = true, + Byte = true, + String = true, + SliceField = true, + Field = true, + Bool = true, + PointedThing = true, +} + +local static_uses = { + "[3]int16", + "AOID" +} + +local function generate(name) + local fnname, index, child, childfn, childtype + local type = name + + local open = name:find("%[") + local clos = name:find("%]") + + if open == 1 then + index = name:sub(open + 1, clos - 1) + child = name:sub(clos + 1) + childfn, childtype = generate(child) + fnname = (index == "" and "Slice" or "Vec" .. index) .. childfn + + type = "[" .. index .. "]" .. childtype + else + fnname = camel_case(name) + + local c = name:sub(1, 1) + if c == c:upper() then + type = "mt." .. name + end + end + + if not readers[fnname] then + local fun = "func read" .. fnname .. "(l *lua.LState, val lua.LValue, ptr *" .. type .. ") {\n" + + if child then + fun = fun .. "\tif val.Type() != lua.LTTable {\n\t\tpanic(\"invalid value for " + .. name .. ": must be a table\")\n\t}\n" + + if index == "" then + fun = fun .. +[[ + tbl := val.(*lua.LTable) + n := tbl.MaxN() + *ptr = make(]] .. type .. [[, n) + for i := range *ptr { + read]] .. childfn .. [[(l, l.RawGetInt(tbl, i+1), &(*ptr)[i]) + } +]] + else + local n = tonumber(index) + for i, v in ipairs({"x", "y", "z"}) do + if i > n then + break + end + + fun = fun + .. "\tread" .. childfn + .. "(l, l.GetField(val, \"" .. v .. "\"), &(*ptr)[" .. (i - 1) .. "])\n" + end + end + else + fun = fun .. "\tif val.Type() != lua.LTNumber {\n\t\tpanic(\"invalid value for " + .. name .. ": must be a number\")\n\t}\n" + .. "\t*ptr = " .. type .. "(val.(lua.LNumber))\n" + end + + fun = fun .. "}\n\n" + + readers[fnname] = fun + end + + return fnname, type +end + +for _, use in ipairs(static_uses) do + generate(use) +end + +local function signature(name, prefix, type) + local camel = camel_case(name) + return "func read" .. camel .. "(l *lua.LState, val lua.LValue, ptr *" .. prefix .. camel .. ") {\n" +end + +for name, fields in spairs(parse_spec("server/enum")) do + local camel = camel_case(name) + local fun = signature(name, "mt.") + + local impl = "" + for _, var in ipairs(fields) do + local equals = "*ptr = mt." .. apply_prefix(fields, var) .. "\n" + + if var == "no" then + fun = fun .. "\tif val.Type() == lua.LTNil {\n\t\t" .. equals .. "\t\treturn\n\t}\n" + else + impl = impl .. "\tcase \"" .. var .. "\":\n\t\t" .. equals + end + end + + fun = fun + .. "\tif val.Type() != lua.LTString {\n\t\tpanic(\"invalid value for " + .. camel .. ": must be a string\")\n\t}\n" + .. "\tstr := string(val.(lua.LString))\n" + .. "\tswitch str {\n" .. impl + .. "\tdefault:\n\t\tpanic(\"invalid value for " .. name .. ": \" + str)\n\t}\n}\n\n" + + readers[camel] = fun +end + +for name, fields in spairs(parse_spec("server/flag")) do + local camel = camel_case(name) + local fun = signature(name, "mt.") + .. "\tif val.Type() != lua.LTTable {\n\t\tpanic(\"invalid value for " + .. camel .. ": must be a table\")\n\t}\n" + + for _, var in ipairs(fields) do + fun = fun .. "\tif l.GetField(val, \"" .. var .. "\") == lua.LTrue {\n" + .. "\t\t*ptr = *ptr | mt." .. apply_prefix(fields, var) .. "\n\t}\n" + end + + fun = fun .. "}\n\n" + readers[camel] = fun +end + +local function fields_fromlua(fields, indent) + local impl = "" + + for name, type in spairs(fields) do + impl = impl .. indent .. "read" .. generate(type) .. "(l, l.GetField(val, \"" .. name .. "\"), &ptr." + .. camel_case(name) .. ")\n" + end + + return impl +end + +for name, fields in spairs(parse_spec("server/struct", true)) do + local camel = camel_case(name) + readers[camel] = signature(name, "mt.") + .. "\tif val.Type() != lua.LTTable {\n" + .. "\t\tpanic(\"invalid value for " .. camel .. ": must be a table\")\n\t}\n" + .. fields_fromlua(fields, "\t") + .. "}\n\n" +end + +local pkt_impl = "" + +for name, fields in spairs(parse_spec("server/pkt", true)) do + pkt_impl = pkt_impl + .. "\tcase \"" .. name .. "\"" .. "" .. ":\n" + .. "\t\tptr := &mt.ToSrv" .. camel_case(name) .. "{}\n" + + if next(fields) then + pkt_impl = pkt_impl + .. "\t\tval := l.CheckTable(3)\n" + .. fields_fromlua(fields, "\t\t") + end + + pkt_impl = pkt_impl + .. "\t\treturn ptr\n" +end + +local funcs = "" +for _, fn in spairs(readers) do + if type(fn) == "string" then + funcs = funcs .. fn + end +end + +local f = io.open("generated.go", "w") +f:write([[ +// generated by generate.lua, DO NOT EDIT +package fromlua + +import ( + "github.com/anon55555/mt" + "github.com/yuin/gopher-lua" +) + +]] .. funcs .. [[ +func Cmd(l *lua.LState) mt.Cmd { + str := l.CheckString(2) + switch str { +]] .. pkt_impl .. [[ + } + + panic("invalid packet type: " + str) +} +]]) +f:close() diff --git a/fromlua/generated.go b/fromlua/generated.go new file mode 100644 index 0000000..c93c229 --- /dev/null +++ b/fromlua/generated.go @@ -0,0 +1,320 @@ +// generated by generate.lua, DO NOT EDIT +package fromlua + +import ( + "github.com/anon55555/mt" + "github.com/yuin/gopher-lua" +) + +func readAOID(l *lua.LState, val lua.LValue, ptr *mt.AOID) { + if val.Type() != lua.LTNumber { + panic("invalid value for AOID: must be a number") + } + *ptr = mt.AOID(val.(lua.LNumber)) +} + +func readCompressionModes(l *lua.LState, val lua.LValue, ptr *mt.CompressionModes) { + if val.Type() != lua.LTNumber { + panic("invalid value for CompressionModes: must be a number") + } + *ptr = mt.CompressionModes(val.(lua.LNumber)) +} + +func readInt16(l *lua.LState, val lua.LValue, ptr *int16) { + if val.Type() != lua.LTNumber { + panic("invalid value for int16: must be a number") + } + *ptr = int16(val.(lua.LNumber)) +} + +func readInt32(l *lua.LState, val lua.LValue, ptr *int32) { + if val.Type() != lua.LTNumber { + panic("invalid value for int32: must be a number") + } + *ptr = int32(val.(lua.LNumber)) +} + +func readInteraction(l *lua.LState, val lua.LValue, ptr *mt.Interaction) { + if val.Type() != lua.LTString { + panic("invalid value for Interaction: must be a string") + } + str := string(val.(lua.LString)) + switch str { + case "dig": + *ptr = mt.Dig + case "stop_digging": + *ptr = mt.StopDigging + case "dug": + *ptr = mt.Dug + case "place": + *ptr = mt.Place + case "use": + *ptr = mt.Use + case "activate": + *ptr = mt.Activate + default: + panic("invalid value for interaction: " + str) + } +} + +func readKeys(l *lua.LState, val lua.LValue, ptr *mt.Keys) { + if val.Type() != lua.LTTable { + panic("invalid value for Keys: must be a table") + } + if l.GetField(val, "forward") == lua.LTrue { + *ptr = *ptr | mt.ForwardKey + } + if l.GetField(val, "backward") == lua.LTrue { + *ptr = *ptr | mt.BackwardKey + } + if l.GetField(val, "left") == lua.LTrue { + *ptr = *ptr | mt.LeftKey + } + if l.GetField(val, "right") == lua.LTrue { + *ptr = *ptr | mt.RightKey + } + if l.GetField(val, "jump") == lua.LTrue { + *ptr = *ptr | mt.JumpKey + } + if l.GetField(val, "special") == lua.LTrue { + *ptr = *ptr | mt.SpecialKey + } + if l.GetField(val, "sneak") == lua.LTrue { + *ptr = *ptr | mt.SneakKey + } + if l.GetField(val, "dig") == lua.LTrue { + *ptr = *ptr | mt.DigKey + } + if l.GetField(val, "place") == lua.LTrue { + *ptr = *ptr | mt.PlaceKey + } + if l.GetField(val, "zoom") == lua.LTrue { + *ptr = *ptr | mt.ZoomKey + } +} + +func readPlayerPos(l *lua.LState, val lua.LValue, ptr *mt.PlayerPos) { + if val.Type() != lua.LTTable { + panic("invalid value for PlayerPos: must be a table") + } + readUint8(l, l.GetField(val, "fov80"), &ptr.FOV80) + readKeys(l, l.GetField(val, "keys"), &ptr.Keys) + readInt32(l, l.GetField(val, "pitch100"), &ptr.Pitch100) + readVec3Int32(l, l.GetField(val, "pos100"), &ptr.Pos100) + readVec3Int32(l, l.GetField(val, "vel100"), &ptr.Vel100) + readUint8(l, l.GetField(val, "wanted_range"), &ptr.WantedRange) + readInt32(l, l.GetField(val, "yaw100"), &ptr.Yaw100) +} + +func readSliceSoundID(l *lua.LState, val lua.LValue, ptr *[]mt.SoundID) { + if val.Type() != lua.LTTable { + panic("invalid value for []SoundID: must be a table") + } + tbl := val.(*lua.LTable) + n := tbl.MaxN() + *ptr = make([]mt.SoundID, n) + for i := range *ptr { + readSoundID(l, l.RawGetInt(tbl, i+1), &(*ptr)[i]) + } +} + +func readSliceString(l *lua.LState, val lua.LValue, ptr *[]string) { + if val.Type() != lua.LTTable { + panic("invalid value for []string: must be a table") + } + tbl := val.(*lua.LTable) + n := tbl.MaxN() + *ptr = make([]string, n) + for i := range *ptr { + readString(l, l.RawGetInt(tbl, i+1), &(*ptr)[i]) + } +} + +func readSliceVec3Int16(l *lua.LState, val lua.LValue, ptr *[][3]int16) { + if val.Type() != lua.LTTable { + panic("invalid value for [][3]int16: must be a table") + } + tbl := val.(*lua.LTable) + n := tbl.MaxN() + *ptr = make([][3]int16, n) + for i := range *ptr { + readVec3Int16(l, l.RawGetInt(tbl, i+1), &(*ptr)[i]) + } +} + +func readSoundID(l *lua.LState, val lua.LValue, ptr *mt.SoundID) { + if val.Type() != lua.LTNumber { + panic("invalid value for SoundID: must be a number") + } + *ptr = mt.SoundID(val.(lua.LNumber)) +} + +func readUint16(l *lua.LState, val lua.LValue, ptr *uint16) { + if val.Type() != lua.LTNumber { + panic("invalid value for uint16: must be a number") + } + *ptr = uint16(val.(lua.LNumber)) +} + +func readUint8(l *lua.LState, val lua.LValue, ptr *uint8) { + if val.Type() != lua.LTNumber { + panic("invalid value for uint8: must be a number") + } + *ptr = uint8(val.(lua.LNumber)) +} + +func readVec3Int16(l *lua.LState, val lua.LValue, ptr *[3]int16) { + if val.Type() != lua.LTTable { + panic("invalid value for [3]int16: must be a table") + } + readInt16(l, l.GetField(val, "x"), &(*ptr)[0]) + readInt16(l, l.GetField(val, "y"), &(*ptr)[1]) + readInt16(l, l.GetField(val, "z"), &(*ptr)[2]) +} + +func readVec3Int32(l *lua.LState, val lua.LValue, ptr *[3]int32) { + if val.Type() != lua.LTTable { + panic("invalid value for [3]int32: must be a table") + } + readInt32(l, l.GetField(val, "x"), &(*ptr)[0]) + readInt32(l, l.GetField(val, "y"), &(*ptr)[1]) + readInt32(l, l.GetField(val, "z"), &(*ptr)[2]) +} + +func Cmd(l *lua.LState) mt.Cmd { + str := l.CheckString(2) + switch str { + case "chat_msg": + ptr := &mt.ToSrvChatMsg{} + val := l.CheckTable(3) + readString(l, l.GetField(val, "msg"), &ptr.Msg) + return ptr + case "clt_ready": + ptr := &mt.ToSrvCltReady{} + val := l.CheckTable(3) + readUint16(l, l.GetField(val, "formspec"), &ptr.Formspec) + readUint8(l, l.GetField(val, "major"), &ptr.Major) + readUint8(l, l.GetField(val, "minor"), &ptr.Minor) + readUint8(l, l.GetField(val, "patch"), &ptr.Patch) + readString(l, l.GetField(val, "version"), &ptr.Version) + return ptr + case "deleted_blks": + ptr := &mt.ToSrvDeletedBlks{} + val := l.CheckTable(3) + readSliceVec3Int16(l, l.GetField(val, "blks"), &ptr.Blks) + return ptr + case "fall_dmg": + ptr := &mt.ToSrvFallDmg{} + val := l.CheckTable(3) + readUint16(l, l.GetField(val, "amount"), &ptr.Amount) + return ptr + case "first_srp": + ptr := &mt.ToSrvFirstSRP{} + val := l.CheckTable(3) + readBool(l, l.GetField(val, "empty_passwd"), &ptr.EmptyPasswd) + readSliceByte(l, l.GetField(val, "salt"), &ptr.Salt) + readSliceByte(l, l.GetField(val, "verifier"), &ptr.Verifier) + return ptr + case "got_blks": + ptr := &mt.ToSrvGotBlks{} + val := l.CheckTable(3) + readSliceVec3Int16(l, l.GetField(val, "blks"), &ptr.Blks) + return ptr + case "init": + ptr := &mt.ToSrvInit{} + val := l.CheckTable(3) + readUint16(l, l.GetField(val, "max_proto_ver"), &ptr.MaxProtoVer) + readUint16(l, l.GetField(val, "min_proto_ver"), &ptr.MinProtoVer) + readString(l, l.GetField(val, "player_name"), &ptr.PlayerName) + readBool(l, l.GetField(val, "send_full_item_meta"), &ptr.SendFullItemMeta) + readUint8(l, l.GetField(val, "serialize_ver"), &ptr.SerializeVer) + readCompressionModes(l, l.GetField(val, "supported_compression"), &ptr.SupportedCompression) + return ptr + case "init2": + ptr := &mt.ToSrvInit2{} + val := l.CheckTable(3) + readString(l, l.GetField(val, "lang"), &ptr.Lang) + return ptr + case "interact": + ptr := &mt.ToSrvInteract{} + val := l.CheckTable(3) + readInteraction(l, l.GetField(val, "action"), &ptr.Action) + readUint16(l, l.GetField(val, "item_slot"), &ptr.ItemSlot) + readPointedThing(l, l.GetField(val, "pointed"), &ptr.Pointed) + readPlayerPos(l, l.GetField(val, "pos"), &ptr.Pos) + return ptr + case "inv_action": + ptr := &mt.ToSrvInvAction{} + val := l.CheckTable(3) + readString(l, l.GetField(val, "action"), &ptr.Action) + return ptr + case "inv_fields": + ptr := &mt.ToSrvInvFields{} + val := l.CheckTable(3) + readSliceField(l, l.GetField(val, "fields"), &ptr.Fields) + readString(l, l.GetField(val, "formname"), &ptr.Formname) + return ptr + case "join_mod_chan": + ptr := &mt.ToSrvJoinModChan{} + val := l.CheckTable(3) + readString(l, l.GetField(val, "channel"), &ptr.Channel) + return ptr + case "leave_mod_chan": + ptr := &mt.ToSrvLeaveModChan{} + val := l.CheckTable(3) + readString(l, l.GetField(val, "channel"), &ptr.Channel) + return ptr + case "msg_mod_chan": + ptr := &mt.ToSrvMsgModChan{} + val := l.CheckTable(3) + readString(l, l.GetField(val, "channel"), &ptr.Channel) + readString(l, l.GetField(val, "msg"), &ptr.Msg) + return ptr + case "nil": + ptr := &mt.ToSrvNil{} + return ptr + case "node_meta_fields": + ptr := &mt.ToSrvNodeMetaFields{} + val := l.CheckTable(3) + readSliceField(l, l.GetField(val, "fields"), &ptr.Fields) + readString(l, l.GetField(val, "formname"), &ptr.Formname) + readVec3Int16(l, l.GetField(val, "pos"), &ptr.Pos) + return ptr + case "player_pos": + ptr := &mt.ToSrvPlayerPos{} + val := l.CheckTable(3) + readPlayerPos(l, l.GetField(val, "pos"), &ptr.Pos) + return ptr + case "removed_sounds": + ptr := &mt.ToSrvRemovedSounds{} + val := l.CheckTable(3) + readSliceSoundID(l, l.GetField(val, "ids"), &ptr.IDs) + return ptr + case "req_media": + ptr := &mt.ToSrvReqMedia{} + val := l.CheckTable(3) + readSliceString(l, l.GetField(val, "filenames"), &ptr.Filenames) + return ptr + case "respawn": + ptr := &mt.ToSrvRespawn{} + return ptr + case "select_item": + ptr := &mt.ToSrvSelectItem{} + val := l.CheckTable(3) + readUint16(l, l.GetField(val, "slot"), &ptr.Slot) + return ptr + case "srp_bytes_a": + ptr := &mt.ToSrvSRPBytesA{} + val := l.CheckTable(3) + readSliceByte(l, l.GetField(val, "a"), &ptr.A) + readBool(l, l.GetField(val, "no_sha1"), &ptr.NoSHA1) + return ptr + case "srp_bytes_m": + ptr := &mt.ToSrvSRPBytesM{} + val := l.CheckTable(3) + readSliceByte(l, l.GetField(val, "m"), &ptr.M) + return ptr + } + + panic("invalid packet type: " + str) +} diff --git a/fromlua/static.go b/fromlua/static.go new file mode 100644 index 0000000..b989db8 --- /dev/null +++ b/fromlua/static.go @@ -0,0 +1,59 @@ +package fromlua + +import ( + "github.com/anon55555/mt" + "github.com/yuin/gopher-lua" +) + +//go:generate ./generate.lua + +func readBool(l *lua.LState, val lua.LValue, ptr *bool) { + if val.Type() != lua.LTBool { + panic("invalid value for bool: must be a boolean") + } + *ptr = bool(val.(lua.LBool)) +} + +func readString(l *lua.LState, val lua.LValue, ptr *string) { + if val.Type() != lua.LTString { + panic("invalid value for string: must be a string") + } + *ptr = string(val.(lua.LString)) +} + +func readSliceByte(l *lua.LState, val lua.LValue, ptr *[]byte) { + if val.Type() != lua.LTString { + panic("invalid value for []byte: must be a string") + } + *ptr = []byte(val.(lua.LString)) +} + +func readSliceField(l *lua.LState, val lua.LValue, ptr *[]mt.Field) { + if val.Type() != lua.LTTable { + panic("invalid value for []Field: must be a table") + } + val.(*lua.LTable).ForEach(func(k, v lua.LValue) { + if k.Type() != lua.LTString || v.Type() != lua.LTString { + panic("invalid value for Field: key and value must be strings") + } + *ptr = append(*ptr, mt.Field{Name: string(k.(lua.LString)), Value: string(v.(lua.LString))}) + }) +} + +func readPointedThing(l *lua.LState, val lua.LValue, ptr *mt.PointedThing) { + if val.Type() != lua.LTTable { + panic("invalid value for PointedThing: must be a table") + } + id := l.GetField(val, "id") + + if id == lua.LNil { + pt := &mt.PointedAO{} + readAOID(l, id, &(*pt).ID) + *ptr = pt + } else { + pt := &mt.PointedNode{} + readVec3Int16(l, l.GetField(val, "under"), &(*pt).Under) + readVec3Int16(l, l.GetField(val, "above"), &(*pt).Above) + *ptr = pt + } +} diff --git a/hydra.go b/hydra.go index 5deb1d4..2f976e2 100644 --- a/hydra.go +++ b/hydra.go @@ -22,10 +22,14 @@ var builtinVector string //go:embed builtin/escapes.lua var builtinEscapes string +//go:embed builtin/client.lua +var builtinClient string + var builtinFiles = []string{ builtinLuaX, builtinVector, builtinEscapes, + builtinClient, } var hydraFuncs = map[string]lua.LGFunction{ diff --git a/parse_spec.lua b/parse_spec.lua index 449360c..588f4d7 100644 --- a/parse_spec.lua +++ b/parse_spec.lua @@ -9,7 +9,7 @@ local function snext(t, state) end end table.sort(t.__sorted) - + key = t.__sorted[1] else for i, v in ipairs(t.__sorted) do @@ -41,7 +41,7 @@ local function parse_pair(pair, value_first) if idx then local first, second = pair:sub(1, idx - 1), pair:sub(idx + 1) - if value_first and first:sub(1, 1) ~= "[" then + if value_first and first:sub(1, 1) ~= "{" then return second, first else return first, second @@ -103,3 +103,6 @@ function camel_case(snake) return camel end +function apply_prefix(fields, str) + return (fields.prefix or "") .. camel_case(str) .. (fields.postfix or "") +end diff --git a/spec/casemap b/spec/casemap index c72df40..ab4c10a 100644 --- a/spec/casemap +++ b/spec/casemap @@ -18,6 +18,11 @@ hud_field HUDField first_srp FirstSRP csm_restriction_flags CSMRestrictionFlags srp_bytes_salt_b SRPBytesSaltB +srp_bytes_a SRPBytesA +srp_bytes_m SRPBytesM no_csms NoCSMs join_ok JoinOK leave_ok LeaveOK +no_sha1 NoSHA1 +fov80 FOV80 +ids IDs diff --git a/spec/client/pkt b/spec/client/pkt index e7c609b..2a150e3 100644 --- a/spec/client/pkt +++ b/spec/client/pkt @@ -6,7 +6,6 @@ hello string username accept_auth vec3 player_pos - # int64 number map_seed number send_interval auth_methods sudo_auth_methods @@ -14,9 +13,9 @@ accept_sudo_mode deny_sudo_mode kick kick_reason reason - [custom] val.Reason == mt.Custom || val.Reason == mt.Shutdown || val.Reason == mt.Crash + {custom} val.Reason == mt.Custom || val.Reason == mt.Shutdown || val.Reason == mt.Crash string custom - [reconnect] val.Reason == mt.Shutdown || val.Reason == mt.Crash + {reconnect} val.Reason == mt.Shutdown || val.Reason == mt.Crash boolean reconnect blk_data vec3 blkpos @@ -46,7 +45,6 @@ chat_msg chat_msg_type type string sender string text - # int64 number timestamp ao_rm_add # TODO @@ -156,18 +154,18 @@ rm_hud change_hud number 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 + {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 @@ -196,15 +194,15 @@ sky_params color sun_fog_tint color moon_fog_tint string fog_tint_type - [textures] val.Type == "skybox" + {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" + {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 diff --git a/spec/server/enum b/spec/server/enum new file mode 100644 index 0000000..6f49e6d --- /dev/null +++ b/spec/server/enum @@ -0,0 +1,7 @@ +interaction + dig + stop_digging + dug + place + use + activate diff --git a/spec/server/flag b/spec/server/flag new file mode 100644 index 0000000..4f8c164 --- /dev/null +++ b/spec/server/flag @@ -0,0 +1,12 @@ +keys + postfix Key + forward + backward + left + right + jump + special + sneak + dig + place + zoom diff --git a/spec/server/pkt b/spec/server/pkt new file mode 100644 index 0000000..5578d55 --- /dev/null +++ b/spec/server/pkt @@ -0,0 +1,63 @@ +nil +init + uint8 serialize_ver + CompressionModes supported_compression + uint16 min_proto_ver + uint16 max_proto_ver + string player_name + bool send_full_item_meta +init2 + string lang +join_mod_chan + string channel +leave_mod_chan + string channel +msg_mod_chan + string channel + string msg +player_pos + PlayerPos pos +got_blks + [][3]int16 blks +deleted_blks + [][3]int16 blks +inv_action + string action +chat_msg + string msg +fall_dmg + uint16 amount +select_item + uint16 slot +respawn +interact + Interaction action + uint16 item_slot + PointedThing pointed + PlayerPos pos +removed_sounds + []SoundID ids +node_meta_fields + [3]int16 pos + string formname + []Field fields +inv_fields + string formname + []Field fields +req_media + []string filenames +clt_ready + uint8 major + uint8 minor + uint8 patch + string version + uint16 formspec +first_srp + []byte salt + []byte verifier + bool empty_passwd +srp_bytes_a + []byte a + bool no_sha1 +srp_bytes_m + []byte m diff --git a/spec/server/struct b/spec/server/struct new file mode 100644 index 0000000..e971dbc --- /dev/null +++ b/spec/server/struct @@ -0,0 +1,8 @@ +player_pos + [3]int32 pos100 + [3]int32 vel100 + int32 pitch100 + int32 yaw100 + Keys keys + uint8 fov80 + uint8 wanted_range diff --git a/tolua/generate.lua b/tolua/generate.lua index 7c2d5a7..3117713 100755 --- a/tolua/generate.lua +++ b/tolua/generate.lua @@ -8,9 +8,8 @@ for name, fields in spairs(parse_spec("client/enum")) do funcs = funcs .. "func " .. camel .. "(l *lua.LState, val mt." .. camel .. ") lua.LValue {\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 "return lua.LNil" or "return lua.LString(\"" .. var .. "\")") .. "\n" + funcs = funcs .. "\tcase mt." .. apply_prefix(fields, var) .. ":\n\t\t" .. + (var == "no" and "return lua.LNil" or "return lua.LString(\"" .. var .. "\")") .. "\n" end funcs = funcs .. "\t}\n\tpanic(\"impossible\")\n\treturn lua.LNil\n}\n\n" @@ -21,15 +20,14 @@ for name, fields in spairs(parse_spec("client/flag")) do funcs = funcs .. "func " .. camel .. "(l *lua.LState, val mt." .. camel .. ") lua.LValue {\n\ttbl := l.NewTable()\n" for _, var in ipairs(fields) do - funcs = funcs .. "\tif val&mt." - .. (fields.prefix or "") .. camel_case(var) .. (fields.postfix or "") + funcs = funcs .. "\tif val&mt." .. apply_prefix(fields, var) .. " != 0 {\n\t\tl.SetField(tbl, \"" .. var .. "\", lua.LTrue)\n\t}\n" end funcs = funcs .. "\treturn tbl\n}\n\n" end -local to_lua = { +local tolua = { string = "lua.LString(string(VAL))", fixed_string = "lua.LString(string(VAL[:]))", boolean = "lua.LBool(VAL)", @@ -41,24 +39,24 @@ local to_lua = { box3 = "Box3(l, [2][3]lua.LNumber{{lua.LNumber(VAL[0][0]), lua.LNumber(VAL[0][1]), lua.LNumber(VAL[0][2])}, {lua.LNumber(VAL[1][0]), lua.LNumber(VAL[1][1]), lua.LNumber(VAL[1][2])}})", } -local function fields_to_lua(fields, indent) +local function fields_tolua(fields, indent) local impl = "" - + for name, type in spairs(fields) do - if name:sub(1, 1) ~= "[" then + if name:sub(1, 1) ~= "{" then local camel = "val." .. camel_case(name) local idt = indent - local condition = fields["[" .. name .. "]"] + local condition = fields["{" .. name .. "}"] if condition then - impl = impl .. indent .. "if " .. condition .. " {\n" + impl = impl .. indent .. "if " .. condition .. " {\n" idt = idt .. "\t" end impl = impl .. idt .. "l.SetField(tbl, \"" .. name .. "\", " - if to_lua[type] then - impl = impl .. to_lua[type]:gsub("VAL", camel) + if tolua[type] then + impl = impl .. tolua[type]:gsub("VAL", camel) else impl = impl .. camel_case(type) .. "(l, " .. camel .. ")" end @@ -77,21 +75,21 @@ for name, fields in spairs(parse_spec("client/struct", true)) do local camel = camel_case(name) funcs = funcs .. "func " .. camel .. "(l *lua.LState, val mt." .. camel .. ") lua.LValue {\n\ttbl := l.NewTable()\n" - .. fields_to_lua(fields, "\t") + .. fields_tolua(fields, "\t") .. "\treturn tbl\n}\n\n" end -local to_string_impl = "" -local to_lua_impl = "" +local pkt_type_impl = "" +local pkt_impl = "" for name, fields in spairs(parse_spec("client/pkt", true)) do local case = "\tcase *mt.ToClt" .. camel_case(name) .. ":\n" - to_string_impl = to_string_impl + pkt_type_impl = pkt_type_impl .. case .. "\t\treturn lua.LString(\"" .. name .. "\")\n" if next(fields) then - to_lua_impl = to_lua_impl .. case .. fields_to_lua(fields, "\t\t") + pkt_impl = pkt_impl .. case .. fields_tolua(fields, "\t\t") end end @@ -108,7 +106,7 @@ import ( ]] .. funcs .. [[ func PktType(pkt *mt.Pkt) lua.LString { switch pkt.Cmd.(type) { -]] .. to_string_impl .. [[ +]] .. pkt_type_impl .. [[ } panic("impossible") return "" @@ -121,7 +119,7 @@ func Pkt(l *lua.LState, pkt *mt.Pkt) lua.LValue { tbl := l.NewTable() l.SetField(tbl, "_type", PktType(pkt)) switch val := pkt.Cmd.(type) { -]] .. to_lua_impl .. [[ +]] .. pkt_impl .. [[ } return tbl } -- 2.44.0