|
| 1 | +function string.fromhex(str) |
| 2 | + return (str:gsub('..', function(cc) |
| 3 | + return string.char(tonumber(cc, 16)) |
| 4 | + end)) |
| 5 | +end |
| 6 | + |
| 7 | +function string.tohex(str) |
| 8 | + return (str:gsub('.', function(c) |
| 9 | + return string.format('%02X', string.byte(c)) |
| 10 | + end)) |
| 11 | +end |
| 12 | + |
| 13 | +function handle(ctx, data) |
| 14 | + |
| 15 | + -- prtocol begins with FFFFFFFF and the packedid |
| 16 | + |
| 17 | + -- get packet index |
| 18 | + |
| 19 | + -- check if start with FFFFFFFF |
| 20 | + |
| 21 | + hex = string.tohex(data) |
| 22 | + |
| 23 | + if string.sub(hex, 1, 8) ~= "FFFFFFFF" then |
| 24 | + debug_print("Invalid Packet " .. hex) |
| 25 | + return |
| 26 | + end |
| 27 | + |
| 28 | + packetId = string.sub(hex, 9, 10) |
| 29 | + |
| 30 | + payload = string.sub(hex, 11) |
| 31 | + |
| 32 | + -- check if packet is 54 |
| 33 | + |
| 34 | + debug_print("Packet ID: " .. packetId) |
| 35 | + |
| 36 | + if packetId == "55" then |
| 37 | + |
| 38 | + if payload == "FFFFFFFF" or payload == "00000000" then |
| 39 | + debug_print("Received Packet: " .. hex) |
| 40 | + resHex = string.fromhex("FFFFFFFF414BA1D522") -- this is not good, as we allways pass the same key for the challenge |
| 41 | + ctx.sendData(resHex) |
| 42 | + return |
| 43 | + end |
| 44 | + |
| 45 | + if payload == "4BA1D522" then |
| 46 | + debug_print("Received Packet: " .. hex) |
| 47 | + resHex = string.fromhex("FFFFFFFF4400") -- this is not good to be hardcoded, but fine for now |
| 48 | + |
| 49 | + ctx.sendData(resHex) |
| 50 | + return |
| 51 | + end |
| 52 | + debug_print("Bad challenge: " .. hex) |
| 53 | + return |
| 54 | + end |
| 55 | + |
| 56 | + if packetId == "56" then |
| 57 | + |
| 58 | + if payload == "FFFFFFFF" or payload == "00000000" then |
| 59 | + debug_print("Received Packet: " .. hex) |
| 60 | + resHex = string.fromhex("FFFFFFFF414BA1D522") -- this is not good, as we allways pass the same key for the challenge |
| 61 | + ctx.sendData(resHex) |
| 62 | + return |
| 63 | + end |
| 64 | + |
| 65 | + if payload == "4BA1D522" then |
| 66 | + debug_print("Received Packet: " .. hex) |
| 67 | + resHex = string.fromhex( |
| 68 | + "FFFFFFFF451A00414C4C4F57444F574E4C4F414443484152535F69003100414C4C4F57444F574E4C4F41444954454D535F69003100436C757374657249645F73004B4150323032326E76637738393233386E3332726677653900435553544F4D5345525645524E414D455F73006B617020707670202F20342D6D616E202F2078352D783235202F20776F726B65727320667269656E646C79207365727665720044617954696D655F730037360047616D654D6F64655F73005465737447616D654D6F64655F43004841534143544956454D4F44535F690031004C45474143595F690030004D4154434854494D454F55545F66003132302E303030303030004D4F44305F7300323839373838353837383A4544393730443545343845324143433334333545374339373345434135373637004D4F44315F7300323536343534363435353A3934413336414236343933453241443335364631343142313932383633453445004D4F44325F7300333034363539363536343A3832453245393730343446444139463642464237353439443730433337423133004D4F44335F7300313939393434373137323A3836453432424644343646453430363338443639344141384342453634344134004D6F6449645F6C0030004E6574776F726B696E675F690030004E554D4F50454E505542434F4E4E003530004F4646494349414C5345525645525F690030004F574E494E474944003930323032313035363131373133353337004F574E494E474E414D45003930323032313035363131373133353337005032504144445200393032303231303536313137313335333700503250504F52540037373837005345415243484B4559574F5244535F7300437573746F6D0053657276657250617373776F72645F620066616C73650053455256455255534553424154544C4559455F6200747275650053455353494F4E464C41475300313730370053455353494F4E49535056455F69003000") -- this is not good to be hardcoded, but fine for now |
| 69 | + |
| 70 | + ctx.sendData(resHex) |
| 71 | + return |
| 72 | + end |
| 73 | + debug_print("Bad challenge: " .. hex) |
| 74 | + return |
| 75 | + end |
| 76 | + |
| 77 | + if packetId == "54" then |
| 78 | + |
| 79 | + |
| 80 | + |
| 81 | + local snapshotMode = get_snapshot_mode() |
| 82 | + local snapshotPercentage = get_snapshot_percentage() |
| 83 | + |
| 84 | + |
| 85 | + queue = get_queue() |
| 86 | + name = get_var("ServerListName") or "Coldstarter is cool (server is idle, join to start)" |
| 87 | + |
| 88 | + map = get_var("MapName") or "server idle" |
| 89 | + |
| 90 | + local finishSec = get_finish_sec() |
| 91 | + |
| 92 | + if finishSec ~= nil then |
| 93 | + finishSec = math.ceil(finishSec) |
| 94 | + end |
| 95 | + |
| 96 | + if snapshotMode ~= "noop" then |
| 97 | + if snapshotMode == "restore" then |
| 98 | + if snapshotPercentage == nil or snapshotPercentage == 100 then |
| 99 | + name = get_var("ServerListNameRestoring") or "EXTRACTING snapshot, this might take a moment" |
| 100 | + map = get_var("MapNameRestoring") or "extracting snapshot" |
| 101 | + else |
| 102 | + name = get_var("ServerListNameRestoring") or "DOWNLOADING snapshot - " .. string.format("%.2f", snapshotPercentage) .. "%" |
| 103 | + map = get_var("MapNameRestoring") or "downloading snapshot" |
| 104 | + end |
| 105 | + else |
| 106 | + if snapshotPercentage == nil or snapshotPercentage == 100 then |
| 107 | + name = get_var("ServerListNameBackingUp") or "BACKING UP, this might take a moment" |
| 108 | + else |
| 109 | + name = get_var("ServerListNameBackingUp") or "BACKING UP - " .. string.format("%.2f", snapshotPercentage) .. "%" |
| 110 | + end |
| 111 | + map = get_var("MapNameBackingUp") or "backing up server" |
| 112 | + end |
| 113 | + elseif queue ~= nil and queue["install"] == "running" then |
| 114 | + if finishSec ~= nil then |
| 115 | + -- finish sec is not necissary applicable, but it's better to show something I guess |
| 116 | + name = get_var("ServerListNameInstalling") or |
| 117 | + string.format("INSTALLING, this might take a moment - %ds", finishSec) |
| 118 | + else |
| 119 | + name = get_var("ServerListNameInstalling") or "INSTALLING, this might take a moment" |
| 120 | + end |
| 121 | + |
| 122 | + map = get_var("MapNameInstalling") or "installing server" |
| 123 | + elseif finishSec ~= nil then |
| 124 | + nameTemplate = get_var("ServerListNameStarting") or "Druid Gameserver (starting) - %ds" |
| 125 | + name = string.format(nameTemplate, finishSec) |
| 126 | + end |
| 127 | + |
| 128 | + folder = get_var("GameSteamFolder") or "ark_survival_evolved" |
| 129 | + |
| 130 | + gameName = get_var("GameName") or "ARK: Survival Evolved" |
| 131 | + |
| 132 | + steamIdString = get_var("GameSteamId") or "0" |
| 133 | + |
| 134 | + steamId = tonumber(steamIdString) |
| 135 | + |
| 136 | + serverPort = get_port("main") |
| 137 | + |
| 138 | + -- hex |
| 139 | + nameHex = string.tohex(name) |
| 140 | + |
| 141 | + mapHex = string.tohex(map) |
| 142 | + |
| 143 | + folderHex = string.tohex(folder) -- ark: ark_survival_evolved |
| 144 | + |
| 145 | + steamIdHex = number_to_little_endian_short(steamId) |
| 146 | + |
| 147 | + gameHex = string.tohex(gameName) |
| 148 | + |
| 149 | + maxPlayerHex = "00" |
| 150 | + playerHex = "00" |
| 151 | + botHex = "00" |
| 152 | + |
| 153 | + serverTypeHex = "64" -- dedicated |
| 154 | + |
| 155 | + osHex = "6C" -- l (6C) for linux, w (77) for windows |
| 156 | + |
| 157 | + visibility = "00" -- 01 for private, 00 for public |
| 158 | + |
| 159 | + version = string.tohex("1.0.0.0") |
| 160 | + |
| 161 | + -- EDF & 0x80: Port |
| 162 | + -- EDF & 0x10: SteamID |
| 163 | + -- EDF & 0x20 Keywords |
| 164 | + -- EDF & 0x01 GameID |
| 165 | + |
| 166 | + edfFlagHex = "B1" |
| 167 | + |
| 168 | + -- short as hex |
| 169 | + gamePortHex = number_to_little_endian_short(serverPort) |
| 170 | + |
| 171 | + steamId = "01D075C44C764001" |
| 172 | + |
| 173 | + tags = |
| 174 | + ",OWNINGID:90202064633057281,OWNINGNAME:90202064633057281,NUMOPENPUBCONN:50,P2PADDR:90202064633057281,P2PPORT:" .. |
| 175 | + serverPort .. ",LEGACY_i:0" |
| 176 | + |
| 177 | + tagsHex = string.tohex(tags) |
| 178 | + |
| 179 | + edfHex = gamePortHex .. steamId .. tagsHex .. "00" .. "FE47050000000000" |
| 180 | + |
| 181 | + res = "FFFFFFFF4911" .. nameHex .."00" .. mapHex .."00".. folderHex .."00" .. gameHex .."00" .. steamIdHex .. playerHex .. maxPlayerHex .. botHex .. serverTypeHex .. |
| 182 | + osHex .. visibility .. version .."00" .. edfFlagHex .. edfHex |
| 183 | + |
| 184 | + resHex = string.fromhex(res) |
| 185 | + |
| 186 | + ctx.sendData(resHex) |
| 187 | + return |
| 188 | + end |
| 189 | + |
| 190 | + debug_print("Unknown Packet: " .. hex) |
| 191 | + |
| 192 | +end |
| 193 | + |
| 194 | +function number_to_little_endian_short(num) |
| 195 | + -- Ensure the number is in the 16-bit range for unsigned short |
| 196 | + if num < 0 or num > 65535 then |
| 197 | + error("Number " .. num .. " out of range for 16-bit unsigned short") |
| 198 | + end |
| 199 | + |
| 200 | + -- Convert the number to two bytes in little-endian format |
| 201 | + local low_byte = num % 256 -- Least significant byte |
| 202 | + local high_byte = math.floor(num / 256) % 256 -- Most significant byte |
| 203 | + |
| 204 | + -- Format as hexadecimal string |
| 205 | + return string.format("%02X%02X", low_byte, high_byte) |
| 206 | +end |
0 commit comments