diff --git a/scrolls/minecraft/forge/1.21.10/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/forge/1.21.10/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.10/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.10/init-files/run.sh b/scrolls/minecraft/forge/1.21.10/init-files/run.sh new file mode 100644 index 00000000..27160e42 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.10/init-files/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar forge-*-shim.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.10/init-files/server.properties.scroll_template b/scrolls/minecraft/forge/1.21.10/init-files/server.properties.scroll_template new file mode 100644 index 00000000..6becd211 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.10/init-files/server.properties.scroll_template @@ -0,0 +1,56 @@ +allow-flight=false +allow-nether=true +broadcast-console-to-ops=true +broadcast-rcon-to-ops=true +difficulty=easy +enable-command-block=false +enable-jmx-monitoring=false +enable-query=false +enable-rcon=true +enable-status=true +enforce-secure-profile=true +enforce-whitelist=false +entity-broadcast-range-percentage=100 +force-gamemode=false +function-permission-level=2 +gamemode=survival +generate-structures=true +generator-settings={} +hardcore=false +hide-online-players=false +initial-disabled-packs= +initial-enabled-packs=vanilla +level-name=world +level-seed= +level-type=minecraft\:normal +max-chained-neighbor-updates=1000000 +max-players=20 +max-tick-time=60000 +max-world-size=29999984 +motd=A Minecraft Server +network-compression-threshold=256 +online-mode=true +op-permission-level=4 +player-idle-timeout=0 +prevent-proxy-connections=false +pvp=true +query.port=25565 +rate-limit=0 +rcon.password={{ .Config.rcon.password }} +rcon.port=25575 +require-resource-pack=false +resource-pack= +resource-pack-prompt= +resource-pack-sha1= +server-ip= +server-port=25565 +simulation-distance=10 +spawn-animals=true +spawn-monsters=true +spawn-npcs=true +spawn-protection=16 +sync-chunk-writes=true +text-filtering-config= +use-native-transport=true +view-distance=10 +white-list=false \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.10/init-files/update.sh b/scrolls/minecraft/forge/1.21.10/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/forge/1.21.10/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.10/init-files/update_user_args.sh b/scrolls/minecraft/forge/1.21.10/init-files/update_user_args.sh new file mode 100755 index 00000000..0f6686bf --- /dev/null +++ b/scrolls/minecraft/forge/1.21.10/init-files/update_user_args.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env sh + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + + +echo -Xmx$MAX > user_jvm_args.txt \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.10/packet_handler/json.lua b/scrolls/minecraft/forge/1.21.10/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.10/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.10/packet_handler/minecraft.lua b/scrolls/minecraft/forge/1.21.10/packet_handler/minecraft.lua new file mode 100644 index 00000000..34bcfa9b --- /dev/null +++ b/scrolls/minecraft/forge/1.21.10/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Downloading snapshot... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Extracting snapshot..." + end + obj.description = "Restoring Minecraft Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Backing up..." + end + obj.description = "Backing up Minecraft Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Minecraft Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/forge/1.21.10/scroll-switch/run.sh b/scrolls/minecraft/forge/1.21.10/scroll-switch/run.sh new file mode 100644 index 00000000..27160e42 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.10/scroll-switch/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar forge-*-shim.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.10/scroll-switch/scroll-switch.sh b/scrolls/minecraft/forge/1.21.10/scroll-switch/scroll-switch.sh new file mode 100755 index 00000000..41ddfb16 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.10/scroll-switch/scroll-switch.sh @@ -0,0 +1,21 @@ +NEWVERSION=$1 + +#wget -O forge-installer-new.jar https://fsn1.your-objectstorage.com/druid-deployment-assets/minecraft/forge/forge-$NEWVERSION.jar + +#java -jar forge-installer-new.jar --installServer +#rm forge-installer-new.jar + +# from forge version 1.20.3 on, we have to provide our own run.sh and overwrite the exiting one +# we need to check if we switch from a version before 1.20.3 to a version after 1.20.3 +# we can use `druid scroll app_version ge 1.20.3` to check this +# we can use `druid scroll semver $NEWVERSION ge 1.20.3` to check this + +if druid app_version ge 1.20.3; then + echo "Already using forge version 1.20.3 or newer, run.sh is up to date" + exit 0 +fi + +if druid app_version $NEWVERSION ge 1.20.3; then + echo "$SCROLL_DIR/scroll-switch/run.sh ./run.sh" + cp $SCROLL_DIR/scroll-switch/run.sh ./run.sh +fi \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.10/scroll.yaml b/scrolls/minecraft/forge/1.21.10/scroll.yaml new file mode 100644 index 00000000..cf7f42c7 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.10/scroll.yaml @@ -0,0 +1,72 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-forge +desc: Minecraft Forge +version: 0.0.1 +app_version: 1.21.10 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + run: restart + dependencies: [jdk21] + procedures: + - mode: exec + data: + - sh + - ./update_user_args.sh + - mode: exec + data: + - sh + - ./run.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert, jdk21] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - forge-installer.jar + - http://192.168.100.200:9000/snapshot-cache/minecraft/forge/forge-1.21.10.jar + - mode: exec + data: + - java + - -jar + - forge-installer.jar + - --installServer + - mode: exec + data: + - rm + - forge-installer.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/forge/1.21.10/update/.gitkeep b/scrolls/minecraft/forge/1.21.10/update/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scrolls/minecraft/forge/1.21.11/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/forge/1.21.11/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.11/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.11/init-files/run.sh b/scrolls/minecraft/forge/1.21.11/init-files/run.sh new file mode 100644 index 00000000..27160e42 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.11/init-files/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar forge-*-shim.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.11/init-files/server.properties.scroll_template b/scrolls/minecraft/forge/1.21.11/init-files/server.properties.scroll_template new file mode 100644 index 00000000..6becd211 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.11/init-files/server.properties.scroll_template @@ -0,0 +1,56 @@ +allow-flight=false +allow-nether=true +broadcast-console-to-ops=true +broadcast-rcon-to-ops=true +difficulty=easy +enable-command-block=false +enable-jmx-monitoring=false +enable-query=false +enable-rcon=true +enable-status=true +enforce-secure-profile=true +enforce-whitelist=false +entity-broadcast-range-percentage=100 +force-gamemode=false +function-permission-level=2 +gamemode=survival +generate-structures=true +generator-settings={} +hardcore=false +hide-online-players=false +initial-disabled-packs= +initial-enabled-packs=vanilla +level-name=world +level-seed= +level-type=minecraft\:normal +max-chained-neighbor-updates=1000000 +max-players=20 +max-tick-time=60000 +max-world-size=29999984 +motd=A Minecraft Server +network-compression-threshold=256 +online-mode=true +op-permission-level=4 +player-idle-timeout=0 +prevent-proxy-connections=false +pvp=true +query.port=25565 +rate-limit=0 +rcon.password={{ .Config.rcon.password }} +rcon.port=25575 +require-resource-pack=false +resource-pack= +resource-pack-prompt= +resource-pack-sha1= +server-ip= +server-port=25565 +simulation-distance=10 +spawn-animals=true +spawn-monsters=true +spawn-npcs=true +spawn-protection=16 +sync-chunk-writes=true +text-filtering-config= +use-native-transport=true +view-distance=10 +white-list=false \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.11/init-files/update.sh b/scrolls/minecraft/forge/1.21.11/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/forge/1.21.11/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.11/init-files/update_user_args.sh b/scrolls/minecraft/forge/1.21.11/init-files/update_user_args.sh new file mode 100755 index 00000000..0f6686bf --- /dev/null +++ b/scrolls/minecraft/forge/1.21.11/init-files/update_user_args.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env sh + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + + +echo -Xmx$MAX > user_jvm_args.txt \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.11/packet_handler/json.lua b/scrolls/minecraft/forge/1.21.11/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.11/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.11/packet_handler/minecraft.lua b/scrolls/minecraft/forge/1.21.11/packet_handler/minecraft.lua new file mode 100644 index 00000000..34bcfa9b --- /dev/null +++ b/scrolls/minecraft/forge/1.21.11/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Downloading snapshot... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Extracting snapshot..." + end + obj.description = "Restoring Minecraft Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Backing up..." + end + obj.description = "Backing up Minecraft Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Minecraft Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/forge/1.21.11/scroll-switch/run.sh b/scrolls/minecraft/forge/1.21.11/scroll-switch/run.sh new file mode 100644 index 00000000..27160e42 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.11/scroll-switch/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar forge-*-shim.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.11/scroll-switch/scroll-switch.sh b/scrolls/minecraft/forge/1.21.11/scroll-switch/scroll-switch.sh new file mode 100755 index 00000000..41ddfb16 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.11/scroll-switch/scroll-switch.sh @@ -0,0 +1,21 @@ +NEWVERSION=$1 + +#wget -O forge-installer-new.jar https://fsn1.your-objectstorage.com/druid-deployment-assets/minecraft/forge/forge-$NEWVERSION.jar + +#java -jar forge-installer-new.jar --installServer +#rm forge-installer-new.jar + +# from forge version 1.20.3 on, we have to provide our own run.sh and overwrite the exiting one +# we need to check if we switch from a version before 1.20.3 to a version after 1.20.3 +# we can use `druid scroll app_version ge 1.20.3` to check this +# we can use `druid scroll semver $NEWVERSION ge 1.20.3` to check this + +if druid app_version ge 1.20.3; then + echo "Already using forge version 1.20.3 or newer, run.sh is up to date" + exit 0 +fi + +if druid app_version $NEWVERSION ge 1.20.3; then + echo "$SCROLL_DIR/scroll-switch/run.sh ./run.sh" + cp $SCROLL_DIR/scroll-switch/run.sh ./run.sh +fi \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.11/scroll.yaml b/scrolls/minecraft/forge/1.21.11/scroll.yaml new file mode 100644 index 00000000..2364d7f5 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.11/scroll.yaml @@ -0,0 +1,72 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-forge +desc: Minecraft Forge +version: 0.0.1 +app_version: 1.21.11 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + run: restart + dependencies: [jdk21] + procedures: + - mode: exec + data: + - sh + - ./update_user_args.sh + - mode: exec + data: + - sh + - ./run.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert, jdk21] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - forge-installer.jar + - http://192.168.100.200:9000/snapshot-cache/minecraft/forge/forge-1.21.11.jar + - mode: exec + data: + - java + - -jar + - forge-installer.jar + - --installServer + - mode: exec + data: + - rm + - forge-installer.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/forge/1.21.11/update/.gitkeep b/scrolls/minecraft/forge/1.21.11/update/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scrolls/minecraft/forge/1.21.8/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/forge/1.21.8/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.8/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.8/init-files/run.sh b/scrolls/minecraft/forge/1.21.8/init-files/run.sh new file mode 100644 index 00000000..27160e42 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.8/init-files/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar forge-*-shim.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.8/init-files/server.properties.scroll_template b/scrolls/minecraft/forge/1.21.8/init-files/server.properties.scroll_template new file mode 100644 index 00000000..6becd211 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.8/init-files/server.properties.scroll_template @@ -0,0 +1,56 @@ +allow-flight=false +allow-nether=true +broadcast-console-to-ops=true +broadcast-rcon-to-ops=true +difficulty=easy +enable-command-block=false +enable-jmx-monitoring=false +enable-query=false +enable-rcon=true +enable-status=true +enforce-secure-profile=true +enforce-whitelist=false +entity-broadcast-range-percentage=100 +force-gamemode=false +function-permission-level=2 +gamemode=survival +generate-structures=true +generator-settings={} +hardcore=false +hide-online-players=false +initial-disabled-packs= +initial-enabled-packs=vanilla +level-name=world +level-seed= +level-type=minecraft\:normal +max-chained-neighbor-updates=1000000 +max-players=20 +max-tick-time=60000 +max-world-size=29999984 +motd=A Minecraft Server +network-compression-threshold=256 +online-mode=true +op-permission-level=4 +player-idle-timeout=0 +prevent-proxy-connections=false +pvp=true +query.port=25565 +rate-limit=0 +rcon.password={{ .Config.rcon.password }} +rcon.port=25575 +require-resource-pack=false +resource-pack= +resource-pack-prompt= +resource-pack-sha1= +server-ip= +server-port=25565 +simulation-distance=10 +spawn-animals=true +spawn-monsters=true +spawn-npcs=true +spawn-protection=16 +sync-chunk-writes=true +text-filtering-config= +use-native-transport=true +view-distance=10 +white-list=false \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.8/init-files/update.sh b/scrolls/minecraft/forge/1.21.8/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/forge/1.21.8/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.8/init-files/update_user_args.sh b/scrolls/minecraft/forge/1.21.8/init-files/update_user_args.sh new file mode 100755 index 00000000..0f6686bf --- /dev/null +++ b/scrolls/minecraft/forge/1.21.8/init-files/update_user_args.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env sh + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + + +echo -Xmx$MAX > user_jvm_args.txt \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.8/packet_handler/json.lua b/scrolls/minecraft/forge/1.21.8/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.8/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.8/packet_handler/minecraft.lua b/scrolls/minecraft/forge/1.21.8/packet_handler/minecraft.lua new file mode 100644 index 00000000..34bcfa9b --- /dev/null +++ b/scrolls/minecraft/forge/1.21.8/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Downloading snapshot... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Extracting snapshot..." + end + obj.description = "Restoring Minecraft Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Backing up..." + end + obj.description = "Backing up Minecraft Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Minecraft Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/forge/1.21.8/scroll-switch/run.sh b/scrolls/minecraft/forge/1.21.8/scroll-switch/run.sh new file mode 100644 index 00000000..27160e42 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.8/scroll-switch/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar forge-*-shim.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.8/scroll-switch/scroll-switch.sh b/scrolls/minecraft/forge/1.21.8/scroll-switch/scroll-switch.sh new file mode 100755 index 00000000..41ddfb16 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.8/scroll-switch/scroll-switch.sh @@ -0,0 +1,21 @@ +NEWVERSION=$1 + +#wget -O forge-installer-new.jar https://fsn1.your-objectstorage.com/druid-deployment-assets/minecraft/forge/forge-$NEWVERSION.jar + +#java -jar forge-installer-new.jar --installServer +#rm forge-installer-new.jar + +# from forge version 1.20.3 on, we have to provide our own run.sh and overwrite the exiting one +# we need to check if we switch from a version before 1.20.3 to a version after 1.20.3 +# we can use `druid scroll app_version ge 1.20.3` to check this +# we can use `druid scroll semver $NEWVERSION ge 1.20.3` to check this + +if druid app_version ge 1.20.3; then + echo "Already using forge version 1.20.3 or newer, run.sh is up to date" + exit 0 +fi + +if druid app_version $NEWVERSION ge 1.20.3; then + echo "$SCROLL_DIR/scroll-switch/run.sh ./run.sh" + cp $SCROLL_DIR/scroll-switch/run.sh ./run.sh +fi \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.8/scroll.yaml b/scrolls/minecraft/forge/1.21.8/scroll.yaml new file mode 100644 index 00000000..3e33b78e --- /dev/null +++ b/scrolls/minecraft/forge/1.21.8/scroll.yaml @@ -0,0 +1,72 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-forge +desc: Minecraft Forge +version: 0.0.1 +app_version: 1.21.8 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + run: restart + dependencies: [jdk21] + procedures: + - mode: exec + data: + - sh + - ./update_user_args.sh + - mode: exec + data: + - sh + - ./run.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert, jdk21] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - forge-installer.jar + - http://192.168.100.200:9000/snapshot-cache/minecraft/forge/forge-1.21.8.jar + - mode: exec + data: + - java + - -jar + - forge-installer.jar + - --installServer + - mode: exec + data: + - rm + - forge-installer.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/forge/1.21.8/update/.gitkeep b/scrolls/minecraft/forge/1.21.8/update/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scrolls/minecraft/forge/1.21.9/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/forge/1.21.9/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.9/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.9/init-files/run.sh b/scrolls/minecraft/forge/1.21.9/init-files/run.sh new file mode 100644 index 00000000..27160e42 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.9/init-files/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar forge-*-shim.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.9/init-files/server.properties.scroll_template b/scrolls/minecraft/forge/1.21.9/init-files/server.properties.scroll_template new file mode 100644 index 00000000..6becd211 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.9/init-files/server.properties.scroll_template @@ -0,0 +1,56 @@ +allow-flight=false +allow-nether=true +broadcast-console-to-ops=true +broadcast-rcon-to-ops=true +difficulty=easy +enable-command-block=false +enable-jmx-monitoring=false +enable-query=false +enable-rcon=true +enable-status=true +enforce-secure-profile=true +enforce-whitelist=false +entity-broadcast-range-percentage=100 +force-gamemode=false +function-permission-level=2 +gamemode=survival +generate-structures=true +generator-settings={} +hardcore=false +hide-online-players=false +initial-disabled-packs= +initial-enabled-packs=vanilla +level-name=world +level-seed= +level-type=minecraft\:normal +max-chained-neighbor-updates=1000000 +max-players=20 +max-tick-time=60000 +max-world-size=29999984 +motd=A Minecraft Server +network-compression-threshold=256 +online-mode=true +op-permission-level=4 +player-idle-timeout=0 +prevent-proxy-connections=false +pvp=true +query.port=25565 +rate-limit=0 +rcon.password={{ .Config.rcon.password }} +rcon.port=25575 +require-resource-pack=false +resource-pack= +resource-pack-prompt= +resource-pack-sha1= +server-ip= +server-port=25565 +simulation-distance=10 +spawn-animals=true +spawn-monsters=true +spawn-npcs=true +spawn-protection=16 +sync-chunk-writes=true +text-filtering-config= +use-native-transport=true +view-distance=10 +white-list=false \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.9/init-files/update.sh b/scrolls/minecraft/forge/1.21.9/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/forge/1.21.9/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.9/init-files/update_user_args.sh b/scrolls/minecraft/forge/1.21.9/init-files/update_user_args.sh new file mode 100755 index 00000000..0f6686bf --- /dev/null +++ b/scrolls/minecraft/forge/1.21.9/init-files/update_user_args.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env sh + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + + +echo -Xmx$MAX > user_jvm_args.txt \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.9/packet_handler/json.lua b/scrolls/minecraft/forge/1.21.9/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.9/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.9/packet_handler/minecraft.lua b/scrolls/minecraft/forge/1.21.9/packet_handler/minecraft.lua new file mode 100644 index 00000000..34bcfa9b --- /dev/null +++ b/scrolls/minecraft/forge/1.21.9/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Downloading snapshot... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Extracting snapshot..." + end + obj.description = "Restoring Minecraft Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Backing up..." + end + obj.description = "Backing up Minecraft Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Minecraft Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/forge/1.21.9/scroll-switch/run.sh b/scrolls/minecraft/forge/1.21.9/scroll-switch/run.sh new file mode 100644 index 00000000..27160e42 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.9/scroll-switch/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar forge-*-shim.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.9/scroll-switch/scroll-switch.sh b/scrolls/minecraft/forge/1.21.9/scroll-switch/scroll-switch.sh new file mode 100755 index 00000000..41ddfb16 --- /dev/null +++ b/scrolls/minecraft/forge/1.21.9/scroll-switch/scroll-switch.sh @@ -0,0 +1,21 @@ +NEWVERSION=$1 + +#wget -O forge-installer-new.jar https://fsn1.your-objectstorage.com/druid-deployment-assets/minecraft/forge/forge-$NEWVERSION.jar + +#java -jar forge-installer-new.jar --installServer +#rm forge-installer-new.jar + +# from forge version 1.20.3 on, we have to provide our own run.sh and overwrite the exiting one +# we need to check if we switch from a version before 1.20.3 to a version after 1.20.3 +# we can use `druid scroll app_version ge 1.20.3` to check this +# we can use `druid scroll semver $NEWVERSION ge 1.20.3` to check this + +if druid app_version ge 1.20.3; then + echo "Already using forge version 1.20.3 or newer, run.sh is up to date" + exit 0 +fi + +if druid app_version $NEWVERSION ge 1.20.3; then + echo "$SCROLL_DIR/scroll-switch/run.sh ./run.sh" + cp $SCROLL_DIR/scroll-switch/run.sh ./run.sh +fi \ No newline at end of file diff --git a/scrolls/minecraft/forge/1.21.9/scroll.yaml b/scrolls/minecraft/forge/1.21.9/scroll.yaml new file mode 100644 index 00000000..623bf5ac --- /dev/null +++ b/scrolls/minecraft/forge/1.21.9/scroll.yaml @@ -0,0 +1,72 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-forge +desc: Minecraft Forge +version: 0.0.1 +app_version: 1.21.9 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + run: restart + dependencies: [jdk21] + procedures: + - mode: exec + data: + - sh + - ./update_user_args.sh + - mode: exec + data: + - sh + - ./run.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert, jdk21] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - forge-installer.jar + - http://192.168.100.200:9000/snapshot-cache/minecraft/forge/forge-1.21.9.jar + - mode: exec + data: + - java + - -jar + - forge-installer.jar + - --installServer + - mode: exec + data: + - rm + - forge-installer.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/forge/1.21.9/update/.gitkeep b/scrolls/minecraft/forge/1.21.9/update/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scrolls/minecraft/minecraft-spigot/1.21.10/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/minecraft-spigot/1.21.10/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.10/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.10/init-files/server.properties.scroll_template b/scrolls/minecraft/minecraft-spigot/1.21.10/init-files/server.properties.scroll_template new file mode 100644 index 00000000..56c0dc3c --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.10/init-files/server.properties.scroll_template @@ -0,0 +1,50 @@ +#Minecraft server properties +#Mon Apr 11 00:50:50 CEST 2022 +enable-jmx-monitoring=false +rcon.port=25575 +gamemode=survival +enable-command-block=false +enable-query=false +level-name=world +motd=A Minecraft Server +query.port=25565 +pvp=true +difficulty=easy +network-compression-threshold=256 +require-resource-pack=false +max-tick-time=60000 +use-native-transport=true +max-players=20 +online-mode=true +enable-status=true +allow-flight=false +broadcast-rcon-to-ops=true +view-distance=10 +server-ip= +resource-pack-prompt= +allow-nether=true +server-port=25565 +enable-rcon=true +sync-chunk-writes=true +op-permission-level=4 +prevent-proxy-connections=false +resource-pack= +entity-broadcast-range-percentage=100 +rcon.password={{ .Config.rcon.password }} +player-idle-timeout=0 +debug=false +force-gamemode=false +rate-limit=0 +hardcore=false +white-list=false +broadcast-console-to-ops=true +spawn-npcs=true +spawn-animals=true +snooper-enabled=true +function-permission-level=2 +text-filtering-config= +spawn-monsters=true +enforce-whitelist=false +resource-pack-sha1= +spawn-protection=16 +max-world-size=29999984 diff --git a/scrolls/minecraft/minecraft-spigot/1.21.10/init-files/start.sh b/scrolls/minecraft/minecraft-spigot/1.21.10/init-files/start.sh new file mode 100755 index 00000000..b1695fc7 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.10/init-files/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar spigot.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.10/init-files/update.sh b/scrolls/minecraft/minecraft-spigot/1.21.10/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.10/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.10/packet_handler/json.lua b/scrolls/minecraft/minecraft-spigot/1.21.10/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.10/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.10/packet_handler/minecraft.lua b/scrolls/minecraft/minecraft-spigot/1.21.10/packet_handler/minecraft.lua new file mode 100644 index 00000000..c8ee5d38 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.10/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Extracting snapshot..." + else + obj.version.name = "§2▶ Downloading snapshot... " .. string.format("%.2f", snapshotPercentage) .. "%" + end + obj.description = "Restoring Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up..." + else + obj.version.name = "§2▶ Backing up... " .. string.format("%.2f", snapshotPercentage) .. "%" + end + obj.description = "Backing up Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/minecraft-spigot/1.21.10/scroll.yaml b/scrolls/minecraft/minecraft-spigot/1.21.10/scroll.yaml new file mode 100644 index 00000000..72e3cf54 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.10/scroll.yaml @@ -0,0 +1,59 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-spigot +desc: Minecraft Spigot +version: 0.0.1 +app_version: 1.21.10 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + mandatory: true + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + dependencies: [jdk21] + run: restart + procedures: + - mode: exec + data: + - bash + - ./start.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - spigot.jar + - http://192.168.100.200:9000/snapshot-cache/minecraft/spigot/spigot-1.21.10.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/minecraft-spigot/1.21.10/update/.gitkeep b/scrolls/minecraft/minecraft-spigot/1.21.10/update/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scrolls/minecraft/minecraft-spigot/1.21.11/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/minecraft-spigot/1.21.11/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.11/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.11/init-files/server.properties.scroll_template b/scrolls/minecraft/minecraft-spigot/1.21.11/init-files/server.properties.scroll_template new file mode 100644 index 00000000..56c0dc3c --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.11/init-files/server.properties.scroll_template @@ -0,0 +1,50 @@ +#Minecraft server properties +#Mon Apr 11 00:50:50 CEST 2022 +enable-jmx-monitoring=false +rcon.port=25575 +gamemode=survival +enable-command-block=false +enable-query=false +level-name=world +motd=A Minecraft Server +query.port=25565 +pvp=true +difficulty=easy +network-compression-threshold=256 +require-resource-pack=false +max-tick-time=60000 +use-native-transport=true +max-players=20 +online-mode=true +enable-status=true +allow-flight=false +broadcast-rcon-to-ops=true +view-distance=10 +server-ip= +resource-pack-prompt= +allow-nether=true +server-port=25565 +enable-rcon=true +sync-chunk-writes=true +op-permission-level=4 +prevent-proxy-connections=false +resource-pack= +entity-broadcast-range-percentage=100 +rcon.password={{ .Config.rcon.password }} +player-idle-timeout=0 +debug=false +force-gamemode=false +rate-limit=0 +hardcore=false +white-list=false +broadcast-console-to-ops=true +spawn-npcs=true +spawn-animals=true +snooper-enabled=true +function-permission-level=2 +text-filtering-config= +spawn-monsters=true +enforce-whitelist=false +resource-pack-sha1= +spawn-protection=16 +max-world-size=29999984 diff --git a/scrolls/minecraft/minecraft-spigot/1.21.11/init-files/start.sh b/scrolls/minecraft/minecraft-spigot/1.21.11/init-files/start.sh new file mode 100755 index 00000000..b1695fc7 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.11/init-files/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar spigot.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.11/init-files/update.sh b/scrolls/minecraft/minecraft-spigot/1.21.11/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.11/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.11/packet_handler/json.lua b/scrolls/minecraft/minecraft-spigot/1.21.11/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.11/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.11/packet_handler/minecraft.lua b/scrolls/minecraft/minecraft-spigot/1.21.11/packet_handler/minecraft.lua new file mode 100644 index 00000000..c8ee5d38 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.11/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Extracting snapshot..." + else + obj.version.name = "§2▶ Downloading snapshot... " .. string.format("%.2f", snapshotPercentage) .. "%" + end + obj.description = "Restoring Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up..." + else + obj.version.name = "§2▶ Backing up... " .. string.format("%.2f", snapshotPercentage) .. "%" + end + obj.description = "Backing up Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/minecraft-spigot/1.21.11/scroll.yaml b/scrolls/minecraft/minecraft-spigot/1.21.11/scroll.yaml new file mode 100644 index 00000000..f86c5a1c --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.11/scroll.yaml @@ -0,0 +1,59 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-spigot +desc: Minecraft Spigot +version: 0.0.1 +app_version: 1.21.11 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + mandatory: true + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + dependencies: [jdk21] + run: restart + procedures: + - mode: exec + data: + - bash + - ./start.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - spigot.jar + - http://192.168.100.200:9000/snapshot-cache/minecraft/spigot/spigot-1.21.11.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/minecraft-spigot/1.21.11/update/.gitkeep b/scrolls/minecraft/minecraft-spigot/1.21.11/update/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scrolls/minecraft/minecraft-spigot/1.21.2/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/minecraft-spigot/1.21.2/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.2/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.2/init-files/server.properties.scroll_template b/scrolls/minecraft/minecraft-spigot/1.21.2/init-files/server.properties.scroll_template new file mode 100644 index 00000000..56c0dc3c --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.2/init-files/server.properties.scroll_template @@ -0,0 +1,50 @@ +#Minecraft server properties +#Mon Apr 11 00:50:50 CEST 2022 +enable-jmx-monitoring=false +rcon.port=25575 +gamemode=survival +enable-command-block=false +enable-query=false +level-name=world +motd=A Minecraft Server +query.port=25565 +pvp=true +difficulty=easy +network-compression-threshold=256 +require-resource-pack=false +max-tick-time=60000 +use-native-transport=true +max-players=20 +online-mode=true +enable-status=true +allow-flight=false +broadcast-rcon-to-ops=true +view-distance=10 +server-ip= +resource-pack-prompt= +allow-nether=true +server-port=25565 +enable-rcon=true +sync-chunk-writes=true +op-permission-level=4 +prevent-proxy-connections=false +resource-pack= +entity-broadcast-range-percentage=100 +rcon.password={{ .Config.rcon.password }} +player-idle-timeout=0 +debug=false +force-gamemode=false +rate-limit=0 +hardcore=false +white-list=false +broadcast-console-to-ops=true +spawn-npcs=true +spawn-animals=true +snooper-enabled=true +function-permission-level=2 +text-filtering-config= +spawn-monsters=true +enforce-whitelist=false +resource-pack-sha1= +spawn-protection=16 +max-world-size=29999984 diff --git a/scrolls/minecraft/minecraft-spigot/1.21.2/init-files/start.sh b/scrolls/minecraft/minecraft-spigot/1.21.2/init-files/start.sh new file mode 100755 index 00000000..b1695fc7 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.2/init-files/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar spigot.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.2/init-files/update.sh b/scrolls/minecraft/minecraft-spigot/1.21.2/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.2/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.2/packet_handler/json.lua b/scrolls/minecraft/minecraft-spigot/1.21.2/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.2/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.2/packet_handler/minecraft.lua b/scrolls/minecraft/minecraft-spigot/1.21.2/packet_handler/minecraft.lua new file mode 100644 index 00000000..c8ee5d38 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.2/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Extracting snapshot..." + else + obj.version.name = "§2▶ Downloading snapshot... " .. string.format("%.2f", snapshotPercentage) .. "%" + end + obj.description = "Restoring Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up..." + else + obj.version.name = "§2▶ Backing up... " .. string.format("%.2f", snapshotPercentage) .. "%" + end + obj.description = "Backing up Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/minecraft-spigot/1.21.2/scroll.yaml b/scrolls/minecraft/minecraft-spigot/1.21.2/scroll.yaml new file mode 100644 index 00000000..eacf2231 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.2/scroll.yaml @@ -0,0 +1,59 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-spigot +desc: Minecraft Spigot +version: 0.0.1 +app_version: 1.21.2 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + mandatory: true + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + dependencies: [jdk21] + run: restart + procedures: + - mode: exec + data: + - bash + - ./start.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - spigot.jar + - http://192.168.100.200:9000/snapshot-cache/minecraft/spigot/spigot-1.21.2.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/minecraft-spigot/1.21.2/update/.gitkeep b/scrolls/minecraft/minecraft-spigot/1.21.2/update/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scrolls/minecraft/minecraft-spigot/1.21.8/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/minecraft-spigot/1.21.8/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.8/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.8/init-files/server.properties.scroll_template b/scrolls/minecraft/minecraft-spigot/1.21.8/init-files/server.properties.scroll_template new file mode 100644 index 00000000..56c0dc3c --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.8/init-files/server.properties.scroll_template @@ -0,0 +1,50 @@ +#Minecraft server properties +#Mon Apr 11 00:50:50 CEST 2022 +enable-jmx-monitoring=false +rcon.port=25575 +gamemode=survival +enable-command-block=false +enable-query=false +level-name=world +motd=A Minecraft Server +query.port=25565 +pvp=true +difficulty=easy +network-compression-threshold=256 +require-resource-pack=false +max-tick-time=60000 +use-native-transport=true +max-players=20 +online-mode=true +enable-status=true +allow-flight=false +broadcast-rcon-to-ops=true +view-distance=10 +server-ip= +resource-pack-prompt= +allow-nether=true +server-port=25565 +enable-rcon=true +sync-chunk-writes=true +op-permission-level=4 +prevent-proxy-connections=false +resource-pack= +entity-broadcast-range-percentage=100 +rcon.password={{ .Config.rcon.password }} +player-idle-timeout=0 +debug=false +force-gamemode=false +rate-limit=0 +hardcore=false +white-list=false +broadcast-console-to-ops=true +spawn-npcs=true +spawn-animals=true +snooper-enabled=true +function-permission-level=2 +text-filtering-config= +spawn-monsters=true +enforce-whitelist=false +resource-pack-sha1= +spawn-protection=16 +max-world-size=29999984 diff --git a/scrolls/minecraft/minecraft-spigot/1.21.8/init-files/start.sh b/scrolls/minecraft/minecraft-spigot/1.21.8/init-files/start.sh new file mode 100755 index 00000000..b1695fc7 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.8/init-files/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar spigot.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.8/init-files/update.sh b/scrolls/minecraft/minecraft-spigot/1.21.8/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.8/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.8/packet_handler/json.lua b/scrolls/minecraft/minecraft-spigot/1.21.8/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.8/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.8/packet_handler/minecraft.lua b/scrolls/minecraft/minecraft-spigot/1.21.8/packet_handler/minecraft.lua new file mode 100644 index 00000000..c8ee5d38 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.8/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Extracting snapshot..." + else + obj.version.name = "§2▶ Downloading snapshot... " .. string.format("%.2f", snapshotPercentage) .. "%" + end + obj.description = "Restoring Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up..." + else + obj.version.name = "§2▶ Backing up... " .. string.format("%.2f", snapshotPercentage) .. "%" + end + obj.description = "Backing up Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/minecraft-spigot/1.21.8/scroll.yaml b/scrolls/minecraft/minecraft-spigot/1.21.8/scroll.yaml new file mode 100644 index 00000000..81f9946c --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.8/scroll.yaml @@ -0,0 +1,59 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-spigot +desc: Minecraft Spigot +version: 0.0.1 +app_version: 1.21.8 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + mandatory: true + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + dependencies: [jdk21] + run: restart + procedures: + - mode: exec + data: + - bash + - ./start.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - spigot.jar + - http://192.168.100.200:9000/snapshot-cache/minecraft/spigot/spigot-1.21.8.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/minecraft-spigot/1.21.8/update/.gitkeep b/scrolls/minecraft/minecraft-spigot/1.21.8/update/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scrolls/minecraft/minecraft-spigot/1.21.9/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/minecraft-spigot/1.21.9/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.9/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.9/init-files/server.properties.scroll_template b/scrolls/minecraft/minecraft-spigot/1.21.9/init-files/server.properties.scroll_template new file mode 100644 index 00000000..56c0dc3c --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.9/init-files/server.properties.scroll_template @@ -0,0 +1,50 @@ +#Minecraft server properties +#Mon Apr 11 00:50:50 CEST 2022 +enable-jmx-monitoring=false +rcon.port=25575 +gamemode=survival +enable-command-block=false +enable-query=false +level-name=world +motd=A Minecraft Server +query.port=25565 +pvp=true +difficulty=easy +network-compression-threshold=256 +require-resource-pack=false +max-tick-time=60000 +use-native-transport=true +max-players=20 +online-mode=true +enable-status=true +allow-flight=false +broadcast-rcon-to-ops=true +view-distance=10 +server-ip= +resource-pack-prompt= +allow-nether=true +server-port=25565 +enable-rcon=true +sync-chunk-writes=true +op-permission-level=4 +prevent-proxy-connections=false +resource-pack= +entity-broadcast-range-percentage=100 +rcon.password={{ .Config.rcon.password }} +player-idle-timeout=0 +debug=false +force-gamemode=false +rate-limit=0 +hardcore=false +white-list=false +broadcast-console-to-ops=true +spawn-npcs=true +spawn-animals=true +snooper-enabled=true +function-permission-level=2 +text-filtering-config= +spawn-monsters=true +enforce-whitelist=false +resource-pack-sha1= +spawn-protection=16 +max-world-size=29999984 diff --git a/scrolls/minecraft/minecraft-spigot/1.21.9/init-files/start.sh b/scrolls/minecraft/minecraft-spigot/1.21.9/init-files/start.sh new file mode 100755 index 00000000..b1695fc7 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.9/init-files/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar spigot.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.9/init-files/update.sh b/scrolls/minecraft/minecraft-spigot/1.21.9/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.9/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.9/packet_handler/json.lua b/scrolls/minecraft/minecraft-spigot/1.21.9/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.9/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-spigot/1.21.9/packet_handler/minecraft.lua b/scrolls/minecraft/minecraft-spigot/1.21.9/packet_handler/minecraft.lua new file mode 100644 index 00000000..c8ee5d38 --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.9/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Extracting snapshot..." + else + obj.version.name = "§2▶ Downloading snapshot... " .. string.format("%.2f", snapshotPercentage) .. "%" + end + obj.description = "Restoring Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up..." + else + obj.version.name = "§2▶ Backing up... " .. string.format("%.2f", snapshotPercentage) .. "%" + end + obj.description = "Backing up Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/minecraft-spigot/1.21.9/scroll.yaml b/scrolls/minecraft/minecraft-spigot/1.21.9/scroll.yaml new file mode 100644 index 00000000..b46791fb --- /dev/null +++ b/scrolls/minecraft/minecraft-spigot/1.21.9/scroll.yaml @@ -0,0 +1,59 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-spigot +desc: Minecraft Spigot +version: 0.0.1 +app_version: 1.21.9 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + mandatory: true + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + dependencies: [jdk21] + run: restart + procedures: + - mode: exec + data: + - bash + - ./start.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - spigot.jar + - http://192.168.100.200:9000/snapshot-cache/minecraft/spigot/spigot-1.21.9.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/minecraft-spigot/1.21.9/update/.gitkeep b/scrolls/minecraft/minecraft-spigot/1.21.9/update/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.10/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/minecraft-vanilla/1.21.10/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.10/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.10/init-files/server.properties.scroll_template b/scrolls/minecraft/minecraft-vanilla/1.21.10/init-files/server.properties.scroll_template new file mode 100644 index 00000000..56c0dc3c --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.10/init-files/server.properties.scroll_template @@ -0,0 +1,50 @@ +#Minecraft server properties +#Mon Apr 11 00:50:50 CEST 2022 +enable-jmx-monitoring=false +rcon.port=25575 +gamemode=survival +enable-command-block=false +enable-query=false +level-name=world +motd=A Minecraft Server +query.port=25565 +pvp=true +difficulty=easy +network-compression-threshold=256 +require-resource-pack=false +max-tick-time=60000 +use-native-transport=true +max-players=20 +online-mode=true +enable-status=true +allow-flight=false +broadcast-rcon-to-ops=true +view-distance=10 +server-ip= +resource-pack-prompt= +allow-nether=true +server-port=25565 +enable-rcon=true +sync-chunk-writes=true +op-permission-level=4 +prevent-proxy-connections=false +resource-pack= +entity-broadcast-range-percentage=100 +rcon.password={{ .Config.rcon.password }} +player-idle-timeout=0 +debug=false +force-gamemode=false +rate-limit=0 +hardcore=false +white-list=false +broadcast-console-to-ops=true +spawn-npcs=true +spawn-animals=true +snooper-enabled=true +function-permission-level=2 +text-filtering-config= +spawn-monsters=true +enforce-whitelist=false +resource-pack-sha1= +spawn-protection=16 +max-world-size=29999984 diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.10/init-files/start.sh b/scrolls/minecraft/minecraft-vanilla/1.21.10/init-files/start.sh new file mode 100755 index 00000000..5eed47c0 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.10/init-files/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar server.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.10/init-files/update.sh b/scrolls/minecraft/minecraft-vanilla/1.21.10/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.10/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.10/packet_handler/json.lua b/scrolls/minecraft/minecraft-vanilla/1.21.10/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.10/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.10/packet_handler/minecraft.lua b/scrolls/minecraft/minecraft-vanilla/1.21.10/packet_handler/minecraft.lua new file mode 100644 index 00000000..34bcfa9b --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.10/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Downloading snapshot... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Extracting snapshot..." + end + obj.description = "Restoring Minecraft Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Backing up..." + end + obj.description = "Backing up Minecraft Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Minecraft Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.10/scroll.yaml b/scrolls/minecraft/minecraft-vanilla/1.21.10/scroll.yaml new file mode 100644 index 00000000..a46cdc09 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.10/scroll.yaml @@ -0,0 +1,58 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-vanilla +desc: Minecraft Vanilla +version: 0.0.1 +app_version: 1.21.10 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + dependencies: [jdk21] + run: restart + procedures: + - mode: exec + data: + - bash + - ./start.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - server.jar + - https://piston-data.mojang.com/v1/objects/95495a7f485eedd84ce928cef5e223b757d2f764/server.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.10/update/.gitkeep b/scrolls/minecraft/minecraft-vanilla/1.21.10/update/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.11/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/minecraft-vanilla/1.21.11/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.11/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.11/init-files/server.properties.scroll_template b/scrolls/minecraft/minecraft-vanilla/1.21.11/init-files/server.properties.scroll_template new file mode 100644 index 00000000..56c0dc3c --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.11/init-files/server.properties.scroll_template @@ -0,0 +1,50 @@ +#Minecraft server properties +#Mon Apr 11 00:50:50 CEST 2022 +enable-jmx-monitoring=false +rcon.port=25575 +gamemode=survival +enable-command-block=false +enable-query=false +level-name=world +motd=A Minecraft Server +query.port=25565 +pvp=true +difficulty=easy +network-compression-threshold=256 +require-resource-pack=false +max-tick-time=60000 +use-native-transport=true +max-players=20 +online-mode=true +enable-status=true +allow-flight=false +broadcast-rcon-to-ops=true +view-distance=10 +server-ip= +resource-pack-prompt= +allow-nether=true +server-port=25565 +enable-rcon=true +sync-chunk-writes=true +op-permission-level=4 +prevent-proxy-connections=false +resource-pack= +entity-broadcast-range-percentage=100 +rcon.password={{ .Config.rcon.password }} +player-idle-timeout=0 +debug=false +force-gamemode=false +rate-limit=0 +hardcore=false +white-list=false +broadcast-console-to-ops=true +spawn-npcs=true +spawn-animals=true +snooper-enabled=true +function-permission-level=2 +text-filtering-config= +spawn-monsters=true +enforce-whitelist=false +resource-pack-sha1= +spawn-protection=16 +max-world-size=29999984 diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.11/init-files/start.sh b/scrolls/minecraft/minecraft-vanilla/1.21.11/init-files/start.sh new file mode 100755 index 00000000..5eed47c0 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.11/init-files/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar server.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.11/init-files/update.sh b/scrolls/minecraft/minecraft-vanilla/1.21.11/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.11/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.11/packet_handler/json.lua b/scrolls/minecraft/minecraft-vanilla/1.21.11/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.11/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.11/packet_handler/minecraft.lua b/scrolls/minecraft/minecraft-vanilla/1.21.11/packet_handler/minecraft.lua new file mode 100644 index 00000000..34bcfa9b --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.11/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Downloading snapshot... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Extracting snapshot..." + end + obj.description = "Restoring Minecraft Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Backing up..." + end + obj.description = "Backing up Minecraft Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Minecraft Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.11/scroll.yaml b/scrolls/minecraft/minecraft-vanilla/1.21.11/scroll.yaml new file mode 100644 index 00000000..9f2a776a --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.11/scroll.yaml @@ -0,0 +1,58 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-vanilla +desc: Minecraft Vanilla +version: 0.0.1 +app_version: 1.21.11 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + dependencies: [jdk21] + run: restart + procedures: + - mode: exec + data: + - bash + - ./start.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - server.jar + - https://piston-data.mojang.com/v1/objects/64bb6d763bed0a9f1d632ec347938594144943ed/server.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.11/update/.gitkeep b/scrolls/minecraft/minecraft-vanilla/1.21.11/update/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.8/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/minecraft-vanilla/1.21.8/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.8/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.8/init-files/server.properties.scroll_template b/scrolls/minecraft/minecraft-vanilla/1.21.8/init-files/server.properties.scroll_template new file mode 100644 index 00000000..56c0dc3c --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.8/init-files/server.properties.scroll_template @@ -0,0 +1,50 @@ +#Minecraft server properties +#Mon Apr 11 00:50:50 CEST 2022 +enable-jmx-monitoring=false +rcon.port=25575 +gamemode=survival +enable-command-block=false +enable-query=false +level-name=world +motd=A Minecraft Server +query.port=25565 +pvp=true +difficulty=easy +network-compression-threshold=256 +require-resource-pack=false +max-tick-time=60000 +use-native-transport=true +max-players=20 +online-mode=true +enable-status=true +allow-flight=false +broadcast-rcon-to-ops=true +view-distance=10 +server-ip= +resource-pack-prompt= +allow-nether=true +server-port=25565 +enable-rcon=true +sync-chunk-writes=true +op-permission-level=4 +prevent-proxy-connections=false +resource-pack= +entity-broadcast-range-percentage=100 +rcon.password={{ .Config.rcon.password }} +player-idle-timeout=0 +debug=false +force-gamemode=false +rate-limit=0 +hardcore=false +white-list=false +broadcast-console-to-ops=true +spawn-npcs=true +spawn-animals=true +snooper-enabled=true +function-permission-level=2 +text-filtering-config= +spawn-monsters=true +enforce-whitelist=false +resource-pack-sha1= +spawn-protection=16 +max-world-size=29999984 diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.8/init-files/start.sh b/scrolls/minecraft/minecraft-vanilla/1.21.8/init-files/start.sh new file mode 100755 index 00000000..5eed47c0 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.8/init-files/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar server.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.8/init-files/update.sh b/scrolls/minecraft/minecraft-vanilla/1.21.8/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.8/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.8/packet_handler/json.lua b/scrolls/minecraft/minecraft-vanilla/1.21.8/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.8/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.8/packet_handler/minecraft.lua b/scrolls/minecraft/minecraft-vanilla/1.21.8/packet_handler/minecraft.lua new file mode 100644 index 00000000..34bcfa9b --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.8/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Downloading snapshot... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Extracting snapshot..." + end + obj.description = "Restoring Minecraft Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Backing up..." + end + obj.description = "Backing up Minecraft Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Minecraft Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.8/scroll.yaml b/scrolls/minecraft/minecraft-vanilla/1.21.8/scroll.yaml new file mode 100644 index 00000000..83ed9ab4 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.8/scroll.yaml @@ -0,0 +1,58 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-vanilla +desc: Minecraft Vanilla +version: 0.0.1 +app_version: 1.21.8 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + dependencies: [jdk21] + run: restart + procedures: + - mode: exec + data: + - bash + - ./start.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - server.jar + - https://piston-data.mojang.com/v1/objects/6bce4ef400e4efaa63a13d5e6f6b500be969ef81/server.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.8/update/.gitkeep b/scrolls/minecraft/minecraft-vanilla/1.21.8/update/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.9/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/minecraft-vanilla/1.21.9/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.9/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.9/init-files/server.properties.scroll_template b/scrolls/minecraft/minecraft-vanilla/1.21.9/init-files/server.properties.scroll_template new file mode 100644 index 00000000..56c0dc3c --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.9/init-files/server.properties.scroll_template @@ -0,0 +1,50 @@ +#Minecraft server properties +#Mon Apr 11 00:50:50 CEST 2022 +enable-jmx-monitoring=false +rcon.port=25575 +gamemode=survival +enable-command-block=false +enable-query=false +level-name=world +motd=A Minecraft Server +query.port=25565 +pvp=true +difficulty=easy +network-compression-threshold=256 +require-resource-pack=false +max-tick-time=60000 +use-native-transport=true +max-players=20 +online-mode=true +enable-status=true +allow-flight=false +broadcast-rcon-to-ops=true +view-distance=10 +server-ip= +resource-pack-prompt= +allow-nether=true +server-port=25565 +enable-rcon=true +sync-chunk-writes=true +op-permission-level=4 +prevent-proxy-connections=false +resource-pack= +entity-broadcast-range-percentage=100 +rcon.password={{ .Config.rcon.password }} +player-idle-timeout=0 +debug=false +force-gamemode=false +rate-limit=0 +hardcore=false +white-list=false +broadcast-console-to-ops=true +spawn-npcs=true +spawn-animals=true +snooper-enabled=true +function-permission-level=2 +text-filtering-config= +spawn-monsters=true +enforce-whitelist=false +resource-pack-sha1= +spawn-protection=16 +max-world-size=29999984 diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.9/init-files/start.sh b/scrolls/minecraft/minecraft-vanilla/1.21.9/init-files/start.sh new file mode 100755 index 00000000..5eed47c0 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.9/init-files/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar server.jar nogui \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.9/init-files/update.sh b/scrolls/minecraft/minecraft-vanilla/1.21.9/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.9/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.9/packet_handler/json.lua b/scrolls/minecraft/minecraft-vanilla/1.21.9/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.9/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.9/packet_handler/minecraft.lua b/scrolls/minecraft/minecraft-vanilla/1.21.9/packet_handler/minecraft.lua new file mode 100644 index 00000000..34bcfa9b --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.9/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Downloading snapshot... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Extracting snapshot..." + end + obj.description = "Restoring Minecraft Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Backing up..." + end + obj.description = "Backing up Minecraft Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Minecraft Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.9/scroll.yaml b/scrolls/minecraft/minecraft-vanilla/1.21.9/scroll.yaml new file mode 100644 index 00000000..c6442ac1 --- /dev/null +++ b/scrolls/minecraft/minecraft-vanilla/1.21.9/scroll.yaml @@ -0,0 +1,58 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-vanilla +desc: Minecraft Vanilla +version: 0.0.1 +app_version: 1.21.9 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + dependencies: [jdk21] + run: restart + procedures: + - mode: exec + data: + - bash + - ./start.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - server.jar + - https://piston-data.mojang.com/v1/objects/11e54c2081420a4d49db3007e66c80a22579ff2a/server.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/minecraft-vanilla/1.21.9/update/.gitkeep b/scrolls/minecraft/minecraft-vanilla/1.21.9/update/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scrolls/minecraft/papermc/1.21.10/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/papermc/1.21.10/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.10/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/papermc/1.21.10/init-files/server.properties.scroll_template b/scrolls/minecraft/papermc/1.21.10/init-files/server.properties.scroll_template new file mode 100644 index 00000000..56c0dc3c --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.10/init-files/server.properties.scroll_template @@ -0,0 +1,50 @@ +#Minecraft server properties +#Mon Apr 11 00:50:50 CEST 2022 +enable-jmx-monitoring=false +rcon.port=25575 +gamemode=survival +enable-command-block=false +enable-query=false +level-name=world +motd=A Minecraft Server +query.port=25565 +pvp=true +difficulty=easy +network-compression-threshold=256 +require-resource-pack=false +max-tick-time=60000 +use-native-transport=true +max-players=20 +online-mode=true +enable-status=true +allow-flight=false +broadcast-rcon-to-ops=true +view-distance=10 +server-ip= +resource-pack-prompt= +allow-nether=true +server-port=25565 +enable-rcon=true +sync-chunk-writes=true +op-permission-level=4 +prevent-proxy-connections=false +resource-pack= +entity-broadcast-range-percentage=100 +rcon.password={{ .Config.rcon.password }} +player-idle-timeout=0 +debug=false +force-gamemode=false +rate-limit=0 +hardcore=false +white-list=false +broadcast-console-to-ops=true +spawn-npcs=true +spawn-animals=true +snooper-enabled=true +function-permission-level=2 +text-filtering-config= +spawn-monsters=true +enforce-whitelist=false +resource-pack-sha1= +spawn-protection=16 +max-world-size=29999984 diff --git a/scrolls/minecraft/papermc/1.21.10/init-files/start.sh b/scrolls/minecraft/papermc/1.21.10/init-files/start.sh new file mode 100755 index 00000000..828a6315 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.10/init-files/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar paper.jar nogui diff --git a/scrolls/minecraft/papermc/1.21.10/init-files/update.sh b/scrolls/minecraft/papermc/1.21.10/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.10/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/papermc/1.21.10/packet_handler/json.lua b/scrolls/minecraft/papermc/1.21.10/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.10/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/papermc/1.21.10/packet_handler/minecraft.lua b/scrolls/minecraft/papermc/1.21.10/packet_handler/minecraft.lua new file mode 100644 index 00000000..34bcfa9b --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.10/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Downloading snapshot... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Extracting snapshot..." + end + obj.description = "Restoring Minecraft Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Backing up..." + end + obj.description = "Backing up Minecraft Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Minecraft Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/papermc/1.21.10/scroll.yaml b/scrolls/minecraft/papermc/1.21.10/scroll.yaml new file mode 100644 index 00000000..88792568 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.10/scroll.yaml @@ -0,0 +1,58 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-paper +desc: PaperMC +version: 0.0.1 +app_version: 1.21.10 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + run: restart + dependencies: [jdk21] + procedures: + - mode: exec + data: + - bash + - ./start.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - paper.jar + - http://192.168.100.200:9000/snapshot-cache/minecraft/papermc/paper-1.21.10.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/papermc/1.21.11/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/papermc/1.21.11/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.11/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/papermc/1.21.11/init-files/server.properties.scroll_template b/scrolls/minecraft/papermc/1.21.11/init-files/server.properties.scroll_template new file mode 100644 index 00000000..56c0dc3c --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.11/init-files/server.properties.scroll_template @@ -0,0 +1,50 @@ +#Minecraft server properties +#Mon Apr 11 00:50:50 CEST 2022 +enable-jmx-monitoring=false +rcon.port=25575 +gamemode=survival +enable-command-block=false +enable-query=false +level-name=world +motd=A Minecraft Server +query.port=25565 +pvp=true +difficulty=easy +network-compression-threshold=256 +require-resource-pack=false +max-tick-time=60000 +use-native-transport=true +max-players=20 +online-mode=true +enable-status=true +allow-flight=false +broadcast-rcon-to-ops=true +view-distance=10 +server-ip= +resource-pack-prompt= +allow-nether=true +server-port=25565 +enable-rcon=true +sync-chunk-writes=true +op-permission-level=4 +prevent-proxy-connections=false +resource-pack= +entity-broadcast-range-percentage=100 +rcon.password={{ .Config.rcon.password }} +player-idle-timeout=0 +debug=false +force-gamemode=false +rate-limit=0 +hardcore=false +white-list=false +broadcast-console-to-ops=true +spawn-npcs=true +spawn-animals=true +snooper-enabled=true +function-permission-level=2 +text-filtering-config= +spawn-monsters=true +enforce-whitelist=false +resource-pack-sha1= +spawn-protection=16 +max-world-size=29999984 diff --git a/scrolls/minecraft/papermc/1.21.11/init-files/start.sh b/scrolls/minecraft/papermc/1.21.11/init-files/start.sh new file mode 100755 index 00000000..828a6315 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.11/init-files/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar paper.jar nogui diff --git a/scrolls/minecraft/papermc/1.21.11/init-files/update.sh b/scrolls/minecraft/papermc/1.21.11/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.11/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/papermc/1.21.11/packet_handler/json.lua b/scrolls/minecraft/papermc/1.21.11/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.11/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/papermc/1.21.11/packet_handler/minecraft.lua b/scrolls/minecraft/papermc/1.21.11/packet_handler/minecraft.lua new file mode 100644 index 00000000..34bcfa9b --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.11/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Downloading snapshot... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Extracting snapshot..." + end + obj.description = "Restoring Minecraft Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Backing up..." + end + obj.description = "Backing up Minecraft Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Minecraft Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/papermc/1.21.11/scroll.yaml b/scrolls/minecraft/papermc/1.21.11/scroll.yaml new file mode 100644 index 00000000..2cc9e49a --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.11/scroll.yaml @@ -0,0 +1,58 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-paper +desc: PaperMC +version: 0.0.1 +app_version: 1.21.11 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + run: restart + dependencies: [jdk21] + procedures: + - mode: exec + data: + - bash + - ./start.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - paper.jar + - http://192.168.100.200:9000/snapshot-cache/minecraft/papermc/paper-1.21.11.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/papermc/1.21.8/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/papermc/1.21.8/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.8/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/papermc/1.21.8/init-files/server.properties.scroll_template b/scrolls/minecraft/papermc/1.21.8/init-files/server.properties.scroll_template new file mode 100644 index 00000000..56c0dc3c --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.8/init-files/server.properties.scroll_template @@ -0,0 +1,50 @@ +#Minecraft server properties +#Mon Apr 11 00:50:50 CEST 2022 +enable-jmx-monitoring=false +rcon.port=25575 +gamemode=survival +enable-command-block=false +enable-query=false +level-name=world +motd=A Minecraft Server +query.port=25565 +pvp=true +difficulty=easy +network-compression-threshold=256 +require-resource-pack=false +max-tick-time=60000 +use-native-transport=true +max-players=20 +online-mode=true +enable-status=true +allow-flight=false +broadcast-rcon-to-ops=true +view-distance=10 +server-ip= +resource-pack-prompt= +allow-nether=true +server-port=25565 +enable-rcon=true +sync-chunk-writes=true +op-permission-level=4 +prevent-proxy-connections=false +resource-pack= +entity-broadcast-range-percentage=100 +rcon.password={{ .Config.rcon.password }} +player-idle-timeout=0 +debug=false +force-gamemode=false +rate-limit=0 +hardcore=false +white-list=false +broadcast-console-to-ops=true +spawn-npcs=true +spawn-animals=true +snooper-enabled=true +function-permission-level=2 +text-filtering-config= +spawn-monsters=true +enforce-whitelist=false +resource-pack-sha1= +spawn-protection=16 +max-world-size=29999984 diff --git a/scrolls/minecraft/papermc/1.21.8/init-files/start.sh b/scrolls/minecraft/papermc/1.21.8/init-files/start.sh new file mode 100755 index 00000000..828a6315 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.8/init-files/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar paper.jar nogui diff --git a/scrolls/minecraft/papermc/1.21.8/init-files/update.sh b/scrolls/minecraft/papermc/1.21.8/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.8/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/papermc/1.21.8/packet_handler/json.lua b/scrolls/minecraft/papermc/1.21.8/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.8/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/papermc/1.21.8/packet_handler/minecraft.lua b/scrolls/minecraft/papermc/1.21.8/packet_handler/minecraft.lua new file mode 100644 index 00000000..34bcfa9b --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.8/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Downloading snapshot... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Extracting snapshot..." + end + obj.description = "Restoring Minecraft Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Backing up..." + end + obj.description = "Backing up Minecraft Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Minecraft Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/papermc/1.21.8/scroll.yaml b/scrolls/minecraft/papermc/1.21.8/scroll.yaml new file mode 100644 index 00000000..d90e3220 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.8/scroll.yaml @@ -0,0 +1,58 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-paper +desc: PaperMC +version: 0.0.1 +app_version: 1.21.8 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + run: restart + dependencies: [jdk21] + procedures: + - mode: exec + data: + - bash + - ./start.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - paper.jar + - http://192.168.100.200:9000/snapshot-cache/minecraft/papermc/paper-1.21.8.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {} diff --git a/scrolls/minecraft/papermc/1.21.9/init-files-template/.scroll_config.yml.scroll_template b/scrolls/minecraft/papermc/1.21.9/init-files-template/.scroll_config.yml.scroll_template new file mode 100644 index 00000000..736724a1 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.9/init-files-template/.scroll_config.yml.scroll_template @@ -0,0 +1,3 @@ +rcon: + password: {{ randAlphaNum 50 }} + port: 25575 \ No newline at end of file diff --git a/scrolls/minecraft/papermc/1.21.9/init-files/server.properties.scroll_template b/scrolls/minecraft/papermc/1.21.9/init-files/server.properties.scroll_template new file mode 100644 index 00000000..56c0dc3c --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.9/init-files/server.properties.scroll_template @@ -0,0 +1,50 @@ +#Minecraft server properties +#Mon Apr 11 00:50:50 CEST 2022 +enable-jmx-monitoring=false +rcon.port=25575 +gamemode=survival +enable-command-block=false +enable-query=false +level-name=world +motd=A Minecraft Server +query.port=25565 +pvp=true +difficulty=easy +network-compression-threshold=256 +require-resource-pack=false +max-tick-time=60000 +use-native-transport=true +max-players=20 +online-mode=true +enable-status=true +allow-flight=false +broadcast-rcon-to-ops=true +view-distance=10 +server-ip= +resource-pack-prompt= +allow-nether=true +server-port=25565 +enable-rcon=true +sync-chunk-writes=true +op-permission-level=4 +prevent-proxy-connections=false +resource-pack= +entity-broadcast-range-percentage=100 +rcon.password={{ .Config.rcon.password }} +player-idle-timeout=0 +debug=false +force-gamemode=false +rate-limit=0 +hardcore=false +white-list=false +broadcast-console-to-ops=true +spawn-npcs=true +spawn-animals=true +snooper-enabled=true +function-permission-level=2 +text-filtering-config= +spawn-monsters=true +enforce-whitelist=false +resource-pack-sha1= +spawn-protection=16 +max-world-size=29999984 diff --git a/scrolls/minecraft/papermc/1.21.9/init-files/start.sh b/scrolls/minecraft/papermc/1.21.9/init-files/start.sh new file mode 100755 index 00000000..828a6315 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.9/init-files/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +MAX=${DRUID_MAX_MEMORY%?} +if [ -z "${MAX}" ]; +then + MAX=1024M +fi + +java -Xmx$MAX -Xms1024M -jar paper.jar nogui diff --git a/scrolls/minecraft/papermc/1.21.9/init-files/update.sh b/scrolls/minecraft/papermc/1.21.9/init-files/update.sh new file mode 100755 index 00000000..966812fe --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.9/init-files/update.sh @@ -0,0 +1,34 @@ +#default update script + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ ! -f "$SCRIPTPATH/scroll-lock.json" ]; then + echo "Scroll lock not found. Skipping update" + exit 0 +fi + +if [ -z "$(ls $SCRIPTPATH/update)" ]; then + echo "Update directory is empty. Skipping update" +else + versionsDirs=$(find $SCRIPTPATH/update/* -maxdepth 0 -type d | sort --version-sort) + current=$(cat $SCRIPTPATH/scroll-lock.json | jq -r .scroll_version) + + for versionsDir in $versionsDirs + do + version=$(basename $versionsDir) + if [ ! "$(printf '%s\n' "$version" "$current" | sort -V | head -n1)" = "$version" ] ; + then + echo "$versionsDir/update.sh" + if [ -f "$versionsDir/update.sh" ]; then + sh $versionsDir/update.sh + else + echo "Warning: update $version has no update.sh... skipping" + fi + fi + done +fi + + + +LATEST_VERSION=$(cat $SCRIPTPATH/scroll.yaml | yq -r .version) +jq --arg LV "$LATEST_VERSION" -r '.scroll_version = $LV' $SCRIPTPATH/scroll-lock.json | sponge $SCRIPTPATH/scroll-lock.json \ No newline at end of file diff --git a/scrolls/minecraft/papermc/1.21.9/packet_handler/json.lua b/scrolls/minecraft/papermc/1.21.9/packet_handler/json.lua new file mode 100644 index 00000000..54d44484 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.9/packet_handler/json.lua @@ -0,0 +1,388 @@ +-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/scrolls/minecraft/papermc/1.21.9/packet_handler/minecraft.lua b/scrolls/minecraft/papermc/1.21.9/packet_handler/minecraft.lua new file mode 100644 index 00000000..34bcfa9b --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.9/packet_handler/minecraft.lua @@ -0,0 +1,262 @@ +json = require("packet_handler/json") + +function string.fromhex(str) + return (str:gsub('..', function(cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function(c) + return string.format('%02X', string.byte(c)) + end)) +end + +-- Bitwise AND +local function band(a, b) + local result = 0 + local bitval = 1 + while a > 0 and b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 and bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Bitwise OR +local function bor(a, b) + local result = 0 + local bitval = 1 + while a > 0 or b > 0 do + local abit = a % 2 + local bbit = b % 2 + if abit == 1 or bbit == 1 then + result = result + bitval + end + a = math.floor(a / 2) + b = math.floor(b / 2) + bitval = bitval * 2 + end + return result +end + +-- Right Shift +local function rshift(value, shift) + return math.floor(value / (2 ^ shift)) +end + +-- Left Shift +local function lshift(value, shift) + return value * (2 ^ shift) +end + +function encodeLEB128(value) + local bytes = {} + repeat + local byte = band(value, 0x7F) + value = rshift(value, 7) + if value ~= 0 then + byte = bor(byte, 0x80) + end + table.insert(bytes, byte) + until value == 0 + return bytes +end + +function decodeLEB128(bytes) + local result = 0 + local shift = 0 + local bytesConsumed = 0 -- Track the number of bytes consumed + + for i, byte in ipairs(bytes) do + local value = band(byte, 0x7F) -- Get lower 7 bits + result = bor(result, lshift(value, shift)) -- Add it to result with the correct shift + bytesConsumed = bytesConsumed + 1 -- Increment the byte counter + if band(byte, 0x80) == 0 then -- If the highest bit is not set, we are done + break + end + shift = shift + 7 -- Move to the next group of 7 bits + end + + return result, bytesConsumed -- Return both the result and the number of bytes consumed +end + +function handle(ctx, data) + hex = string.tohex(data) + + debug_print("Received Packet: " .. hex) + + -- check if hex starts with 0x01 0x00 + if hex:sub(1, 4) == "FE01" then + debug_print("Received Legacy Ping Packet") + sendData(string.fromhex( + "ff002300a7003100000034003700000031002e0034002e0032000000410020004d0069006e006500630072006100660074002000530065007200760065007200000030000000320030")) + end + + local packetNo = 0 + + local maxLoops = 2 + + restBytes = data + + while hex ~= "" do + + queue = get_queue() + + hex = string.tohex(restBytes) + + debug_print("Remaining Bytes: " .. hex) + packetNo = packetNo + 1 + debug_print("Packet No: " .. packetNo) + + packetLength, bytesConsumed = decodeLEB128({string.byte(restBytes, 1, 1)}) + debug_print("Packet Length: " .. packetLength) + + -- cut of consumedBytes and read untul packetLength + packetWithLength = string.sub(restBytes, bytesConsumed + 1, packetLength + bytesConsumed) + + -- next varint is the packetid + packetId, bytesConsumed = decodeLEB128({string.byte(packetWithLength, 1, 1)}) + + debug_print("Packet ID: " .. packetId) + + packetWithLengthHex = string.tohex(packetWithLength) + + debug_print("Trimmed Packet: " .. packetWithLengthHex) + + -- make hex to the rest of the data + restBytes = string.sub(restBytes, packetLength + bytesConsumed + 1) + + debug_print("Rest Bytes: " .. string.tohex(restBytes)) + + if packetLength == 1 and packetId == 0 then + debug_print("Received Status Packet " .. packetWithLengthHex) + sendData(pingResponse()) + + -- check if second byte is 0x01 + elseif packetId == 1 then + debug_print("Received Ping Packet " .. packetWithLengthHex) + -- send same packet back + close(data) + -- login packet 0x20 0x00 + elseif packetId == 0 and packetWithLengthHex:sub(-2) == "02" then -- check for enum at the end + debug_print("Received Login Packet " .. packetWithLengthHex) + -- return + -- debug_print("Received Login Packet") + + sendData(disconnectResponse()) + -- sleep for a sec before closing + finish() + -- return + else + debug_print("Received unknown packet " .. packetWithLengthHex) + -- close("") + end + end +end + +function formatResponse(jsonObj) + local response = json.encode(jsonObj) + local responseBuffer = {string.byte(response, 1, -1)} + local additional = {0x00} + local responseBufferLength = encodeLEB128(#responseBuffer) + local packetLenthBuffer = encodeLEB128(#responseBuffer + #responseBufferLength + 1) + + local concatedBytes = {} + + for i = 1, #packetLenthBuffer do + table.insert(concatedBytes, packetLenthBuffer[i]) + end + + for i = 1, #additional do + table.insert(concatedBytes, additional[i]) + end + + for i = 1, #responseBufferLength do + table.insert(concatedBytes, responseBufferLength[i]) + end + + for i = 1, #responseBuffer do + table.insert(concatedBytes, responseBuffer[i]) + end + + -- convert back to string + local finalString = string.char(unpack(concatedBytes)) + + return finalString +end + +function pingResponse() + + local description = { + color = "red", + extra = {"\n", { + color = "gray", + extra = {{ + bold = true, + text = "HINT" + }, ":", " ", { + color = "white", + text = "Get free servers at:" + }, " ", { + color = "green", + text = "druid.gg" + }}, + text = "" + }}, + text = "This server is in standby." + } + + local obj = { + version = { + name = "§9🕐 Waiting...", + protocol = -1 + }, + description = description, + players = { + max = 0, + online = 1 + }, + favicon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAAXNSR0IArs4c6QAAAMlQTFRFR3BM6ndq5Wxb3WBQ6HFi0EUvvVxI8IBzzTwm0EUv11RC3GBQ7X1w00w50EUv42pa1lRB3mNT4WZV0Ugz2VlH0ks22lpJ0ks332RU1VI/6XZo8oV4421e63Zn32JR0046ytvZ2FZEieHa5nBgb+fZFerZ1NrZDOrZDurZ1tjYQunZztrZO+jZruDZFOrZDOrZDOrZ6HVoDOrZ09rZ0cvJn+LZbebZi+PZkOPZC+rZ942B7Xpr9op98oR29Id67n1uz9vZH+rZjeTZHadAYQAAADl0Uk5TAOr9sP4WBv4CDXqV8kcf3m277CmGPaAzx1Pg8tD90lw3YxDx/mzTQ+aq/nYk/bT50NSS71SwxIbiWYkesQAABERJREFUeNqll2tfozgUxkshIeF+vxWoiNfRUaszuztDC7rf/0PtISAlpR1dfPLzTZLzz3POIUgXp0XD2PJUkGetfbT4fyJI9+xNsuqVbGx1beDPh7uKnazq7e+96lWSqj79XLihpKv691SrRPU/4YLGtsbCp9quNp5BPjreE1j4KYT9ZxPYDbQt7GObW9XwxxHqTUz/EB/a8hbC2+iVJpiRbUdpokE92RwbdVJQcjp+x3Ztay0N1iFClFLk6oqYMEa3thUKeqp74q7zLYjQdUzIgjBhGiqRBohOdaLjo/FIldm6FhWIEH4NG8pGHgiReywJagnd8eqwzCF0cTAhq/TIDt+stzAE79Rz76pAYKMW4ukZKJDr9nzldJcMIHSd3dloYiAWapCm8iu83ECrO00tIHEH87JojCfP78/O7u/x/pQw3bEcYCM9MKALANht9HH42d3Pn389PF9enw/bLNjWapf4vAUcyDCreaMGn91dfb/49gv09HxNegAS5ZohNIUHuGlrIHVH8bcv/0I40+MDEDoVYGEHkkXMZbAWYBIMjOJfIX7Qw3W/0YjkHSBqOTW4DFQNAElIhvxvX76z+MHDfU+AnUyJPwZQG7jjyv64er34NdbNZb/CvMJmYT0GGCkANAXvDbyCAU7vFkJTZgRNGQP8RAamTsYVeOPiH5/6KqD2LNiteWNALMCUaewBXAZcDjTtHajjJhSCLMvRtARTAAEAEwdYWABoRPwhgJWrkYcUeEAAgNMpPF0P5WLii7g+AJxzReS6AGcxCRZXxKQZAwi5ezlo4+Mz7i9NxeKbRB8DQrPhasD1kcsgTJsOwD/KKAcAdGGv9iq+jUvYG1AE2Amj4l8IWKyaxkRkNANJ7Ak3z+e9gahqmAT+OhMAN6VPRjOYvQ7euqfwso9HQdZ0Mn0eoJtVkymYmzu7vfrn4tvNDbxP+gWqJL0BlgF/HbPJJI5/3N39fXk5vBSRBcd0KteEBxClrCoz5Gf1IEYLMvBc7z2+ykQ0eWPnVVUqmLcV5J6PujnqFmJZNf0wdXIIwB5YyN3FQWWWqWrFuh4Xnlhm1btKDx/51xxl/QJPlcrSNM1SyqpBknjsQwdbZZWZOk81RKmaSLLDaTzrsVSVosFT/UiqMhhVto8/9ZlEQpYE5Qk6EDpl3XACLp7vu5llpoUPPKgOIDIIbSHLyOLy50ULJ5PMNTmoQ6zmzlICLR3bCunitAi1gJDH+MAZaj+7PU8pdJd+9I2ttIQ1nmRHEUIUk8WHQpYjSXlBF3NFaGFKkqkgMhtB41ySnMDFswlYt5fSMorpbBPEDRww4bl4LgKakbcm1gh/IY3WhKjPRhDDa004wXwE1kWzQxhzEciynRYhFuHcx8JQGGKZe7FLZ3a0RbB7qIRzERbUorURWWhuQ9Zq5CyXS0dBs++HbwU5EKwv3FJDh2rk/uILoqFlT38O/QdGyOZnTVzZRwAAAABJRU5ErkJggg==" + } + + local snapshotMode = get_snapshot_mode() + local snapshotPercentage = get_snapshot_percentage() + + if snapshotMode ~= "noop" then + if snapshotMode == "restore" then + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Downloading snapshot... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Extracting snapshot..." + end + obj.description = "Restoring Minecraft Server, this might take a moment" + else + if snapshotPercentage == nil or snapshotPercentage == 100 then + obj.version.name = "§2▶ Backing up... " + format("%.2f", snapshotPercentage) + "%" + else + obj.version.name = "§2▶ Backing up..." + end + obj.description = "Backing up Minecraft Server, this might take a moment" + end + elseif queue ~= nil and queue["install"] == "running" then + obj.version.name = "§2▶ Installing..." + obj.description = "Installing Minecraft Server, this might take a moment" + elseif get_finish_sec() ~= nil then + obj.version.name = "§2▶ Starting..." + obj.description = "Starting " .. math.ceil(get_finish_sec()) .. "s" + end + + return formatResponse(obj) +end + +function disconnectResponse() + local obj = "Our super cool system will start now... please wait" + return formatResponse(obj) +end diff --git a/scrolls/minecraft/papermc/1.21.9/scroll.yaml b/scrolls/minecraft/papermc/1.21.9/scroll.yaml new file mode 100644 index 00000000..4ae33536 --- /dev/null +++ b/scrolls/minecraft/papermc/1.21.9/scroll.yaml @@ -0,0 +1,58 @@ +name: artifacts.druid.gg/druid-team/scroll-minecraft-paper +desc: PaperMC +version: 0.0.1 +app_version: 1.21.9 +ports: + - name: main + protocol: tcp + port: 25565 + sleep_handler: packet_handler/minecraft.lua + start_delay: 10 + finish_after_command: install + - name: rcon + protocol: tcp + port: 25575 +init: "start" +commands: + start: + needs: [install] + run: restart + dependencies: [jdk21] + procedures: + - mode: exec + data: + - bash + - ./start.sh + stop: + procedures: + - mode: rcon + data: stop + install: + run: once + dependencies: [wget, cacert] + procedures: + - mode: exec + data: + - wget + - -q + - -O + - paper.jar + - http://192.168.100.200:9000/snapshot-cache/minecraft/papermc/paper-1.21.9.jar + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt + update: + procedures: + - mode: exec + data: + - sh + - $SCROLL_DIR/update.sh + - mode: exec + data: + - bash + - -c + - echo eula=true > eula.txt +plugins: + rcon: {}