diff --git a/manifests/games/Minecraft/1.0.14/Minecraft - Linux.json b/manifests/games/Minecraft/1.0.14/Minecraft - Linux.json new file mode 100644 index 0000000..48b05e7 --- /dev/null +++ b/manifests/games/Minecraft/1.0.14/Minecraft - Linux.json @@ -0,0 +1,4103 @@ +{ + "__TCA:ExportVersion": "3.10.27.45532", + "__TCA:ExportedAt": "2026-06-17T22:59:25.1631326Z", + "name": "Minecraft", + "shortName": "Minecraft", + "description": "", + "operatingSystem": "Linux", + "iconImage": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Logo_Minecraft.png/960px-Logo_Minecraft.png", + "backgroundImage": "https://img.primaservers.dk/minecraft/index.php", + "minSlots": 1, + "maxSlots": 128, + "defaultSlots": 10, + "startedUntilResponding": true, + "startingTimeout": "00:03:00", + "defaultDiskSpace": 10737418240, + "categoryId": 1, + "editableExtensions": [ + ".txt", + ".cfg", + ".json", + ".xml" + ], + "logExtensions": [ + ".log" + ], + "queryRconProtocolConfig": { + "queryProtocol": "minecraft", + "rconProtocol": "", + "privateRule": "", + "privateRuleValue": "", + "hiddenRules": [] + }, + "consoleConfig": { + "enabled": true, + "outputSource": "Terminal", + "logFile": "logs/console.log", + "inputSource": "ConsoleCommands", + "printRconResponse": false, + "outputFilters": "", + "stopCommand": "stop", + "waitAfterStopCommand": 10000, + "screenCaptureTitleBar": true, + "screenCaptureQuality": 25, + "screenCaptureFPS": 10, + "screenCaptureAllowMouse": false, + "screenCaptureAllowKeys": true, + "screenCaptureMouseHardware": true + }, + "ipPortAllocationConfig": { + "usePrimaryIpOnly": false, + "useDefaultPortsOnly": false, + "supportsIpv6": false, + "uniquePort": "ServerIp", + "customPorts": [ + { + "id": "GamePort", + "port": 25565, + "expression": "" + }, + { + "id": "RConPort", + "port": 25575, + "expression": "" + }, + { + "id": "QueryPort", + "port": 25565, + "expression": "" + } + ], + "portIncrement": 10 + }, + "fileAndDirectoryConfig": { + "relativeExecutable": "minecraft_server.sh", + "autoSetupFolderName": "minecraft", + "failOnMissingExecutable": false, + "externalDownloadEnable": false + }, + "steamConfig": { + "steamUpdate": false, + "appId": 0, + "runAsServiceUser": false, + "branch": "public", + "storeId": 0, + "steamUsername": "anonymous", + "steamPassword": "", + "steamDownloadRetries": 5, + "updateAfterCreateOrReinstall": false, + "verifyAll": false, + "steamTool": "DepotDownloader" + }, + "gameCommandlineConfig": { + "privateCommandline": "-Xmx${Xmx}M -Xms${Xms}M -XX:\u002BUseG1GC -XX:\u002BParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:\u002BUnlockExperimentalVMOptions -XX:\u002BDisableExplicitGC -XX:\u002BAlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:\u002BPerfDisableSharedMem -XX:MaxTenuringThreshold=1 -jar ${Jar} nogui" + }, + "commandlineConfig": { + "enableSelection": false, + "defaultCommandline": "-Xmx${Xmx}M -Xms${Xms}M -XX:\u002BUseG1GC -XX:\u002BParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:\u002BUnlockExperimentalVMOptions -XX:\u002BDisableExplicitGC -XX:\u002BAlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:\u002BPerfDisableSharedMem -XX:MaxTenuringThreshold=1 -jar ${Jar} nogui", + "defaultCustomCommandline": "", + "predefinedCommandlines": [ + { + "id": 1, + "name": "Default", + "commandLine": "-Xmx${Xmx}M -Xms${Xms}M -XX:\u002BUseG1GC -XX:\u002BParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:\u002BUnlockExperimentalVMOptions -XX:\u002BDisableExplicitGC -XX:\u002BAlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:\u002BPerfDisableSharedMem -XX:MaxTenuringThreshold=1 -jar ${Jar} nogui" + } + ] + }, + "runAsConfig": { + "runAs": "TCAGame", + "interactWithDesktop": true, + "elevated": false + }, + "gameVariables": [ + { + "name": "JavaVersion", + "defaultValue": "Automatic", + "required": true, + "requiredMessage": "The java version is required", + "scriptParameter": true, + "commandlineParameter": false, + "saveScriptParameter": true, + "syncCommandlineParameter": false, + "template": "${JavaVersion}", + "editor": { + "items": [ + { + "text": "", + "value": "Automatic" + }, + { + "text": "", + "value": "Java 8" + }, + { + "value": "Java 11" + }, + { + "value": "Java 16" + }, + { + "value": "Java 17" + }, + { + "value": "Java 21" + }, + { + "value": "Java 25" + } + ], + "id": "JavaVersion", + "defaultValue": "Automatic", + "label": "Select the Java version to use", + "description": "This value is required", + "required": true, + "valueType": "string", + "requiredMessage": "The java version is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-07-12T02:29:13.214", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "Xmx", + "defaultValue": "2048", + "required": true, + "requiredMessage": "A value for Xmx is required", + "scriptParameter": false, + "commandlineParameter": true, + "saveScriptParameter": false, + "syncCommandlineParameter": true, + "template": "${Xmx}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 128, + "id": "Xmx", + "defaultValue": 4096, + "label": "Xmx", + "description": "Max memory allocated by Java in megabytes", + "required": true, + "requiredMessage": "A value for Xmx is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:21:16.005", + "metadata": { + "notes": "" + }, + "variableRoles": [] + }, + { + "name": "Xms", + "defaultValue": "128", + "required": true, + "requiredMessage": "A value for Xms is required", + "scriptParameter": false, + "commandlineParameter": true, + "saveScriptParameter": false, + "syncCommandlineParameter": true, + "template": "${Xms}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "id": "Xms", + "defaultValue": "128", + "label": "Xms", + "description": "Min memory allocated by Java in megabytes", + "required": true, + "requiredMessage": "A value for Xms is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:23:14.911", + "metadata": { + "notes": "" + }, + "variableRoles": [] + }, + { + "name": "AllowFlight", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${AllowFlight}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "AllowFlight", + "defaultValue": "false", + "label": "Allow Flight", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:44:59.965", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "AllowNether", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${AllowNether}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "AllowNether", + "defaultValue": "true", + "label": "Allow Nether", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:46:37.852", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "BroadcastConsoleToOps", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${BroadcastConsoleToOps}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "BroadcastConsoleToOps", + "defaultValue": "true", + "label": "Broadcast Console To Ops", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:47:19.034", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "BroadcastRconToOps", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${BroadcastRconToOps}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "BroadcastRconToOps", + "defaultValue": "true", + "label": "Broadcast Rcon To Ops", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:48:30.508", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "Difficulty", + "defaultValue": "easy", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${Difficulty}", + "editor": { + "items": [ + { + "text": "peaceful" + }, + { + "text": "easy" + }, + { + "text": "normal" + }, + { + "text": "hard" + } + ], + "id": "Difficulty", + "defaultValue": "easy", + "label": "Difficulty", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:49:42.518", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EnableCommandBlock", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EnableCommandBlock}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableCommandBlock", + "defaultValue": "false", + "label": "Enable Command Block", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:50:16.927", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EnableJMXMonitoring", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EnableJMXMonitoring}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableJMXMonitoring", + "defaultValue": "false", + "label": "Enable JMX Monitoring", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:50:46.921", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EnableRcon", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EnableRcon}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableRcon", + "defaultValue": "false", + "label": "Enable Rcon", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:52:29.229", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EnableStatus", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EnableStatus}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableStatus", + "defaultValue": "true", + "label": "Enable Status", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:53:19.974", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EnforceWhiteList", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EnforceWhiteList}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnforceWhiteList", + "defaultValue": "false", + "label": "Enforce WhiteList", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:53:40.708", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EntityBroadcastRangePercentage", + "defaultValue": "100", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EntityBroadcastRangePercentage}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "maxValue": 100, + "id": "EntityBroadcastRangePercentage", + "defaultValue": "100", + "label": "Entity Broadcast Range Percentage", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:54:40.903", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "SpawnProtection", + "defaultValue": "16", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${SpawnProtection}", + "editor": { + "items": [ + { + "text": "Disabled", + "value": "0" + }, + { + "text": "3x3 area around the spawn point", + "value": "1" + }, + { + "text": "5x5 area around the spawn point", + "value": "2" + }, + { + "text": "7x7 area around the spawn point", + "value": "3" + }, + { + "text": "33x33 area around the spawn point", + "value": "16" + } + ], + "id": "SpawnProtection", + "defaultValue": "16", + "label": "Spawn Protection", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:55:56.796", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "MaxTickTime", + "defaultValue": "60000", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${MaxTickTime}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "MaxTickTime", + "defaultValue": "60000", + "label": "Max Tick Time", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:59:36.963", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "GeneratorSettings", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${GeneratorSettings}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "GeneratorSettings", + "label": "Generator Settings", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:02:07.457", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "SyncChunkWrites", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${SyncChunkWrites}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SyncChunkWrites", + "label": "Sync Chunk Writes", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:03:21.227", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "ForceGamemode", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${ForceGamemode}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "ForceGamemode", + "label": "Force Game mode", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:03:57.103", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "Gamemode", + "defaultValue": "0", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${Gamemode}", + "editor": { + "items": [ + { + "text": "Survival", + "value": "0" + }, + { + "text": "Creative", + "value": "1" + }, + { + "text": "Adventure", + "value": "2" + }, + { + "text": "Spectator", + "value": "3" + } + ], + "id": "Gamemode", + "defaultValue": "0", + "label": "Game Mode", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:04:32.185", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "PlayerIdleTimeout", + "defaultValue": "0", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${PlayerIdleTimeout}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "id": "PlayerIdleTimeout", + "defaultValue": "0", + "label": "Player Idle Timeout", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:09:12.013", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "TextFilteringConfig", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${TextFilteringConfig}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "TextFilteringConfig", + "label": "Text Filtering Config", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:10:23.327", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "SpawnMonsters", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${SpawnMonsters}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SpawnMonsters", + "defaultValue": "true", + "label": "Spawn Monsters", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:10:55.09", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "OpPermissionLevel", + "defaultValue": "4", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${OpPermissionLevel}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "minValue": 1, + "maxValue": 4, + "id": "OpPermissionLevel", + "defaultValue": "4", + "label": "Op Permission Level", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:11:29.749", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "PVP", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${PVP}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "PVP", + "label": "PVP", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:14:04.215", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "LevelType", + "defaultValue": "minecraft:normal", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${LevelType}", + "editor": { + "items": [ + { + "text": "minecraft:normal" + }, + { + "text": "minecraft:flat" + }, + { + "text": "minecraft:large_biomes" + }, + { + "text": "minecraft:amplified" + }, + { + "text": "minecraft:single_biome_surface" + } + ], + "id": "LevelType", + "defaultValue": "minecraft:normal", + "label": "Level Type", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:14:41.27", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "ResourcePackPrompt", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${ResourcePackPrompt}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "ResourcePackPrompt", + "label": "Resource Pack Prompt", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:15:05.743", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "Hardcore", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${Hardcore}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "Hardcore", + "defaultValue": "false", + "label": "Hardcore", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:15:47.202", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "NetworkCompressionThreshold", + "defaultValue": "256", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${NetworkCompressionThreshold}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "minValue": -1, + "maxValue": 512, + "id": "NetworkCompressionThreshold", + "defaultValue": "256", + "label": "Network Compression Threshold", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:16:28.751", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "MaxWorldSize", + "defaultValue": "29999984", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${MaxWorldSize}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "id": "MaxWorldSize", + "defaultValue": "29999984", + "label": "Max World Size", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:18:01.866", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "ResourcePackSHA1", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${ResourcePackSHA1}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "ResourcePackSHA1", + "label": "Resource Pack SHA1", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:18:41.876", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "FunctionPermissionLevel", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${FunctionPermissionLevel}", + "editor": { + "items": [ + { + "text": "All", + "value": "0" + }, + { + "text": "Moderator", + "value": "2" + }, + { + "text": "Admin", + "value": "3" + }, + { + "text": "Owner", + "value": "4" + } + ], + "id": "FunctionPermissionLevel", + "label": "Function Permission Level", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:19:35.944", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "SpawnNPCs", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${SpawnNPCs}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SpawnNPCs", + "defaultValue": "true", + "label": "Spawn NPCs", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:21:32.915", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "RequireResourcePack", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${RequireResourcePack}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "RequireResourcePack", + "defaultValue": "false", + "label": "Require Resource Pack", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:22:08.535", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "LevelName", + "defaultValue": "world", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${LevelName}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "LevelName", + "defaultValue": "world", + "label": "Level Name", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:22:53.826", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "ViewDistance", + "defaultValue": "10", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${ViewDistance}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "minValue": 1, + "maxValue": 32, + "id": "ViewDistance", + "defaultValue": "10", + "label": "View Distance", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:23:20.377", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "ResourcePack", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${ResourcePack}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "ResourcePack", + "label": "Resource Pack URL", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:24:29.712", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "SpawnAnimals", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${SpawnAnimals}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SpawnAnimals", + "defaultValue": "true", + "label": "Spawn Animals", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:25:03.163", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "WhiteList", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${WhiteList}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "WhiteList", + "defaultValue": "false", + "label": "White List", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:25:29.473", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "GenerateStructures", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${GenerateStructures}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "GenerateStructures", + "defaultValue": "true", + "label": "Generate Structures", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:26:37.263", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "OnlineMode", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${OnlineMode}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "OnlineMode", + "defaultValue": "true", + "label": "Online Mode", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:27:18.719", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "LevelSeed", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${LevelSeed}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "id": "LevelSeed", + "label": "Level Seed", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:27:45.9", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "PreventProxyConnections", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${PreventProxyConnections}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "PreventProxyConnections", + "defaultValue": "false", + "label": "Prevent Proxy Connections", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:28:33.688", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "UseNativeTransport", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${UseNativeTransport}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "UseNativeTransport", + "defaultValue": "true", + "label": "Use Native Transport", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:29:09.022", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "MOTD", + "defaultValue": "A Minecraft Server ", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${MOTD}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "MOTD", + "defaultValue": "A Minecraft Server ", + "label": "Message Of The Day", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:29:53.054", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "RateLimit", + "defaultValue": "0", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${RateLimit}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "RateLimit", + "defaultValue": "0", + "label": "Rate Limit", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:34:00.818", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EnforceSecureProfile", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EnforceSecureProfile}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnforceSecureProfile", + "defaultValue": "true", + "label": "Enforce Secure Profile", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:35:26.511", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "MaxChainedNeighborUpdates", + "defaultValue": "1000000", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${MaxChainedNeighborUpdates}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "id": "MaxChainedNeighborUpdates", + "defaultValue": "1000000", + "label": "Max Chained Neighbor Updates", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:36:05.199", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "HideOnlinePlayers", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${HideOnlinePlayers}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "HideOnlinePlayers", + "defaultValue": "false", + "label": "Hide Online Players", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:37:05.86", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "SimulationDistance", + "defaultValue": "10", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${SimulationDistance}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "minValue": 1, + "maxValue": 16, + "id": "SimulationDistance", + "defaultValue": "10", + "label": "Simulation Distance", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:37:50.322", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "Jar", + "defaultValue": "minecraft_server.jar", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": true, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${Jar}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "Jar", + "defaultValue": "minecraft_server.jar", + "label": "Jar", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-06-26T00:58:37.667", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + } + ], + "configFiles": [ + { + "id": 5, + "relativePath": "eula.txt", + "description": "End user license agreement", + "template": "eula=true", + "order": 2, + "enableEditor": false, + "editor": [] + }, + { + "id": 6, + "relativePath": "server.properties", + "description": "Main configuration file", + "template": "server-name=${Hostname}\r\nallow-flight=${AllowFlight}\r\nallow-nether=${AllowNether}\r\nbroadcast-console-to-ops=${BroadcastConsoleToOps}\r\nbroadcast-rcon-to-ops=${BroadcastRconToOps}\r\ndifficulty=${Difficulty}\r\nenable-command-block=${EnableCommandBlock}\r\nenable-jmx-monitoring=${EnableJMXMonitoring}\r\nenable-query=true\r\nenable-rcon=${EnableRcon}\r\nenable-status=${EnableStatus}\r\nenforce-whitelist=${EnforceWhiteList}\r\nentity-broadcast-range-percentage=${EntityBroadcastRangePercentage}\r\nspawn-protection=${SpawnProtection}\r\nmax-tick-time=${MaxTickTime}\r\nquery.port=${QueryPort}\r\ngenerator-settings=${GeneratorSettings}\r\nsync-chunk-writes=${SyncChunkWrites}\r\nforce-gamemode=${ForceGamemode}\r\ngamemode=${Gamemode}\r\nplayer-idle-timeout=${PlayerIdleTimeout}\r\ntext-filtering-config=${TextFilteringConfig}\r\nspawn-monsters=${SpawnMonsters}\r\nop-permission-level=${OpPermissionLevel}\r\npvp=${PVP}\r\nlevel-type=${LevelType}\r\nresource-pack-prompt=${ResourcePackPrompt}\r\nhardcore=${Hardcore}\r\nnetwork-compression-threshold=${NetworkCompressionThreshold}\r\nmax-players=${Slots}\r\nmax-world-size=${MaxWorldSize}\r\nresource-pack-sha1=${ResourcePackSHA1}\r\nfunction-permission-level=${FunctionPermissionLevel}\r\nrcon.port=${RConPort}\r\nserver-port=${GamePort}\r\nserver-ip=${IPAddress}\r\nspawn-npcs=${SpawnNPCs}\r\nrequire-resource-pack=${RequireResourcePack}\r\nlevel-name=${LevelName}\r\nview-distance=${ViewDistance}\r\nresource-pack=${ResourcePack}\r\nspawn-animals=${SpawnAnimals}\r\nwhite-list=${WhiteList}\r\nrcon.password=${RconPassword}\r\ngenerate-structures=${GenerateStructures}\r\nonline-mode=${OnlineMode}\r\nlevel-seed=${LevelSeed}\r\nprevent-proxy-connections=${PreventProxyConnections}\r\nuse-native-transport=${UseNativeTransport}\r\nmotd=${MOTD}\r\nrate-limit=${RateLimit}\r\nenforce-secure-profile=${EnforceSecureProfile}\r\nmax-chained-neighbor-updates=${MaxChainedNeighborUpdates}\r\nhide-online-players=${HideOnlinePlayers}\r\nsimulation-distance=${SimulationDistance}", + "order": 1, + "enableEditor": true, + "editor": [ + { + "id": "Header-27045", + "label": "General Settings", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicHeader", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "Hostname", + "label": "Hostname", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "MOTD", + "defaultValue": "A Minecraft Server ", + "label": "Message Of The Day", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "items": [ + { + "text": "Survival", + "value": "0" + }, + { + "text": "Creative", + "value": "1" + }, + { + "text": "Adventure", + "value": "2" + }, + { + "text": "Spectator", + "value": "3" + } + ], + "id": "Gamemode", + "defaultValue": "0", + "label": "Game Mode", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "ForceGamemode", + "label": "Force Game mode", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "Difficulty", + "defaultValue": "easy", + "label": "Difficulty", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "maxValue": 32, + "id": "ViewDistance", + "defaultValue": "10", + "label": "View Distance", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "PVP", + "label": "PVP", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "Hardcore", + "defaultValue": "false", + "label": "Hardcore", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "OnlineMode", + "defaultValue": "true", + "label": "Online Mode", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableStatus", + "defaultValue": "true", + "label": "Enable Status", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableRcon", + "defaultValue": "false", + "label": "Enable Rcon", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "RconPassword", + "label": "Rcon Password", + "description": "Remote console password.", + "required": false, + "valueType": "string", + "requiredMessage": "", + "controlType": "DynamicTextBox", + "sm": 4, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "id": "Header-40113", + "label": "World Settings", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicHeader", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "LevelName", + "defaultValue": "world", + "label": "Level Name", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "LevelSeed", + "label": "Level Seed", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "LevelType", + "defaultValue": "minecraft:normal", + "label": "Level Type", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "GenerateStructures", + "defaultValue": "true", + "label": "Generate Structures", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "GeneratorSettings", + "label": "Generator Settings", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "MaxWorldSize", + "defaultValue": "29999984", + "label": "Max World Size", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SpawnAnimals", + "defaultValue": "true", + "label": "Spawn Animals", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SpawnMonsters", + "defaultValue": "true", + "label": "Spawn Monsters", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SpawnNPCs", + "defaultValue": "true", + "label": "Spawn NPCs", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "AllowNether", + "defaultValue": "true", + "label": "Allow Nether", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "AllowFlight", + "defaultValue": "false", + "label": "Allow Flight", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "items": [ + { + "text": "Disabled", + "value": "0" + }, + { + "text": "3x3 area around the spawn point", + "value": "1" + }, + { + "text": "2", + "value": "5x5 area around the spawn point" + }, + { + "text": "3", + "value": "7x7 area around the spawn point" + }, + { + "text": "33x33 area around the spawn point", + "value": "16" + } + ], + "id": "SpawnProtection", + "defaultValue": "33", + "label": "Spawn Protection", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "id": "Header-86727", + "label": "Whitelist", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicHeader", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "WhiteList", + "defaultValue": "false", + "label": "White List", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnforceWhiteList", + "defaultValue": "false", + "label": "Enforce WhiteList", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "id": "Header-65045", + "label": "Resource Pack", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicHeader", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "RequireResourcePack", + "defaultValue": "false", + "label": "Require Resource Pack", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "ResourcePack", + "label": "Resource Pack", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "ResourcePackPrompt", + "label": "Resource Pack Prompt", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "id": "Header-79389", + "label": "Miscellaneous Settings", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicHeader", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "BroadcastConsoleToOps", + "defaultValue": "true", + "label": "Broadcast Console To Ops", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "BroadcastRconToOps", + "defaultValue": "true", + "label": "Broadcast Rcon To Ops", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableCommandBlock", + "defaultValue": "false", + "label": "Enable Command Block", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableJMXMonitoring", + "defaultValue": "false", + "label": "Enable JMX Monitoring", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SyncChunkWrites", + "label": "Sync Chunk Writes", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "maxValue": 100, + "id": "EntityBroadcastRangePercentage", + "defaultValue": "100", + "label": "Entity Broadcast Range Percentage", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "items": [ + { + "text": "All", + "value": "0" + }, + { + "text": "Moderator", + "value": "2" + }, + { + "text": "Admin", + "value": "3" + }, + { + "text": "Owner", + "value": "4" + } + ], + "id": "FunctionPermissionLevel", + "label": "Function Permission Level", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "MaxTickTime", + "defaultValue": "60000", + "label": "Max Tick Time", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnforceSecureProfile", + "defaultValue": "true", + "label": "Enforce Secure Profile", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "HideOnlinePlayers", + "defaultValue": "false", + "label": "Hide Online Players", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "PlayerIdleTimeout", + "defaultValue": "0", + "label": "Player Idle Timeout", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "TextFilteringConfig", + "label": "Text Filtering Config", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "maxValue": 4, + "id": "OpPermissionLevel", + "defaultValue": "4", + "label": "Op Permission Level", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": -1, + "maxValue": 512, + "id": "NetworkCompressionThreshold", + "defaultValue": "256", + "label": "Network Compression Threshold", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "PreventProxyConnections", + "defaultValue": "false", + "label": "Prevent Proxy Connections", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "UseNativeTransport", + "defaultValue": "true", + "label": "Use Native Transport", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "RateLimit", + "defaultValue": "0", + "label": "Rate Limit", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "MaxChainedNeighborUpdates", + "defaultValue": "1000000", + "label": "Max Chained Neighbor Updates", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "maxValue": 16, + "id": "SimulationDistance", + "defaultValue": "10", + "label": "Simulation Distance", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + } + ] + } + ], + "fileSystemPermissions": [ + { + "roleId": 2, + "rootPermission": { + "permissionMode": "Basic", + "defaultPermissions": "Write, Read, Delete", + "skipListingSecurityCheck": false, + "additionalPermissions": [] + } + }, + { + "roleId": 3, + "rootPermission": { + "permissionMode": "Basic", + "defaultPermissions": "Write, Read, Delete", + "skipListingSecurityCheck": false, + "additionalPermissions": [ + { + "pathOrFilters": "Modpack-*.data", + "permissionType": "File", + "permissionMode": "None", + "permissions": "None" + }, + { + "pathOrFilters": "java", + "permissionType": "FilePath", + "permissionMode": "None", + "permissions": "None" + } + ] + } + } + ], + "gamesScripts": [ + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "IronPython3", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [ + "CustomServiceAction", + "ScheduledTask", + "AfterServiceCreated", + "AfterServiceReinstalled" + ], + "name": "Download/Update Java", + "description": "Downloads and installs Java binaries from Adoptium", + "code": "# UPDATE-JAVA\r\n# This script is also run on After Created and After Reinstall. If you need to make any changes, make them in this script.\r\nimport clr\r\nclr.AddReference(\u0027System.IO\u0027)\r\nclr.AddReference(\u0027System.IO.Compression.ZipFile\u0027)\r\nclr.AddReference(\u0027System.IO.Compression\u0027)\r\nclr.AddReference(\u0022System\u0022)\r\nclr.AddReference(\u0022TCAdmin.SDK\u0022)\r\nclr.AddReference(\u0022System.Net.Http\u0022) # \u003C-- required for HttpClient types\r\n\r\n# For .NET 7\u002B tar support\r\ntry:\r\n clr.AddReference(\u0027System.Formats.Tar\u0027)\r\n TAR_SUPPORT = True\r\nexcept:\r\n TAR_SUPPORT = False\r\n\r\nfrom datetime import datetime\r\nfrom System.Net.Http import HttpClient, HttpClientHandler, HttpCompletionOption\r\nfrom System.IO import Path, Directory, File, FileStream, FileMode, FileAccess\r\nfrom System.IO.Compression import GZipStream, CompressionMode, ZipArchive, ZipArchiveMode\r\n# keep both: Python zipfile for listing members, and .NET ZipFile for ExtractToDirectory\r\nfrom zipfile import ZipFile as ZF\r\nfrom System.IO.Compression import ZipFile\r\nfrom System import Array, Byte\r\n\r\nif TAR_SUPPORT:\r\n from System.Formats.Tar import TarReader, TarEntryType\r\n\r\nfrom os import environ\r\nfrom System import Environment, PlatformID, TimeSpan\r\nfrom System.Runtime.InteropServices import RuntimeInformation\r\n\r\ndef WriteLog(s, progress=None, rewriteLastLog=False):\r\n if \u0027ThisTaskStepHandler\u0027 in globals() and ThisTaskStepHandler is not None:\r\n ThisTaskStepHandler.UpdateProgress(progress=progress, log=s)\r\n elif \u0027ThisTaskStep\u0027 in globals() and ThisTaskStep is not None:\r\n if progress is not None:\r\n ThisTaskStep.Progress = progress\r\n \r\n if rewriteLastLog:\r\n ThisTaskStep.DisplayLog = s\r\n ThisTaskStep.DisplayDebugLog = s\r\n else:\r\n ThisTaskStep.AddDebugLog(s, skipDuplicates=True)\r\n TCATaskManager.Update(ThisTask, True)\r\n else:\r\n if s is not None:\r\n print(s)\r\n #elif progress is not None:\r\n # print(str(progress) \u002B \u0027%\u0027)\r\n\r\ndef check_cancellation():\r\n \u0022\u0022\u0022Check if cancellation has been requested and raise if so.\u0022\u0022\u0022\r\n if \u0027ThisCancellationToken\u0027 in globals() and ThisCancellationToken is not None:\r\n if ThisCancellationToken.IsCancellationRequested:\r\n raise Exception(\u0022Operation cancelled\u0022)\r\n\r\ndef format_duration(seconds):\r\n \u0022\u0022\u0022Format duration in seconds to a human-readable string.\u0022\u0022\u0022\r\n if seconds \u003C 60:\r\n return \u0022{:.1f}s\u0022.format(seconds)\r\n elif seconds \u003C 3600:\r\n mins = int(seconds // 60)\r\n secs = int(seconds % 60)\r\n return \u0022{}m {}s\u0022.format(mins, secs)\r\n else:\r\n hours = int(seconds // 3600)\r\n mins = int((seconds % 3600) // 60)\r\n secs = int(seconds % 60)\r\n return \u0022{}h {}m {}s\u0022.format(hours, mins, secs)\r\n\r\nstart_time = datetime.now()\r\nWriteLog(\u0022\uD83D\uDE80 Starting at: \u0022 \u002B start_time.strftime(\u0022%H:%M:%S\u0022))\r\n\r\n# Set service to processing\r\nThisGameService.Variables[\u0027Processing\u0027] = \u0022True\u0022\r\n#ThisService.Configure()\r\n\r\njava_folder = Path.Combine(ThisService.RootDirectory, \u0027java\u0027)\r\n\r\nos = \u0027windows\u0027 if Environment.OSVersion.Platform == PlatformID.Win32NT else \u0027linux\u0027\r\narch = \u0027aarch64\u0027 if str(RuntimeInformation.ProcessArchitecture) == \u0027Arm64\u0027 else \u0027x64\u0027\r\n#WriteLog(\u0022Operating system is {0} Architecture is {1}\u0022.format(os, arch))\r\n\r\n# Should you want to use another API to grab the Java binaries, replace the urls in the following section\r\njava8 = {\r\n \u0027version\u0027: \u00278\u0027,\r\n \u0027url\u0027: \u0027https://api.adoptium.net/v3/binary/latest/8/ga/{0}/{1}/jdk/hotspot/normal/eclipse?project=jdk\u0027.format(os, arch)\r\n}\r\njava11 = {\r\n \u0027version\u0027: \u002711\u0027,\r\n \u0027url\u0027: \u0027https://api.adoptium.net/v3/binary/latest/11/ga/{0}/{1}/jdk/hotspot/normal/eclipse?project=jdk\u0027.format(os, arch)\r\n}\r\njava16 = {\r\n \u0027version\u0027: \u002716\u0027,\r\n \u0027url\u0027: \u0027https://api.adoptium.net/v3/binary/latest/16/ga/{0}/{1}/jdk/hotspot/normal/eclipse?project=jdk\u0027.format(os, arch)\r\n}\r\njava17 = {\r\n \u0027version\u0027: \u002717\u0027,\r\n \u0027url\u0027: \u0027https://api.adoptium.net/v3/binary/latest/17/ga/{0}/{1}/jdk/hotspot/normal/eclipse?project=jdk\u0027.format(os, arch)\r\n}\r\njava21 = {\r\n \u0027version\u0027: \u002721\u0027,\r\n \u0027url\u0027: \u0027https://api.adoptium.net/v3/binary/latest/21/ga/{0}/{1}/jdk/hotspot/normal/eclipse?project=jdk\u0027.format(os, arch)\r\n}\r\njava25 = {\r\n \u0027version\u0027: \u002725\u0027,\r\n \u0027url\u0027: \u0027https://api.adoptium.net/v3/binary/latest/25/ga/{0}/{1}/jdk/hotspot/normal/eclipse?project=jdk\u0027.format(os, arch)\r\n}\r\nurls = [java8, java11, java16, java17, java21, java25]\r\n\r\n# Create the java folder if it doesn\u0027t exist\r\nif not Directory.Exists(java_folder):\r\n Directory.CreateDirectory(java_folder)\r\n\r\n# Create HttpClientHandler with UseProxy = False.\r\nhandler = HttpClientHandler()\r\nhandler.UseProxy = False\r\n# defensive: try to clear Proxy object if allowed\r\ntry:\r\n handler.Proxy = None\r\nexcept Exception:\r\n pass\r\n\r\nclient = HttpClient(handler)\r\n# set a User-Agent \r\nclient.DefaultRequestHeaders.TryAddWithoutValidation(\u0022User-Agent\u0022, \u0022MyApp/1.0 (IronPython)\u0022)\r\n# set a reasonable timeout (adjust as needed)\r\nclient.Timeout = TimeSpan.FromMinutes(10)\r\n\r\n# helper to download a file synchronously with progress reporting\r\ndef download_file_to_path(url, destination_path):\r\n check_cancellation()\r\n \r\n resp = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result\r\n resp.EnsureSuccessStatusCode()\r\n \r\n # Get content length for progress reporting\r\n # IronPython auto-unwraps Nullable\u003CT\u003E to value or None\r\n content_length = resp.Content.Headers.ContentLength\r\n total_bytes = content_length if content_length is not None else 0\r\n \r\n # ensure parent dir exists\r\n parent = Path.GetDirectoryName(destination_path)\r\n if parent and not Directory.Exists(parent):\r\n Directory.CreateDirectory(parent)\r\n \r\n # Stream to file with progress\r\n stream = resp.Content.ReadAsStreamAsync().Result\r\n try:\r\n fs = FileStream(destination_path, FileMode.Create, FileAccess.Write)\r\n try:\r\n buffer = Array.CreateInstance(Byte, 81920) # 80KB buffer\r\n total_read = 0\r\n last_pct = -1\r\n \r\n bytes_read = stream.Read(buffer, 0, buffer.Length)\r\n while bytes_read \u003E 0:\r\n check_cancellation()\r\n \r\n fs.Write(buffer, 0, bytes_read)\r\n total_read \u002B= bytes_read\r\n \r\n # Report progress (whole numbers only, no repeats)\r\n if total_bytes \u003E 0:\r\n pct = int((total_read * 100) / total_bytes)\r\n if pct != last_pct:\r\n WriteLog(None, pct)\r\n last_pct = pct\r\n \r\n bytes_read = stream.Read(buffer, 0, buffer.Length)\r\n finally:\r\n fs.Dispose()\r\n finally:\r\n stream.Dispose()\r\n\r\ndef extract_zip(archive_path, destination_folder):\r\n \u0022\u0022\u0022\r\n Extract a .zip file using .NET ZipArchive with progress reporting and cancellation support.\r\n Returns the name of the root folder in the archive.\r\n \u0022\u0022\u0022\r\n root_folder_name = None\r\n \r\n # Get folder name using Python zipfile (quick peek)\r\n with ZF(archive_path, \u0027r\u0027) as zipObj:\r\n root_folder_name = zipObj.infolist()[0].filename\r\n \r\n # Extract using .NET ZipArchive for progress reporting and cancellation\r\n zipFs = FileStream(archive_path, FileMode.Open, FileAccess.Read)\r\n try:\r\n archive = ZipArchive(zipFs, ZipArchiveMode.Read)\r\n try:\r\n entries = list(archive.Entries)\r\n total_entries = len(entries)\r\n last_pct = -1\r\n \r\n for i, entry in enumerate(entries):\r\n check_cancellation()\r\n \r\n # Build destination path\r\n entry_path = Path.Combine(destination_folder, entry.FullName.replace(\u0027/\u0027, Path.DirectorySeparatorChar.ToString()))\r\n \r\n # Directory entries typically end with /\r\n if entry.FullName.endswith(\u0027/\u0027) or (entry.Length == 0 and not entry.Name):\r\n if not Directory.Exists(entry_path):\r\n Directory.CreateDirectory(entry_path)\r\n else:\r\n # Ensure parent directory exists\r\n parent_dir = Path.GetDirectoryName(entry_path)\r\n if parent_dir and not Directory.Exists(parent_dir):\r\n Directory.CreateDirectory(parent_dir)\r\n # Extract file by copying stream\r\n entryStream = entry.Open()\r\n try:\r\n outFs = FileStream(entry_path, FileMode.Create, FileAccess.Write)\r\n try:\r\n entryStream.CopyTo(outFs)\r\n finally:\r\n outFs.Dispose()\r\n finally:\r\n entryStream.Dispose()\r\n \r\n # Report progress (whole numbers only, no repeats)\r\n pct = int(((i \u002B 1) * 100) / total_entries)\r\n if pct != last_pct:\r\n WriteLog(None, pct)\r\n last_pct = pct\r\n finally:\r\n archive.Dispose()\r\n finally:\r\n zipFs.Dispose()\r\n \r\n return root_folder_name\r\n\r\ndef extract_tar_gz_native(archive_path, destination_folder):\r\n \u0022\u0022\u0022\r\n Extract a .tar.gz file using native .NET classes (System.IO.Compression.GZipStream \r\n and System.Formats.Tar.TarReader).\r\n Returns the name of the root folder in the archive.\r\n \u0022\u0022\u0022\r\n root_folder_name = None\r\n \r\n # First pass: count entries for progress reporting\r\n entry_count = 0\r\n gzipFileStream = FileStream(archive_path, FileMode.Open, FileAccess.Read)\r\n try:\r\n gzipStream = GZipStream(gzipFileStream, CompressionMode.Decompress)\r\n try:\r\n tarReader = TarReader(gzipStream)\r\n try:\r\n entry = tarReader.GetNextEntry()\r\n while entry is not None:\r\n check_cancellation()\r\n entry_count \u002B= 1\r\n entry = tarReader.GetNextEntry()\r\n finally:\r\n tarReader.Dispose()\r\n finally:\r\n gzipStream.Dispose()\r\n finally:\r\n gzipFileStream.Dispose()\r\n \r\n # Second pass: extract with progress reporting\r\n gzipFileStream = FileStream(archive_path, FileMode.Open, FileAccess.Read)\r\n try:\r\n gzipStream = GZipStream(gzipFileStream, CompressionMode.Decompress)\r\n try:\r\n tarReader = TarReader(gzipStream)\r\n try:\r\n current_entry = 0\r\n last_pct = -1\r\n \r\n entry = tarReader.GetNextEntry()\r\n while entry is not None:\r\n check_cancellation()\r\n \r\n # Capture the root folder name from the first entry\r\n if root_folder_name is None and entry.Name:\r\n name_parts = entry.Name.replace(\u0027\\\\\u0027, \u0027/\u0027).split(\u0027/\u0027)\r\n if name_parts and name_parts[0]:\r\n root_folder_name = name_parts[0]\r\n \r\n # Build the destination path\r\n entry_path = Path.Combine(destination_folder, entry.Name.replace(\u0027/\u0027, Path.DirectorySeparatorChar.ToString()))\r\n \r\n # Handle different entry types\r\n if entry.EntryType == TarEntryType.Directory:\r\n if not Directory.Exists(entry_path):\r\n Directory.CreateDirectory(entry_path)\r\n elif entry.EntryType == TarEntryType.RegularFile:\r\n parent_dir = Path.GetDirectoryName(entry_path)\r\n if parent_dir and not Directory.Exists(parent_dir):\r\n Directory.CreateDirectory(parent_dir)\r\n entry.ExtractToFile(entry_path, True)\r\n elif entry.EntryType == TarEntryType.SymbolicLink:\r\n pass\r\n \r\n # Report progress (whole numbers only, no repeats)\r\n current_entry \u002B= 1\r\n if entry_count \u003E 0:\r\n pct = int((current_entry * 100) / entry_count)\r\n if pct != last_pct:\r\n WriteLog(None, pct)\r\n last_pct = pct\r\n \r\n entry = tarReader.GetNextEntry()\r\n finally:\r\n tarReader.Dispose()\r\n finally:\r\n gzipStream.Dispose()\r\n finally:\r\n gzipFileStream.Dispose()\r\n \r\n return root_folder_name\r\n\r\ntry:\r\n for adoptium in urls:\r\n check_cancellation()\r\n \r\n # Only download binaries for non-installed versions of Java (environment variables must be set as JAVA8, JAVA11 and JAVA16)\r\n if \u0027JAVA{version}\u0027.format(version=adoptium[\u0027version\u0027]) in environ:\r\n WriteLog(\u0027\u23ED\uFE0F Skipping Java \u0027 \u002B adoptium[\u0027version\u0027] \u002B \u0027 (already installed)\u0027)\r\n continue\r\n\r\n # Delete folder if it exists (ie /java/jre16) when we need to update\r\n target_dir = Path.Combine(java_folder, \u0027java\u0027 \u002B adoptium[\u0027version\u0027])\r\n if Directory.Exists(target_dir):\r\n Directory.Delete(target_dir, True)\r\n WriteLog(\u0027\uD83D\uDDD1\uFE0F Deleted \u0027 \u002B Path.Combine(\u0027java\u0027, \u0027java\u0027 \u002B adoptium[\u0027version\u0027]))\r\n\r\n archive_type = \u0027.zip\u0027 if Environment.OSVersion.Platform == PlatformID.Win32NT else \u0027.tar.gz\u0027\r\n archive_name = \u0027adoptium\u0027 \u002B adoptium[\u0027version\u0027] \u002B archive_type\r\n archive_path = Path.Combine(java_folder, archive_name)\r\n\r\n WriteLog(\u0027\uD83D\uDCE5 Downloading \u0027 \u002B archive_name)\r\n\r\n # Download archive file\r\n try:\r\n download_file_to_path(adoptium[\u0027url\u0027], archive_path)\r\n except Exception as e:\r\n if \u0027cancelled\u0027 in str(e).lower():\r\n raise\r\n WriteLog(\u0027\u274C Download failed for {}: {}\u0027.format(adoptium[\u0027url\u0027], e))\r\n # skip to next version (don\u0027t try to extract)\r\n continue\r\n\r\n check_cancellation()\r\n\r\n # Extract archive (Windows / zip)\r\n if archive_name.endswith(\u0022.zip\u0022):\r\n if Directory.Exists(Path.Combine(java_folder, \u0027jdk\u0027)):\r\n Directory.Delete(Path.Combine(java_folder, \u0027jdk\u0027), True)\r\n \r\n WriteLog(\u0027\uD83D\uDCE6 Extracting \u0027 \u002B archive_name)\r\n foldername = extract_zip(archive_path, java_folder)\r\n WriteLog(\u0027\u2705 Extraction complete\u0027)\r\n \r\n WriteLog(\u0027\uD83D\uDCC1 Renaming to \u0027 \u002B Path.Combine(\u0027java\u0027, \u0027java\u0027 \u002B adoptium[\u0027version\u0027]))\r\n # foldername may have trailing slash, clean it\r\n foldername = foldername.rstrip(\u0027/\u0027)\r\n Directory.Move(Path.Combine(java_folder, foldername), Path.Combine(java_folder, \u0027java\u0027 \u002B adoptium[\u0027version\u0027]))\r\n\r\n # Extract archive (Linux / tar.gz) using native .NET\r\n elif archive_name.endswith(\u0022tar.gz\u0022):\r\n if not TAR_SUPPORT:\r\n raise Exception(\u0022System.Formats.Tar is not available. Please ensure you are running on .NET 7 or later.\u0022)\r\n \r\n WriteLog(\u0027\uD83D\uDCE6 Extracting \u0027 \u002B archive_name)\r\n \r\n # Extract and get the root folder name\r\n foldername = extract_tar_gz_native(archive_path, java_folder)\r\n \r\n if not foldername:\r\n raise Exception(\u0022Could not determine root folder name from tar archive\u0022)\r\n \r\n WriteLog(\u0027\u2705 Extraction complete\u0027)\r\n \r\n # Check if the extracted folder exists and rename it\r\n extracted_path = Path.Combine(java_folder, foldername)\r\n final_path = Path.Combine(java_folder, \u0027java\u0027 \u002B adoptium[\u0027version\u0027])\r\n \r\n if Directory.Exists(extracted_path):\r\n WriteLog(\u0027\uD83D\uDCC1 Renaming to \u0027 \u002B Path.Combine(\u0027java\u0027, \u0027java\u0027 \u002B adoptium[\u0027version\u0027]))\r\n Directory.Move(extracted_path, final_path)\r\n else:\r\n WriteLog(\u0027\u26A0\uFE0F Warning: Expected folder {} not found\u0027.format(foldername))\r\n\r\n # Delete archive file\r\n if File.Exists(archive_path):\r\n try:\r\n File.Delete(archive_path)\r\n WriteLog(\u0027\uD83E\uDDF9 Cleaned up \u0027 \u002B archive_name)\r\n except Exception as e:\r\n WriteLog(\u0027\u26A0\uFE0F Could not delete %s: %s\u0027 % (archive_name, e))\r\n\r\n# If anything goes wrong, set Processing to False\r\nexcept Exception as e:\r\n WriteLog(\u0027\u274C Error: {}\u0027.format(e))\r\n ThisGameService.Variables[\u0027Processing\u0027] = \u0022False\u0022\r\n #ThisService.Configure()\r\n\r\n# Set processing to false\r\nThisGameService.Variables[\u0027Processing\u0027] = \u0022False\u0022\r\n#ThisService.Configure()\r\n\r\nend_time = datetime.now()\r\nelapsed = (end_time - start_time).total_seconds()\r\nWriteLog(\u0022\u2705 Java configuration finished at {} (took {}) \uD83D\uDC4D\u0022.format(end_time.strftime(\u0022%H:%M:%S\u0022), format_duration(elapsed)))", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cg\u003E\u003Cpath d=\u0022M0,0h24v24H0V0z\u0022 fill=\u0022none\u0022/\u003E\u003C/g\u003E\u003Cg\u003E\u003Cpath d=\u0022M18.5,3H6C4.9,3,4,3.9,4,5v5.71c0,3.83,2.95,7.18,6.78,7.29c3.96,0.12,7.22-3.06,7.22-7v-1h0.5c1.93,0,3.5-1.57,3.5-3.5 S20.43,3,18.5,3z M16,5v3H6V5H16z M18.5,8H18V5h0.5C19.33,5,20,5.67,20,6.5S19.33,8,18.5,8z M4,19h16v2H4V19z\u0022/\u003E\u003C/g\u003E", + "scriptInputVariables": [], + "scriptRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ], + "editor": [], + "identifier": "b9f8ba99-c8cd-4181-816f-f95de8f2e064" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [ + "BeforeServiceStarted" + ], + "name": "EULA Check", + "description": "Check if EULA is set to true before start", + "code": "using System;\r\nusing System.IO;\r\nusing System.Text.RegularExpressions;\r\n\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\nstring eulaFile = Path.Combine(Globals.ThisService.RootDirectory, \u0022eula.txt\u0022);\r\n\r\nstring pattern = @\u0022eula=(?\u003Ceula\u003E(true|false)*)?\u0022;\r\nstring fileContents = File.ReadAllText(eulaFile);\r\nMatch match = Regex.Match(fileContents, pattern, RegexOptions.IgnoreCase);\r\n\r\nif (match.Groups[\u0022eula\u0022].Value != \u0022true\u0022)\r\n{\r\n throw new Exception(\u0022You need to accept the EULA. Open eula.txt and set eula=true in the file.\u0022);\r\n}\r\nelse\r\n{\r\n Console.WriteLine(\u0022EULA is OK \uD83D\uDC4D\u0022);\r\n}", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": false, + "order": 1, + "icon": "\u003Cg\u003E\u003Crect fill=\u0022none\u0022 height=\u002224\u0022 width=\u002224\u0022/\u003E\u003C/g\u003E\u003Cg\u003E\u003Cpath d=\u0022M23,12l-2.44-2.79l0.34-3.69l-3.61-0.82L15.4,1.5L12,2.96L8.6,1.5L6.71,4.69L3.1,5.5L3.44,9.2L1,12l2.44,2.79l-0.34,3.7 l3.61,0.82L8.6,22.5l3.4-1.47l3.4,1.46l1.89-3.19l3.61-0.82l-0.34-3.69L23,12z M10.09,16.72l-3.8-3.81l1.48-1.48l2.32,2.33 l5.85-5.87l1.48,1.48L10.09,16.72z\u0022/\u003E\u003C/g\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "2d59a23d-52aa-4563-a00e-038c5c9e4e0a" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "IronPython3", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [ + "AfterServiceReinstalled", + "AfterServiceCreated" + ], + "name": "Create executable", + "description": "Create minecraft_server.(sh|bat)", + "code": "import clr\r\nfrom System import Environment, PlatformID\r\nfrom System.IO import Path\r\nfrom platform import system\r\nimport os\r\n\r\nentrypointMappings = {\r\n \u0022Java 8\u0022: \u0022java8\u0022,\r\n \u0022Java 11\u0022: \u0022java11\u0022,\r\n \u0022Java 16\u0022: \u0022java16\u0022,\r\n}\r\n\r\njava_folder = os.path.join(ThisService.RootDirectory, \u0027java\u0027)\r\nexecutable = \u0027.exe\u0027 if system() == \u0027Windows\u0027 else \u0027\u0027\r\ncontent = \u0027 %*\u0027 if system() == \u0027Windows\u0027 else \u0027 $*\u0027\r\nbash_shebang = \u0027#!/bin/bash\\n\u0027 if system() == \u0027Linux\u0027 else \u0027\u0027 #On Linux, we need to add a shebang at the top of the .sh file\r\n\r\nif entrypointMappings[\u0027Java 16\u0027].upper() in os.environ: #If environment variables exists, use them\r\n java_path = \u0027\\\u0022%\u0027\u002BentrypointMappings[\u0027Java 16\u0027].upper()\u002B\u0027%\\\u0022\u0027 if system() == \u0027Windows\u0027 else \u0027\\\u0022${\u0027\u002BentrypointMappings[\u0027Java 16\u0027].upper()\u002B\u0027}\\\u0022\u0027\r\nelif entrypointMappings[\u0027Java 11\u0027].upper() in os.environ:\r\n java_path = \u0027\\\u0022%\u0027\u002BentrypointMappings[\u0027Java 11\u0027].upper()\u002B\u0027%\\\u0022\u0027 if system() == \u0027Windows\u0027 else \u0027\\\u0022${\u0027\u002BentrypointMappings[\u0027Java 11\u0027].upper()\u002B\u0027}\\\u0022\u0027\r\nelif entrypointMappings[\u0027Java 8\u0027].upper() in os.environ:\r\n java_path = \u0027\\\u0022%\u0027\u002BentrypointMappings[\u0027Java 8\u0027].upper()\u002B\u0027%\\\u0022\u0027 if system() == \u0027Windows\u0027 else \u0027\\\u0022${\u0027\u002BentrypointMappings[\u0027Java 8\u0027].upper()\u002B\u0027}\\\u0022\u0027\r\nelif os.path.isfile(os.path.join(java_folder, entrypointMappings[\u0027Java 16\u0027], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)): #If no environment variables exists, check if Java binaries exists on the service\r\n java_path = os.path.join(java_folder, entrypointMappings[\u0027Java 16\u0027], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)\r\nelif os.path.isfile(os.path.join(java_folder, entrypointMappings[\u0027Java 11\u0027], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)):\r\n java_path = os.path.join(java_folder, entrypointMappings[\u0027Java 11\u0027], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)\r\nelif os.path.isfile(os.path.join(java_folder, entrypointMappings[\u0027Java 8\u0027], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)):\r\n java_path = os.path.join(java_folder, entrypointMappings[\u0027Java 8\u0027], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)\r\nelse: #Fallback to using \u0027java\u0027\r\n java_path = \u0027java\u0027\r\n\r\n#Write the java path to the executable file\r\nexe = Path.Combine(ThisService.RootDirectory, ThisService.Executable)\r\nwith open(exe, \u0027w\u0027) as file:\r\n file.write(bash_shebang \u002B java_path \u002B content)\r\n\r\nprint(\u0022Created \u0022 \u002B exe)", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cpath d=\u0022M0 0h24v24H0z\u0022 fill=\u0022none\u0022/\u003E\u003Cpath d=\u0022M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-3.6 13.9l1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2 7 1.4z\u0022/\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "e2ca1a81-f21e-4dad-8edf-571cce800c55" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "IronPython3", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [ + "CustomServiceAction", + "BeforeServiceStarted" + ], + "name": "Detect java version", + "description": "Read Minecraft version from .jar file", + "code": "import os\r\nimport zipfile\r\nimport hashlib\r\nimport json\r\nfrom io import BytesIO\r\nimport re\r\nfrom platform import system \r\n\r\ndefault = \u0022Java 25\u0022\r\n# Set Automatic Java version if the variable doesn\u0027t exist.\r\nif not ThisGameService.Variables.ContainsKey(\u0022JavaVersion\u0022):\r\n ThisGameService.Variables[\u0022JavaVersion\u0022] = \u0027Automatic\u0027\r\n\r\n# Used for mapping Java versions to environment variables\r\nentrypointMappings = {\r\n \u0022Java 8\u0022: \u0022java8\u0022,\r\n \u0022Java 11\u0022: \u0022java11\u0022,\r\n \u0022Java 16\u0022: \u0022java16\u0022,\r\n \u0022Java 17\u0022: \u0022java17\u0022,\r\n \u0022Java 21\u0022: \u0022java21\u0022,\r\n \u0022Java 25\u0022: \u0022java25\u0022\r\n} \r\n \r\n# Function to compute SHA-256 checksum of a file\r\ndef compute_sha256(file_path):\r\n sha256 = hashlib.sha256()\r\n with open(file_path, \u0027rb\u0027) as f:\r\n for chunk in iter(lambda: f.read(4096), b\u0022\u0022):\r\n sha256.update(chunk)\r\n return sha256.hexdigest()\r\n\r\n# Function to read the manifest file and return the version\r\ndef read_manifest(jar_file):\r\n try:\r\n with zipfile.ZipFile(jar_file, \u0027r\u0027) as jar:\r\n if \u0027META-INF/MANIFEST.MF\u0027 in jar.namelist():\r\n manifest = jar.read(\u0027META-INF/MANIFEST.MF\u0027).decode(\u0027utf-8\u0027).splitlines()\r\n for line in manifest:\r\n if line.startswith(\u0027Build-Jdk\u0027):\r\n return parse_build_jdk(line)\r\n elif line.startswith(\u0027Main-Class\u0027):\r\n main_class = line.split(\u0027: \u0027)[1].strip().replace(\u0027.\u0027, \u0027/\u0027)\r\n return check_main_class(jar, main_class)\r\n except Exception as e:\r\n print(\u0022Error reading MANIFEST.MF: {0}\u0022.format(e))\r\n return None\r\n\r\n# Helper to parse Build-Jdk entry in META-INF/MANIFEST.MF\r\ndef parse_build_jdk(line):\r\n build_jdk = line.split(\u0027: \u0027)[1].strip()\r\n major_version = \u0027.\u0027.join(build_jdk.split(\u0027.\u0027)[:2])\r\n return \u0022Java \u0022 \u002B str(major_version)\r\n\r\n# Function to check Main-Class if found in MANIFEST.MF\r\ndef check_main_class(jar, main_class_path):\r\n main_class_file = main_class_path \u002B \u0022.class\u0022\r\n if main_class_file in jar.namelist():\r\n return extract_java_version_from_class(jar.read(main_class_file))\r\n return None\r\n\r\n# Function to extract Java version from a .class file\r\ndef extract_java_version_from_class(class_data):\r\n if len(class_data) \u003E= 8:\r\n major_version = int.from_bytes(class_data[6:8], byteorder=\u0027big\u0027)\r\n return {\r\n 45: \u0027Java 1.1\u0027,\r\n 46: \u0027Java 1.2\u0027,\r\n 47: \u0027Java 1.3\u0027,\r\n 48: \u0027Java 1.4\u0027,\r\n 49: \u0027Java 5\u0027,\r\n 50: \u0027Java 6\u0027,\r\n 51: \u0027Java 7\u0027,\r\n 52: \u0027Java 8\u0027,\r\n 53: \u0027Java 9\u0027,\r\n 54: \u0027Java 10\u0027,\r\n 55: \u0027Java 11\u0027,\r\n 56: \u0027Java 12\u0027,\r\n 57: \u0027Java 13\u0027,\r\n 58: \u0027Java 14\u0027,\r\n 59: \u0027Java 15\u0027,\r\n 60: \u0027Java 16\u0027,\r\n 61: \u0027Java 17\u0027,\r\n 62: \u0027Java 18\u0027,\r\n 63: \u0027Java 19\u0027,\r\n 64: \u0027Java 20\u0027,\r\n 65: \u0027Java 21\u0027,\r\n 66: \u0027Java 22\u0027,\r\n 67: \u0027Java 23\u0027,\r\n 68: \u0027Java 24\u0027,\r\n 69: \u0027Java 25\u0027\r\n }.get(major_version, \u0027Unknown Java version\u0027)\r\n return \u0027Unknown Java version\u0027\r\n\r\n# Function to check version.json in a nested jar\r\ndef check_version_json(jar_file):\r\n try:\r\n with zipfile.ZipFile(jar_file, \u0027r\u0027) as jar:\r\n return check_root_version_json(jar) or check_nested_version_json(jar)\r\n except Exception as e:\r\n print(\u0022Error reading version.json: {0}\u0022.format(e))\r\n return None\r\n\r\n# Helper to check the root version.json\r\ndef check_root_version_json(jar):\r\n if \u0027version.json\u0027 in jar.namelist():\r\n with jar.open(\u0027version.json\u0027) as version_file:\r\n return extract_java_version_from_json(version_file)\r\n\r\n# Helper to check nested jars for version.json\r\ndef check_nested_version_json(jar):\r\n for name in jar.namelist():\r\n if name.startswith(\u0027META-INF/versions/\u0027) and name.endswith(\u0027.jar\u0027):\r\n with jar.open(name) as nested_jar_file:\r\n with zipfile.ZipFile(BytesIO(nested_jar_file.read())) as nested_jar:\r\n if \u0027version.json\u0027 in nested_jar.namelist():\r\n with nested_jar.open(\u0027version.json\u0027) as version_file:\r\n return extract_java_version_from_json(version_file)\r\n\r\n# Function to extract Java version from version.json\r\ndef extract_java_version_from_json(version_file):\r\n version_data = json.loads(version_file.read().decode(\u0027utf-8\u0027))\r\n java_version = version_data.get(\u0027java_version\u0027)\r\n if java_version:\r\n print(\u0022Found version in version.json: Java {0}\u0022.format(java_version)) \r\n return \u0022Java \u0022 \u002B str(java_version)\r\n return None\r\n\r\n# Function to process a single jar file\r\ndef process_jar(jar_file):\r\n jar_sha256 = compute_sha256(jar_file)\r\n sha256_folder = os.path.join(ThisService.RootDirectory, \u0027Temp\u0027, \u0027MinecraftSHA256\u0027)\r\n sha256_file = os.path.join(sha256_folder, jar_sha256)\r\n\r\n # Check if SHA-256 checksum exists and read the Java version from it\r\n \r\n if not os.path.isdir(sha256_folder):\r\n os.mkdir(sha256_folder)\r\n if os.path.exists(sha256_file):\r\n with open(sha256_file, \u0027r\u0027) as f:\r\n saved_java_version = f.read().strip()\r\n print(\u0022SHA-256 checksum matched ({0}), using cached Java version: {1}\u0022.format(sha256_file, saved_java_version))\r\n return saved_java_version\r\n\r\n # Process jar and determine highest Java version\r\n print(\u0022Processing: {0}\u0022.format(jar_file))\r\n\r\n java_versions = [\r\n read_manifest(jar_file),\r\n check_version_json(jar_file)\r\n ]\r\n\r\n with zipfile.ZipFile(jar_file, \u0027r\u0027) as jar:\r\n class_files = [name for name in jar.namelist() if name.endswith(\u0027.class\u0027)]\r\n java_versions.extend([extract_java_version_from_class(jar.read(class_file)) for class_file in class_files])\r\n\r\n highest_version = determine_highest_java_version(java_versions)\r\n print(\u0022Highest Java version: {0}\u0022.format(highest_version))\r\n # Save the SHA-256 and highest Java version in a file\r\n with open(sha256_file, \u0027w\u0027) as f:\r\n f.write(\u0022{0}\u0022.format(highest_version))\r\n\r\n return highest_version\r\n\r\n# Determine the highest Java version with version grouping logic\r\ndef determine_highest_java_version(java_versions):\r\n adjusted_versions = []\r\n for version in java_versions:\r\n if version and version.startswith(\u0022Java \u0022) and version != \u0022Unknown Java version\u0022:\r\n version_number = version.split(\u0022 \u0022)[1]\r\n if float(version_number) \u003C= 8:\r\n adjusted_versions.append(\u0022Java 8\u0022)\r\n elif float(version_number) \u003C= 11:\r\n adjusted_versions.append(\u0022Java 11\u0022)\r\n elif float(version_number) \u003C= 16:\r\n adjusted_versions.append(\u0022Java 16\u0022)\r\n elif float(version_number) == 17:\r\n adjusted_versions.append(\u0022Java 17\u0022)\r\n elif float(version_number) \u003C= 21:\r\n adjusted_versions.append(\u0022Java 21\u0022)\r\n else:\r\n adjusted_versions.append(\u0022Java 25\u0022)\r\n\r\n return max(adjusted_versions, key=lambda v: list(map(int, v.split(\u0022 \u0022)[1].split(\u0027.\u0027))), default=\u0027Java 21\u0027)\r\n\r\n# Parse major version from version string\r\ndef parse_major_version(version_number):\r\n try:\r\n return int(version_number.split(\u0022.\u0022)[0]) if len(version_number.split(\u0022.\u0022)) == 1 else int(version_number.split(\u0022.\u0022)[1])\r\n except ValueError:\r\n return 0 # Fallback to 0 for invalid versions\r\n\r\n# Function to extract the version number from a filename\r\ndef extract_version_from_filename(filename):\r\n version_match = re.search(r\u0022(\\d\u002B\\.\\d\u002B(\\.\\d\u002B)?)\u0022, filename)\r\n return version_match.group(0) if version_match else None\r\n\r\ndef search_jars(directory=\u0027.\u0027, extension=\u0027\u0027):\r\n extension = extension.lower()\r\n for dirpath, dirnames, files in os.walk(directory):\r\n for name in files:\r\n if extension and name.lower().endswith(extension):\r\n return os.path.join(dirpath, name)\r\n elif not extension:\r\n return os.path.join(dirpath, name) \r\n \r\nstartup = ThisService.Commandline\r\ndef getJarFromStartup():\r\n splitted_commandline = startup.split()\r\n if not \u0027-jar\u0027 in startup:\r\n # Find the path segment starting with \u0022@\u0022 and ending with \u0022.txt\u0022 - takes care of Minecraft Forge and NeoForge\r\n print(\u0027Jar not found in commandline\u0027)\r\n for segment in splitted_commandline:\r\n if segment.startswith(\u0022@\u0022) and segment.endswith(\u0022.txt\u0022):\r\n print(\u0027Modloader arguments detected\u0027)\r\n path = segment[1:].rsplit(\u0022/\u0022, 1)[0] # Remove the @ and extract the directory\r\n print(\u0022Searching {}\u0022.format(os.path.join(ThisService.RootDirectory, os.path.dirname(path))))\r\n return search_jars(os.path.join(ThisService.RootDirectory, os.path.dirname(path)), \u0027.jar\u0027)\r\n else:\r\n return None # If no matching segment found\r\n # Search for \u0022server.jar\u0022 in the same directory\r\n for file in os.listdir(os.path.join(ThisService.RootDirectory, path)):\r\n if file.endswith(\u0022server.jar\u0022):\r\n print(\u0022Found {}\u0022.format(file))\r\n return os.path.join(path, file)\r\n return None\r\n \r\n \r\n for x in splitted_commandline:\r\n # Take care of Fabric. It relies on a separate \u0022server.jar\u0022 file in the root directory\r\n if x.startswith(\u0022fabric-server-launch\u0022) and x.endswith(\u0022.jar\u0022):\r\n return os.path.join(ThisService.RootDirectory, \u0027server.jar\u0027)\r\n # Every other .jar file\r\n if x.endswith(\u0027.jar\u0027):\r\n return x.strip()\r\n\r\ndef main():\r\n if ThisGameService.Variables.Parse[str](\u0022JavaVersion\u0022, \u0022Automatic\u0022) == \u0027Automatic\u0027:\r\n java_folder = os.path.join(ThisService.RootDirectory, \u0027java\u0027)\r\n executable = \u0027.exe\u0027 if system() == \u0027Windows\u0027 else \u0027\u0027\r\n content = \u0027 %*\u0027 if system() == \u0027Windows\u0027 else \u0027 $*\u0027\r\n bash_shebang = \u0027#!/bin/bash\\n\u0027 if system() == \u0027Linux\u0027 else \u0027\u0027 #On Linux, we need to add a shebang at the top of the .sh file\r\n jar_file = getJarFromStartup()\r\n if not jar_file:\r\n raise Exception(\u0027Your commandline does not include a jar file. Please select one.\u0027)\r\n print(\u0027Found jar file {}\u0027.format(jar_file))\r\n java_version = process_jar(os.path.join(ThisService.RootDirectory, jar_file))\r\n print(\u0027test 1\u0027 \u002B str(os.environ))\r\n if entrypointMappings[java_version].upper() in os.environ:\r\n print(\u0027test 2\u0027)\r\n java_path = \u0027\\\u0022%\u0027\u002BentrypointMappings[java_version].upper()\u002B\u0027%\\\u0022\u0027 if system() == \u0027Windows\u0027 else \u0027\\\u0022${\u0027\u002BentrypointMappings[java_version].upper()\u002B\u0027}\\\u0022\u0027\r\n elif os.path.isfile(os.path.join(java_folder, entrypointMappings[java_version], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)):\r\n print(\u0027test 3\u0027)\r\n java_path = os.path.join(java_folder, entrypointMappings[java_version], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)\r\n else:\r\n print(\u0027test 4\u0027)\r\n java_path = \u0027java\u0027\r\n with open(os.path.join(ThisService.RootDirectory, ThisService.Executable), \u0027w\u0027) as file:\r\n file.write(bash_shebang \u002B java_path \u002B content) \r\n else:\r\n print(\u0022Automatic detection is disabled. Configured to use {0}\u0022.format(ThisGameService.Variables[\u0022JavaVersion\u0022]));\r\n\r\n \r\nif __name__ == \u0022\u003Cmodule\u003E\u0022:\r\n main()", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": false, + "order": 2, + "icon": "\u003Cpath d=\u0022M0 0h24v24H0z\u0022 fill=\u0022none\u0022/\u003E\u003Cpath d=\u0022M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm8.94 3c-.46-4.17-3.77-7.48-7.94-7.94V1h-2v2.06C6.83 3.52 3.52 6.83 3.06 11H1v2h2.06c.46 4.17 3.77 7.48 7.94 7.94V23h2v-2.06c4.17-.46 7.48-3.77 7.94-7.94H23v-2h-2.06zM12 19c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z\u0022/\u003E", + "scriptInputVariables": [], + "scriptRoles": [ + { + "roleId": 3 + }, + { + "roleId": 2 + } + ], + "editor": [], + "identifier": "71e62194-d283-423a-a5a0-01bd094fc4d7" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [ + "AfterServiceCreated" + ], + "name": "Create Java task", + "description": "Create Scheduled Task for updating Adoptium", + "code": "//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll\r\n// Check if we need to create the task\r\n// The task will be created if any one of the Java versions does not have an environment variable\r\nusing TCAdmin.Scripting.Engines.Addons;\r\nusing System;\r\nusing System.Linq;\r\nusing System.Collections.Generic;\r\nusing Microsoft.EntityFrameworkCore;\r\nusing TCAdmin.SDK.Database.Entities;\r\nusing TCAdmin.GameHosting.SDK;\r\nusing TCAdmin.SDK.Models;\r\nusing TCAdmin.Web.Shared.Models;\r\nusing Quartz;\r\nusing Quartz.Impl.Triggers;\r\n\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\nvar javaVersions = new List\u003Cint\u003E { 8, 11, 16, 17, 21 };\r\nbool createTask = false;\r\nforeach (int javaVersion in javaVersions)\r\n{\r\n if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(String.Format(\u0022JAVA{0}\u0022, javaVersion))))\r\n {\r\n Globals.ScriptConsole.WriteLine(\u0022Creating scheduled task to update Java versions\u0022);\r\n createTask = true;\r\n break;\r\n }\r\n}\r\n\r\nif (createTask)\r\n{\r\n //var javaUpdateScript = Globals.RecurringTaskManager.TCAdminDbContext.Scripts.AsNoTracking().SingleOrDefault(x=\u003Ex.Code.StartsWith(\u0022# UPDATE-JAVA\u0022));\r\n var javaUpdateScript = Globals.ThisGame.Scripts.SingleOrDefault(x=\u003Ex.Code.StartsWith(\u0022# UPDATE-JAVA\u0022));\r\n if(javaUpdateScript == null)\r\n {\r\n throw new Exception(\u0022Java update script not found!\u0022);\r\n }\r\n var rtask = new RecurringTask()\r\n {\r\n OwnerId = Globals.ThisService.OwnerId,\r\n ServerId = Globals.ThisService.ServerId,\r\n ServiceId = Globals.ThisService.ServiceId,\r\n DisplayName = \u0022Update Java\u0022,\r\n Enabled = true,\r\n };\r\n\r\n var rstep = new RecurringTaskStep()\r\n {\r\n DisplayName = rtask.DisplayName,\r\n ModuleId = GameHostingModule.ModuleId,\r\n ProcessId = \u0022ScheduledGameScript\u0022,\r\n ServerId = rtask.ServerId,\r\n Variables = new TCAdminVars()\r\n {\r\n {\u0022ScriptId\u0022, javaUpdateScript.ScriptId},\r\n {\u0022ServiceId\u0022, Globals.ThisService.ServiceId}\r\n }\r\n };\r\n\r\n rtask.Steps = new List\u003CRecurringTaskStep\u003E() { rstep };\r\n\r\n var timeOfDay = new TimeOfDay(0, 0);\r\n var trigger = Globals.RecurringTaskManager.CreateDailyIntervalTrigger(1, IntervalUnit.Hour, timeOfDay, timeOfDay, DayOfWeek.Monday);\r\n\r\n rtask.Triggers = new() { (AbstractTrigger)trigger };\r\n\r\n await Globals.RecurringTaskManager.CreateAsync(rtask);\r\n\r\n await Globals.RecurringTaskManager.ReloadRecurringTask(rtask);\r\n\r\n Globals.ScriptConsole.WriteLine(\u0022Sucess!\u0022);\r\n}else\r\n{\r\n Globals.ScriptConsole.WriteLine(\u0022Not created!\u0022);\r\n}", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cg\u003E\u003Crect fill=\u0022none\u0022 height=\u002224\u0022 width=\u002224\u0022/\u003E\u003C/g\u003E\u003Cg\u003E\u003Cpath d=\u0022M19,4h-1V2h-2v2H8V2H6v2H5C3.89,4,3.01,4.9,3.01,6L3,20c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4z M19,20 H5V10h14V20z M9,14H7v-2h2V14z M13,14h-2v-2h2V14z M17,14h-2v-2h2V14z M9,18H7v-2h2V18z M13,18h-2v-2h2V18z M17,18h-2v-2h2V18z\u0022/\u003E\u003C/g\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "5fcba68a-8575-4815-9d72-1dd3ee3e6e12" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "IronPython3", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [ + "BeforeServiceStarted" + ], + "name": "Log4jPatcher securing", + "code": "import clr\r\nimport json, os, re\r\nclr.AddReference(\u0022System\u0022)\r\nclr.AddReference(\u0022System.Net.Http\u0022) # required for HttpClient types\r\n\r\nfrom System import Version, TimeSpan\r\nfrom System.Net import ServicePointManager, SecurityProtocolType\r\nfrom System.Net.Http import HttpClient, HttpClientHandler, HttpCompletionOption\r\n\r\n# ensure TLS1.2 (important for GitHub on older .NET versions)\r\nServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12\r\n\r\n# Create an HttpClientHandler with UseProxy = False\r\nhandler = HttpClientHandler()\r\nhandler.UseProxy = False\r\n# explicitly clear proxy object (defensive)\r\ntry:\r\n handler.Proxy = None\r\nexcept Exception:\r\n # some platform/versions may not allow setting Proxy, ignore if so\r\n pass\r\n\r\n# Create HttpClient using the handler\r\nclient = HttpClient(handler)\r\n# Add a User-Agent header\r\nclient.DefaultRequestHeaders.TryAddWithoutValidation(\u0022User-Agent\u0022, \u0022MyApp/1.0 (IronPython)\u0022)\r\n\r\n# Optional: set a reasonable timeout (adjust if you want)\r\nclient.Timeout = TimeSpan.FromSeconds(100)\r\n\r\n# helper to perform a GET and return the response content as text\r\ndef http_get_text(url):\r\n resp = client.GetAsync(url).Result\r\n resp.EnsureSuccessStatusCode()\r\n return resp.Content.ReadAsStringAsync().Result\r\n\r\n# helper to download bytes (for files)\r\ndef http_get_bytes(url):\r\n # use ResponseHeadersRead to start streaming once headers are available\r\n resp = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result\r\n resp.EnsureSuccessStatusCode()\r\n return resp.Content.ReadAsByteArrayAsync().Result\r\n\r\nurl = \u0027https://api.github.com/repos/CreeperHost/Log4jPatcher/releases/latest\u0027\r\njson_text = http_get_text(url)\r\njson_info = json.loads(json_text)\r\n\r\n# extract a numeric version if the \u0027name\u0027 contains a \u0022v\u0022 or other text:\r\nname = json_info.get(\u0027name\u0027, \u0027\u0027)\r\nm = re.search(r\u0027(\\d\u002B(?:\\.\\d\u002B)\u002B)\u0027, name)\r\nver_str = m.group(1) if m else name\r\n\r\ntry:\r\n log4jpatcher_version_latest = Version(ver_str)\r\nexcept Exception:\r\n # fallback if Version(...) can\u0027t parse the string\r\n log4jpatcher_version_latest = Version(\u00270.0.0\u0027)\r\n\r\nlog4jpatcher_version_installed = Version(\u00270.0.0\u0027) # adjust as needed\r\n\r\nif log4jpatcher_version_installed.CompareTo(log4jpatcher_version_latest) \u003C 0:\r\n print(\u0027Service {} - Updating Log4jPatcher before startup\u0027.format(ThisService.ServiceId))\r\n assets = [] # ensure variable exists even if an exception occurs\r\n try:\r\n assets = json_info.get(\u0027assets\u0027) or []\r\n if not assets:\r\n raise Exception(\u0027No assets found in release JSON\u0027)\r\n download_url = assets[0][\u0027browser_download_url\u0027]\r\n\r\n destination_file = os.path.join(ThisService.RootDirectory, \u0027Log4jPatcher\u0027, \u0027Log4jPatcher.jar\u0027)\r\n destination_path = os.path.dirname(destination_file)\r\n if not os.path.exists(destination_path):\r\n os.makedirs(destination_path)\r\n\r\n # Download file bytes and write to disk\r\n file_bytes = http_get_bytes(download_url)\r\n with open(destination_file, \u0027wb\u0027) as f:\r\n f.write(file_bytes)\r\n\r\n ThisService.Commandline = \u0027-javaagent:Log4jPatcher/Log4jPatcher.jar \u0027 \u002B ThisService.Commandline\r\n print(\u0027Service {} - Log4jPatcher has been updated\u0027.format(ThisService.ServiceId))\r\n print(ThisService.Commandline)\r\n except Exception as e:\r\n asset_name = (assets[0][\u0027name\u0027] if assets else \u0027\u003Cunknown\u003E\u0027)\r\n print(\u0027Service {} - Failed to download {}: {}\u0027.format(ThisService.ServiceId, asset_name, e))\r\n", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": true, + "order": 3, + "icon": "\u003Crect fill=\u0022none\u0022 height=\u002224\u0022 width=\u002224\u0022/\u003E\u003Cpath d=\u0022M12,1L3,5v6c0,5.55,3.84,10.74,9,12c5.16-1.26,9-6.45,9-12V5L12,1z M14.5,12.59l0.9,3.88L12,14.42l-3.4,2.05l0.9-3.87 l-3-2.59l3.96-0.34L12,6.02l1.54,3.64L17.5,10L14.5,12.59z\u0022/\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "48743c56-e62c-41f9-925f-d711797319af" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [], + "name": "Install modpack - Curseforge ", + "code": "//CURSE-MODPACK-INSTALL\r\n//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Diagnostics;\r\nusing System.IO;\r\nusing System.Linq;\r\nusing System.Net.Http;\r\nusing System.Text;\r\nusing System.Text.Json;\r\nusing System.Text.RegularExpressions;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nusing System.Xml.Linq;\r\nusing Microsoft.EntityFrameworkCore;\r\nusing Version = System.Version;\r\nusing TCAdmin.SDK;\r\nusing TCAdmin.SDK.Misc;\r\nusing TCAdmin.Scripting.Engines.Addons;\r\nusing TCAdmin.SDK.Misc.Compression;\r\nusing TCAdmin.SDK.Misc.Extensions;\r\nusing TCAdmin.SDK.Database.Entities;\r\nusing TCAdmin.SDK.Hubs.Interfaces.Server;\r\nusing TCAdmin.Web.Shared.Models;\r\nusing TCAdmin.Web.Shared.Models.Enums;\r\nusing TCAdmin.Web.Shared.Models.CustomMods.Curse;\r\nusing TCAdmin.GameHosting.SDK.CustomMods;\r\nusing TCAdmin.GameHosting.SDK.Curse;\r\nusing TCAdmin.SDK.Misc.OperatingSystem;\r\n\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\n// Don\u0027t do anything if mod type is not curse\r\nif(!Globals.Variables.TryGetValue\u003Cstring\u003E(\u0022ModType\u0022, out var modType) || modType!=\u0022curse\u0022) return;\r\n\r\n// Main execution\r\nCurseForgeModpacks installer = null;\r\nvar deleteWorld = Globals.GetValue\u003Cbool\u003E(\u0022DeleteWorld\u0022);\r\nvar totalSteps = deleteWorld ? 6 : 5;\r\ninstaller = new CurseForgeModpacks(Globals, totalSteps);\r\n\r\nvar modpackInfo = await installer.GetModpackInformation();\r\nvar fileName = installer.DownloadModpack(modpackInfo).Result;\r\nvar (installerJar, specificVersion) = await installer.ExtractModpack(fileName);\r\n\r\nif(installerJar!=null \u0026\u0026 installerJar.IndexOf(\u0022packwiz\u0022) != -1) installerJar = null;\r\n\r\nvar (modloaderInfo, isNeo) = await installer.InstallModloader(modpackInfo, installerJar, specificVersion);\r\nawait installer.SetCommandLine(modloaderInfo, isNeo);\r\nif (deleteWorld)\r\n{\r\n var propertiesFile = Path.Combine(Globals.ThisService.RootDirectory, \u0022server.properties\u0022);\r\n var levelName = installer.GetLevelName(propertiesFile);\r\n installer.DeleteWorldFolders(levelName);\r\n}\r\n\r\ninstaller.WriteLog($\u0022{Globals.ThisTaskStep.DisplayName} was installed sucessfully\u0022);\r\ninstaller.Dispose();\r\ninstaller=null;\r\n\r\n// CurseForge class\r\nclass CurseForgeModpacks : IDisposable\r\n{\r\n private readonly string _modpackId;\r\n private readonly string _modpackVersion;\r\n private readonly string _modLoaderType;\r\n private readonly string _gameVersion;\r\n private readonly bool _deleteWorldFolders;\r\n private readonly string _apiKey;\r\n private readonly int _totalSteps;\r\n private readonly HttpClient _httpClient;\r\n private readonly string _tcAdminFolder;\r\n private readonly MinecraftModpacksProvider _provider;\r\n private readonly CSharpGameGlobals Globals;\r\n\r\n public CurseForgeModpacks(CSharpGameGlobals globals, int totalSteps)\r\n {\r\n Globals = globals;\r\n _tcAdminFolder = Globals.TCAdminConfig.TCAdminPath;\r\n _modpackId = Globals.GetValue\u003Cstring\u003E(\u0022ModId\u0022);\r\n _modpackVersion = Globals.GetValue\u003Cstring\u003E(\u0022ModVersion\u0022);\r\n _modLoaderType = Globals.GetValue\u003Cstring\u003E(\u0022LoaderType\u0022);\r\n _gameVersion = Globals.GetValue\u003Cstring\u003E(\u0022MinecraftVersion\u0022);\r\n _deleteWorldFolders = Globals.GetValue\u003Cbool\u003E(\u0022DeleteWorld\u0022);\r\n _provider = Globals.GetValue\u003CMinecraftModpacksProvider\u003E(\u0022ModProvider\u0022);\r\n _apiKey = CurseBrowser.CURSE_API_KEY;\r\n _totalSteps = _deleteWorldFolders ? totalSteps - 2 : totalSteps - 1;\r\n _httpClient = new HttpClient();\r\n _httpClient.DefaultRequestHeaders.Add(\u0022x-api-key\u0022, _apiKey);\r\n }\r\n\r\n public void WriteLog(string s, int? progress = null, bool rewriteLastLog = false)\r\n {\r\n if(Globals.ThisTaskStepHandler != null)\r\n {\r\n Globals.ThisTaskStepHandler.UpdateProgress(progress: progress, log: s);\r\n }\r\n else if(Globals.ThisTaskStep!=null)\r\n {\r\n if(progress.HasValue)\r\n {\r\n Globals.ThisTaskStep.Progress = progress.Value;\r\n }\r\n\r\n if(rewriteLastLog)\r\n {\r\n Globals.ThisTaskStep.DisplayLog = s;\r\n Globals.ThisTaskStep.DisplayDebugLog = s;\r\n }\r\n else\r\n {\r\n Globals.ThisTaskStep.AddDebugLog(s, skipDuplicates:true);\r\n }\r\n Globals.TCATaskManager.Update(Globals.ThisTask, true);\r\n }\r\n else\r\n {\r\n Globals.ScriptConsole.WriteLine(s);\r\n }\r\n }\r\n\r\n public void SetDisplayMessage(string s)\r\n {\r\n if(Globals.ThisTaskStep!=null)\r\n {\r\n Globals.ThisTaskStep.DisplayLog = s;\r\n Globals.ThisTaskStep.DisplayDebugLog = s;\r\n Globals.TCATaskManager.Update(Globals.ThisTask, true);\r\n }\r\n else\r\n {\r\n Globals.ScriptConsole.WriteLine(s);\r\n }\r\n }\r\n\r\n public void UpdateProgress(int progress)\r\n {\r\n if(Globals.ThisTaskStepHandler != null)\r\n {\r\n WriteLog(null, progress);\r\n }\r\n }\r\n\r\n public async Task\u003CLatestFile\u003E GetModpackInformation()\r\n {\r\n WriteLog($\u00221/{_totalSteps} - Getting information about the modpack ID {_modpackId} version {_modpackVersion}...\u0022);\r\n \r\n var modpackInfo = await CurseBrowser.GetFile(_modpackId, _modpackVersion);\r\n \r\n if (!modpackInfo.IsServerPack \u0026\u0026 modpackInfo.ServerPackFileId \u003E 0)\r\n {\r\n var serverPackId = modpackInfo.ServerPackFileId.ToString();\r\n WriteLog($\u00221/{_totalSteps} - Serverpack ID: {serverPackId}\u0022);\r\n var serverpackInfo = await CurseBrowser.GetFile(_modpackId, serverPackId);\r\n \r\n if (serverpackInfo.GameVersions.Count == 0)\r\n {\r\n //modpackInfo = serverpackInfo.Clone();\r\n serverpackInfo.GameVersions = modpackInfo.GameVersions;\r\n serverpackInfo.SortableGameVersions = modpackInfo.SortableGameVersions;\r\n modpackInfo = serverpackInfo;\r\n }\r\n else\r\n {\r\n modpackInfo = serverpackInfo;\r\n }\r\n WriteLog($\u00221/{_totalSteps} - Serverpack information received\u0022);\r\n }\r\n \r\n UpdateProgress(33);\r\n return modpackInfo;\r\n }\r\n\r\n public async Task\u003Cstring\u003E DownloadModpack(LatestFile modpackInfo)\r\n {\r\n var fileName = modpackInfo.FileName;\r\n var fileSize = modpackInfo.FileLength / (1024 * 1024);\r\n var downloadUrl = modpackInfo.DownloadUrl;\r\n \r\n if (string.IsNullOrEmpty(downloadUrl))\r\n {\r\n var fileId = modpackInfo.Id;\r\n downloadUrl = $\u0022https://edge.forgecdn.net/files/{fileId.Substring(0, 4)}/{fileId.Substring(fileId.Length - 3)}/{fileName}\u0022;\r\n }\r\n\r\n WriteLog($\u00222/{_totalSteps} - Downloading modpack \\\u0022{fileName}\\\u0022 ({fileSize}MB)\u0022);\r\n var filePath = Path.Combine(Globals.ThisService.RootDirectory, fileName);\r\n \r\n \r\n using (var response = await _httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead, Globals.ThisCancellationToken))\r\n using (var stream = await response.Content.ReadAsStreamAsync())\r\n using (var fileStream = new FileStream(filePath, FileMode.Create))\r\n {\r\n var totalBytes = response.Content.Headers.ContentLength ?? 0;\r\n var buffer = new byte[8192];\r\n var bytesRead = 0;\r\n var totalRead = 0L;\r\n var lastProgress = -1;\r\n \r\n while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) \u003E 0)\r\n {\r\n Globals.ThisCancellationToken.ThrowIfCancellationRequested();\r\n await fileStream.WriteAsync(buffer, 0, bytesRead);\r\n totalRead \u002B= bytesRead;\r\n var progress = (int)((double)totalRead / totalBytes * 100);\r\n if(progress != lastProgress)\r\n {\r\n lastProgress = progress;\r\n UpdateProgress(progress);\r\n }\r\n }\r\n }\r\n \r\n UpdateProgress(100);\r\n return fileName;\r\n }\r\n\r\n public async Task\u003C(string installerJar, string specificModloaderVersion)\u003E ExtractModpack(string fileName)\r\n {\r\n UpdateProgress(0);\r\n var specificModloaderVersion = (string)null;\r\n WriteLog($\u00223/{_totalSteps} - Extracting \\\u0022{fileName}\\\u0022\u0022);\r\n \r\n var zipFile = Path.Combine(Globals.ThisService.RootDirectory, fileName);\r\n var tmpDir = Path.Combine(Globals.ThisService.RootDirectory, $\u0022{_modpackId}-tmp\u0022);\r\n if(!Directory.Exists(tmpDir)) Directory.CreateDirectory(tmpDir);\r\n\r\n var c = new CompressionTools(Globals.OperatingSystem)\r\n {\r\n ThrowException = true,\r\n KeepListOfExtractedFiles = true,\r\n SetFileOwnerAutomatically = true\r\n };\r\n var lastExtractProgress = -1;\r\n c.Extracting\u002B=(o, e) =\u003E {\r\n var progress = (int)e.Progress;\r\n if(progress != lastExtractProgress)\r\n {\r\n lastExtractProgress = progress;\r\n UpdateProgress(progress);\r\n }\r\n };\r\n c.Decompress(zipFile, tmpDir, Globals.ThisCancellationToken);\r\n \r\n WriteLog($\u00223/{_totalSteps} - Extraction complete\u0022);\r\n var extractedFiles = c.ExtractedFiles;\r\n var regex = Globals.ThisServer.OperatingSystem == EOperatingSystem.Windows\r\n ? new Regex(@\u0022^.*[\\\\/]\u002Bmods[\\\\/]\u002B.*\\.jar$\u0022)\r\n : new Regex(@\u0022^.*/mods/.*\\.jar$\u0022);\r\n\r\n var modsFound = extractedFiles.Any(f =\u003E regex.IsMatch(f));\r\n string installerJar = null;\r\n\r\n if (!modsFound \u0026\u0026 File.Exists(Path.Combine(tmpDir, \u0022mods.csv\u0022)))\r\n {\r\n WriteLog($\u00223/{_totalSteps} - No mods folder, but mods.csv available. Downloading files...\u0022);\r\n var downloadFiles = File.ReadAllLines(Path.Combine(tmpDir, \u0022mods.csv\u0022))\r\n .Select(line =\u003E line.Split(\u0027,\u0027))\r\n .Where(parts =\u003E parts.Length \u003E= 2)\r\n .ToList();\r\n \r\n var totalFiles = downloadFiles.Count;\r\n var lastDownloadProgress = -1;\r\n for (var i = 0; i \u003C totalFiles; i\u002B\u002B)\r\n {\r\n Globals.ThisCancellationToken.ThrowIfCancellationRequested();\r\n var parts = downloadFiles[i];\r\n var fileUrl = parts[0];\r\n var modFileName = parts[1];\r\n var modFilePath = Path.Combine(Globals.ThisService.RootDirectory, modFileName);\r\n var modDir = Path.GetDirectoryName(modFilePath);\r\n \r\n if (!Directory.Exists(modDir))\r\n Directory.CreateDirectory(modDir);\r\n \r\n var progress = (int)((double)i / totalFiles * 100);\r\n if(progress != lastDownloadProgress)\r\n {\r\n lastDownloadProgress = progress;\r\n WriteLog($\u00223/{_totalSteps} - Downloading file {i\u002B1}/{totalFiles} ({progress}%)\u0022, progress, true);\r\n }\r\n \r\n using (var response = await _httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead, Globals.ThisCancellationToken))\r\n {\r\n response.EnsureSuccessStatusCode();\r\n using (var stream = await response.Content.ReadAsStreamAsync())\r\n using (var fileStream = new FileStream(modFilePath, FileMode.Create, FileAccess.Write, FileShare.None))\r\n {\r\n await stream.CopyToAsync(fileStream);\r\n }\r\n }\r\n \r\n if (modFileName.Contains(\u0022-installer.jar\u0022))\r\n installerJar = Path.GetFileName(modFileName);\r\n }\r\n }\r\n else if (modsFound)\r\n {\r\n var modsFile = extractedFiles.First(f =\u003E regex.IsMatch(f));\r\n var modsDir = Path.GetDirectoryName(modsFile);\r\n var basePath = Globals.ThisServer.OperatingSystem == EOperatingSystem.Windows\r\n ? modsDir.Replace(\u0022\\\\mods\u0022, \u0022\u0022)\r\n : modsDir.Replace(\u0022/mods\u0022, \u0022\u0022);\r\n \r\n installerJar = extractedFiles\r\n .FirstOrDefault(f =\u003E f.EndsWith(\u0022-installer.jar\u0022, StringComparison.OrdinalIgnoreCase));\r\n installerJar = installerJar != null ? Path.GetFileName(installerJar) : null;\r\n\r\n var totalFiles = extractedFiles.Count;\r\n var lastMoveProgress = -1;\r\n for (var i = 0; i \u003C totalFiles; i\u002B\u002B)\r\n {\r\n Globals.ThisCancellationToken.ThrowIfCancellationRequested();\r\n var file = extractedFiles[i];\r\n if (Path.GetFileName(file) == \u0022eula.txt\u0022) continue;\r\n \r\n var dest = Path.Combine(Globals.ThisService.RootDirectory, file.Replace(basePath, \u0022\u0022).TrimStart(Path.DirectorySeparatorChar));\r\n var destDir = Path.GetDirectoryName(dest);\r\n \r\n if (Path.GetFileName(file) == \u0022variables.txt\u0022)\r\n {\r\n WriteLog($\u00223/{_totalSteps} - variables.txt found. Loading modloader version...\u0022);\r\n specificModloaderVersion = File.ReadAllLines(file)\r\n .Where(line =\u003E !line.Trim().StartsWith(\u0022#\u0022) \u0026\u0026 line.Contains(\u0027=\u0027))\r\n .Select(line =\u003E line.Split(\u0027=\u0027))\r\n .Where(parts =\u003E parts.Length \u003E 1 \u0026\u0026 parts[0].Trim() == \u0022MODLOADER_VERSION\u0022)\r\n .Select(parts =\u003E parts[1].Trim())\r\n .FirstOrDefault();\r\n \r\n WriteLog($\u00223/{_totalSteps} - Modloader version: {specificModloaderVersion}\u0022);\r\n continue;\r\n }\r\n \r\n if (!Directory.Exists(destDir))\r\n Directory.CreateDirectory(destDir);\r\n \r\n File.Move(file, dest, true);\r\n var progress = 50 \u002B (int)((double)i / totalFiles * 50);\r\n if(progress != lastMoveProgress)\r\n {\r\n lastMoveProgress = progress;\r\n WriteLog($\u00223/{_totalSteps} - Moving files ({i\u002B1}/{totalFiles})\u0022, progress, true);\r\n }\r\n }\r\n \r\n WriteLog($\u00223/{_totalSteps} - All files moved\u0022);\r\n while (true)\r\n {\r\n Globals.ThisCancellationToken.ThrowIfCancellationRequested();\r\n try\r\n {\r\n File.Delete(zipFile);\r\n var fabricProps = Path.Combine(Globals.ThisService.RootDirectory, \u0022fabric-server-launcher.properties\u0022);\r\n if (File.Exists(fabricProps)) File.Delete(fabricProps);\r\n if (Directory.Exists(tmpDir)) Directory.Delete(tmpDir, true);\r\n break;\r\n }\r\n catch\r\n {\r\n WriteLog($\u00223/{_totalSteps} - File still in use, retrying...\u0022);\r\n Thread.Sleep(1000);\r\n }\r\n }\r\n }\r\n else\r\n {\r\n throw new Exception($\u00223/{_totalSteps} - No mods available in the pack\u0022);\r\n }\r\n \r\n UpdateProgress(100);\r\n return (installerJar, specificModloaderVersion);\r\n }\r\n\r\n public async Task\u003C(string[] modloaderInfo, bool isNeo)\u003E InstallModloader(LatestFile modloader, string includedInstallerJar, string specificModloaderVersion)\r\n {\r\n WriteLog($\u00224/{_totalSteps} - Beginning modloader installation\u0022);\r\n UpdateProgress(0);\r\n \r\n var modloaderType = modloader.SortableGameVersions\r\n .FirstOrDefault(v =\u003E v.GameVersionTypeId == 68441)?.GameVersionName;\r\n\r\n var gameVersions = modloader.GameVersions.ToList();\r\n\r\n if (gameVersions.Count \u003C 2 \u0026\u0026 string.IsNullOrEmpty(_modLoaderType))\r\n throw new Exception(\u0022You need to specify a modloader to install\u0022);\r\n\r\n gameVersions.Sort();\r\n var gameVersion = !string.IsNullOrEmpty(_gameVersion) ? _gameVersion : gameVersions[0];\r\n var isNeo = false;\r\n string modloaderVersion = null;\r\n string installerJar = null;\r\n string modloaderJar = null;\r\n\r\n if (string.IsNullOrEmpty(includedInstallerJar) || specificModloaderVersion != null)\r\n {\r\n if (_modLoaderType == \u0022Forge\u0022 || _modLoaderType == \u0022NeoForge\u0022 || \r\n modloaderType == \u0022Forge\u0022 || modloaderType == \u0022NeoForge\u0022)\r\n {\r\n isNeo = _modLoaderType == \u0022NeoForge\u0022 || modloaderType == \u0022NeoForge\u0022;\r\n var loaderResponse = _httpClient.GetAsync($\u0022https://api.curseforge.com/v1/minecraft/modloader?version={gameVersion}\u0022, Globals.ThisCancellationToken).Result;\r\n loaderResponse.EnsureSuccessStatusCode();\r\n var loaderData = JsonDocument.Parse(loaderResponse.Content.ReadAsStringAsync().Result);\r\n var latestVersions = loaderData.RootElement.GetProperty(\u0022data\u0022).EnumerateArray()\r\n .Where(v =\u003E v.GetProperty(\u0022latest\u0022).GetBoolean())\r\n .Select(v =\u003E v.GetProperty(\u0022name\u0022).GetString())\r\n .ToList();\r\n \r\n modloaderVersion = latestVersions.Max();\r\n var versionResponse = _httpClient.GetAsync($\u0022https://api.curseforge.com/v1/minecraft/modloader/{modloaderVersion}\u0022, Globals.ThisCancellationToken).Result;\r\n versionResponse.EnsureSuccessStatusCode();\r\n var versionData = JsonDocument.Parse(versionResponse.Content.ReadAsStringAsync().Result);\r\n var forgeVersion = versionData.RootElement.GetProperty(\u0022data\u0022).GetProperty(\u0022forgeVersion\u0022).GetString();\r\n modloaderVersion = specificModloaderVersion ?? forgeVersion;\r\n \r\n // Handle old Forge versions\r\n if ((_modLoaderType == \u0022Forge\u0022 || modloaderType == \u0022Forge\u0022) \u0026\u0026 \r\n new Version(modloaderVersion).CompareTo(new Version(\u002212.18.0.2008\u0022)) \u003C 0)\r\n {\r\n modloaderVersion \u002B= \u0022-\u0022 \u002B gameVersion;\r\n }\r\n \r\n if (isNeo)\r\n {\r\n var mcVersion = versionData.RootElement.GetProperty(\u0022data\u0022).GetProperty(\u0022minecraftVersion\u0022).GetString();\r\n if(new Version(mcVersion).CompareTo(new Version(\u00221.20.1\u0022)) \u003E 0)\r\n {\r\n var neoforgePrefix = GetNeoForgeVersion(gameVersion);\r\n\r\n // Step 1: Download the XML\r\n var MetadataUrl = \u0022https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml\u0022;\r\n using var client = new HttpClient();\r\n var xmlData = await client.GetStringAsync(MetadataUrl);\r\n \r\n // Step 2: Parse the XML\r\n var doc = XDocument.Parse(xmlData);\r\n var versions = doc\r\n .Root\r\n .Element(\u0022versioning\u0022)\r\n .Element(\u0022versions\u0022)\r\n .Elements(\u0022version\u0022)\r\n .Select(x =\u003E x.Value);\r\n\r\n // Step 3: Filter by prefix\r\n var matching = versions\r\n .Where(v =\u003E v.StartsWith(neoforgePrefix, StringComparison.OrdinalIgnoreCase))\r\n .ToList();\r\n\r\n if (matching.Count == 0)\r\n throw new InvalidOperationException(\r\n $\u0022Could not find a Neoforge version starting with \u0027{neoforgePrefix}\u0027.\u0022);\r\n\r\n // Step 4: Sort and pick latest\r\n matching.Sort(CompareVersionKeys);\r\n modloaderVersion = matching.SingleOrDefault(x=\u003Ex == modloaderVersion) ?? matching[^1]; // last element\r\n\r\n installerJar = $\u0022https://maven.neoforged.net/releases/net/neoforged/neoforge/{modloaderVersion}/neoforge-{modloaderVersion}-installer.jar\u0022;\r\n }\r\n else\r\n {\r\n WriteLog(\u0022NeoForge version is smaller than 1.20.1\u0022);\r\n\r\n installerJar = $\u0022https://maven.neoforged.net/releases/net/neoforged/forge/{gameVersion}-{modloaderVersion}/forge-{gameVersion}-{modloaderVersion}-installer.jar\u0022;\r\n\r\n }\r\n }\r\n\r\n if(!isNeo)\r\n {\r\n installerJar = $\u0022https://maven.minecraftforge.net/net/minecraftforge/forge/{gameVersion}-{modloaderVersion}/forge-{gameVersion}-{modloaderVersion}-installer.jar\u0022;\r\n }\r\n \r\n modloaderJar = versionData.RootElement.GetProperty(\u0022data\u0022).GetProperty(\u0022filename\u0022).GetString();\r\n }\r\n else if (_modLoaderType == \u0022Fabric\u0022 || modloaderType == \u0022Fabric\u0022)\r\n {\r\n var fabricMetadata = _httpClient.GetStringAsync(\u0022https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml\u0022).Result;\r\n var doc = XDocument.Parse(fabricMetadata);\r\n modloaderVersion = doc.Descendants(\u0022release\u0022).First().Value;\r\n installerJar = $\u0022https://maven.fabricmc.net/net/fabricmc/fabric-installer/{modloaderVersion}/fabric-installer-{modloaderVersion}.jar\u0022;\r\n modloaderJar = \u0022fabric-server-launch.jar\u0022;\r\n }\r\n \r\n var installerPath = Path.Combine(Globals.ThisService.RootDirectory, Path.GetFileName(installerJar));\r\n\r\n using (var response = await _httpClient.GetAsync(installerJar, HttpCompletionOption.ResponseHeadersRead, Globals.ThisCancellationToken))\r\n {\r\n response.EnsureSuccessStatusCode();\r\n var totalBytes = response.Content.Headers.ContentLength ?? -1L;\r\n var canReportProgress = totalBytes != -1;\r\n\r\n using (var contentStream = await response.Content.ReadAsStreamAsync())\r\n using (var fileStream = new FileStream(installerPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))\r\n {\r\n var buffer = new byte[8192];\r\n long totalRead = 0;\r\n int read;\r\n var lastProgress = -1;\r\n WriteLog($\u00224/{_totalSteps} - Downloading modloader \\\u0022{installerJar}\\\u0022\u0022);\r\n\r\n while ((read = await contentStream.ReadAsync(buffer, 0, buffer.Length)) \u003E 0)\r\n {\r\n Globals.ThisCancellationToken.ThrowIfCancellationRequested();\r\n await fileStream.WriteAsync(buffer, 0, read);\r\n totalRead \u002B= read;\r\n\r\n if (canReportProgress)\r\n {\r\n var progress = (int)((totalRead * 100L) / totalBytes);\r\n if(progress != lastProgress)\r\n {\r\n lastProgress = progress;\r\n UpdateProgress(progress);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n \r\n installerJar = Path.GetFileName(installerJar);\r\n }\r\n else\r\n {\r\n isNeo = _modLoaderType == \u0022NeoForge\u0022 || modloaderType == \u0022NeoForge\u0022;\r\n installerJar = includedInstallerJar;\r\n WriteLog($\u00224/{_totalSteps} - Modloader installer found in modpack ({installerJar})\u0022);\r\n modloaderJar = installerJar.Replace(\u0022-installer\u0022, \u0022\u0022);\r\n \r\n if (modloaderJar.Contains(\u0022fabric\u0022))\r\n {\r\n modloaderVersion = modloaderJar.Replace(\u0022fabric-\u0022, \u0022\u0022).Replace(\u0022.jar\u0022, \u0022\u0022).Replace(\u0022-\u0022, \u0022\u0022);\r\n modloaderJar = \u0022fabric-server-launch.jar\u0022;\r\n }\r\n else\r\n {\r\n modloaderVersion = modloaderJar\r\n .Replace($\u0022forge-{gameVersion}-\u0022, \u0022\u0022)\r\n .Replace(\u0022neoforge-\u0022, \u0022\u0022)\r\n .Replace(\u0022.jar\u0022, \u0022\u0022);\r\n }\r\n }\r\n\r\n WriteLog($\u00224/{_totalSteps} - Installing modloader\u0022, -1);\r\n var args = (_modLoaderType.EndsWith(\u0022Forge\u0022) || _modLoaderType.EndsWith(\u0022Forge\u0022) || isNeo)\r\n ? $\u0022-jar {installerJar} --installServer\u0022\r\n : $\u0022-jar {installerJar} server -downloadMinecraft -mcversion {gameVersion} -dir {Globals.ThisService.RootDirectory}\u0022;\r\n \r\n Process p;\r\n if (Globals.ThisServer.OperatingSystem == EOperatingSystem.Windows)\r\n {\r\n var startInfo = new ServiceToolStartOptions\r\n {\r\n Executable = Path.Combine(Globals.ThisService.RootDirectory, Globals.ThisService.Executable),\r\n WorkingDirectory = Globals.ThisService.RootDirectory,\r\n Arguments = args\r\n };\r\n\r\n var startResult = await Globals.ServiceManagerService.StartService(Globals.ThisService.ServiceId, startInfo);\r\n\r\n if(!startResult.Success) throw new Exception(\u0022Could not start installer process\u0022);\r\n\r\n p = Process.GetProcessById(startResult.ProcessId);\r\n }\r\n else\r\n {\r\n var psi = new ProcessStartInfo\r\n {\r\n FileName = Path.Combine(Globals.ThisService.RootDirectory, Globals.ThisService.Executable),\r\n Arguments = args,\r\n WorkingDirectory = Globals.ThisService.RootDirectory,\r\n UseShellExecute = false\r\n };\r\n\r\n var runAs = Globals.ServiceManagerService.GetSafeAccessTokenAndCredential(Globals.ThisService.ServiceId).Credential;\r\n if(runAs != null \u0026\u0026 !string.IsNullOrEmpty(runAs.UserName))\r\n {\r\n psi.UserName = runAs.UserName;\r\n var owner = Globals.OperatingSystem.GetOwner(Globals.ThisService.RootDirectory);\r\n Globals.OperatingSystem.SetDirectoryOwner(Globals.ThisService.RootDirectory, string.Empty, owner, true);\r\n }\r\n\r\n LinuxSystemUtility.MakeExecutable(psi.FileName);\r\n p = Process.Start(psi);\r\n if (p == null) throw new Exception(\u0022Could not start installer process\u0022);\r\n }\r\n p.EnableRaisingEvents = true;\r\n UpdateProgress(-1);\r\n Globals.Globals.ThisCancellationToken.Register(() =\u003E {\r\n if(p != null \u0026\u0026 !p.HasExited) p.Kill(true);\r\n });\r\n await p.WaitForExitAsync(Globals.Globals.ThisCancellationToken);\r\n UpdateProgress(100);\r\n \r\n if (p.ExitCode != 0)\r\n {\r\n if ((_modLoaderType == \u0022Fabric\u0022 || modloaderType == \u0022Fabric\u0022) \u0026\u0026 !string.IsNullOrEmpty(includedInstallerJar))\r\n throw new Exception(\u0022Fabric modloader installation failed. Try another version/modloader\u0022);\r\n \r\n if ((_modLoaderType.EndsWith(\u0022Forge\u0022) || modloaderType.EndsWith(\u0022Forge\u0022)) \u0026\u0026 !string.IsNullOrEmpty(includedInstallerJar))\r\n {\r\n WriteLog($\u00224/{_totalSteps} - Included Forge failed. Trying latest for {gameVersion}\u0022);\r\n return await InstallModloader(modloader, null, null);\r\n }\r\n p=null;\r\n throw new Exception(\u0022Modloader installation failed. Check logs for details.\u0022);\r\n }\r\n else\r\n {\r\n p=null;\r\n }\r\n \r\n // Check for universal jar\r\n if ((_modLoaderType == \u0022Forge\u0022 || modloaderType == \u0022Forge\u0022) \u0026\u0026 !isNeo)\r\n {\r\n var universalJar = Path.Combine(Globals.ThisService.RootDirectory, installerJar.Replace(\u0022-installer\u0022, \u0022-universal\u0022));\r\n WriteLog($\u00224/{_totalSteps} - Checking if Forge installer created universal jar {universalJar}\u0022);\r\n if (File.Exists(universalJar))\r\n {\r\n modloaderJar = Path.GetFileName(universalJar);\r\n WriteLog($\u00224/{_totalSteps} - Using universal jar: {modloaderJar}\u0022);\r\n }\r\n }\r\n \r\n UpdateProgress(90);\r\n WriteLog($\u00224/{_totalSteps} - Modloader installed\u0022);\r\n \r\n // Cleanup\r\n var installerPathToDelete = Path.Combine(Globals.ThisService.RootDirectory, installerJar);\r\n if (File.Exists(installerPathToDelete)) File.Delete(installerPathToDelete);\r\n \r\n UpdateProgress(100);\r\n\r\n return (new[] { modloaderJar, gameVersion, modloaderVersion }, isNeo);\r\n }\r\n\r\n public async Task SetCommandLine(string[] modloaderInfo, bool isNeo)\r\n {\r\n WriteLog($\u00225/{_totalSteps} - Configuring commandline\u0022);\r\n var variables = new TCAdminVars() {\r\n {_provider.JarVariableName, modloaderInfo[0].Replace(Globals.ThisService.RootDirectory, \u0022\u0022)}\r\n };\r\n\r\n // Delete previous command line\r\n var prevCmdLineKey=$\u0022{_provider.InstallPrefix}:CustomModsCmd\u0022;\r\n if(Globals.ThisService.Variables.TryGetValue\u003Clong?\u003E(prevCmdLineKey, out var prevCmdLineId))\r\n {\r\n WriteLog($\u00225/{_totalSteps} - Deleting previous commandline\u0022);\r\n var prevCmdLine = Globals.ThisService.CustomCommandLines.SingleOrDefault(x=\u003Ex.CmdLineId==prevCmdLineId);\r\n if(prevCmdLine!=null) Globals.ThisService.CustomCommandLines.Remove(prevCmdLine);\r\n }\r\n\r\n var cmdLine = new ServiceCustomCommandLine\r\n { \r\n Name=Globals.ThisTaskStep.DisplayName,\r\n CommandLine = Globals.ThisGame.CommandlineConfig.DefaultCommandline,\r\n Variables = variables\r\n };\r\n Globals.ThisService.CustomCommandLines.Add(cmdLine);\r\n\r\n if (Version.TryParse($\u0022{modloaderInfo[2].Split(\u0027.\u0027)[0]}.0\u0022, out var parsed) \u0026\u0026 parsed.CompareTo(new Version(\u002237.0\u0022)) \u003C 0 \u0026\u0026 !isNeo)\r\n {\r\n // Nothing to do?\r\n }\r\n else\r\n {\r\n var argsOs = Globals.ThisServer.OperatingSystem == EOperatingSystem.Windows ? \u0022win\u0022 : \u0022unix\u0022;\r\n var forgeType = isNeo ? \u0022neoforged\u0022 : \u0022minecraftforge\u0022;\r\n var jarVar = $\u0022-jar ${{{_provider.JarVariableName}}}\u0022;\r\n \r\n // WriteLog($\u00225/{_totalSteps} - Detected isNeo: {isNeo} forgeType: {forgeType} jarVar: {jarVar} modloaderInfo: {modloaderInfo.ToJson()}\u0022);\r\n cmdLine.OverrideDefault = true;\r\n if (isNeo \u0026\u0026 new Version(modloaderInfo[1]).CompareTo(new Version(\u00221.20.1\u0022)) \u003E 0)\r\n {\r\n cmdLine.CommandLine = cmdLine.CommandLine.Replace(jarVar, $\u0022@libraries/net/{forgeType}/neoforge/{modloaderInfo[2]}/{argsOs}_args.txt\u0022);\r\n }\r\n else\r\n {\r\n cmdLine.CommandLine=cmdLine.CommandLine.Replace(jarVar, $\u0022@libraries/net/{forgeType}/forge/{modloaderInfo[1]}-{modloaderInfo[2]}/{argsOs}_args.txt\u0022);\r\n }\r\n }\r\n\r\n await Globals.ServiceManager.UpdateAsync(Globals.ThisService, true, default);\r\n Globals.ThisService.Variables[prevCmdLineKey] = cmdLine.CmdLineId;\r\n Globals.ThisService.Variables[$\u0022{_provider.InstallPrefix}:Type\u0022] = \u0022Curse\u0022;\r\n Globals.ThisService.Variables[\u0022__SelectedCmdLine\u0022] = $\u0022CUS:{cmdLine.CmdLineId}\u0022;\r\n \r\n var appliedCmdline = await Globals.ServiceManager.ApplyCustomCommandLine(null, Globals.ThisService.ServiceId, cmdLine.CmdLineId, false);\r\n if(!appliedCmdline) throw new Exception($\u0022Could not apply command line id {cmdLine.CmdLineId}\u0022);\r\n\r\n await Globals.ServiceManager.UpdateAsync(Globals.ThisService, true, default);\r\n }\r\n\r\n public string GetLevelName(string filePath)\r\n {\r\n foreach (var line in File.ReadLines(filePath))\r\n {\r\n if (line.StartsWith(\u0022level-name=\u0022))\r\n {\r\n var levelName = line.Split(\u0027=\u0027)[1].Trim();\r\n WriteLog($\u00226/{_totalSteps} - Level name detected: {levelName}\u0022);\r\n return levelName;\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n public void DeleteWorldFolders(string levelName)\r\n {\r\n if (string.IsNullOrEmpty(levelName))\r\n {\r\n WriteLog($\u00226/{_totalSteps} - Level name empty. Skipping deletion\u0022);\r\n return;\r\n }\r\n \r\n WriteLog($\u00226/{_totalSteps} - Deleting world folders\u0022);\r\n var basePath = Path.Combine(Globals.ThisService.RootDirectory, levelName);\r\n var folders = new[]\r\n {\r\n basePath,\r\n basePath \u002B \u0022_the_end\u0022,\r\n basePath \u002B \u0022_nether\u0022\r\n };\r\n \r\n foreach (var folder in folders)\r\n {\r\n if (Directory.Exists(folder))\r\n {\r\n Directory.Delete(folder, true);\r\n WriteLog($\u00226/{_totalSteps} - Deleted: {folder}\u0022);\r\n }\r\n else\r\n {\r\n WriteLog($\u00226/{_totalSteps} - Not found: {folder}\u0022);\r\n }\r\n }\r\n }\r\n\r\n public static (List\u003Cint\u003E NumericParts, bool IsBeta) VersionKey(string v)\r\n {\r\n if (v == null) throw new ArgumentNullException(nameof(v));\r\n\r\n bool isBeta = v.Contains(\u0022-beta\u0022, StringComparison.OrdinalIgnoreCase);\r\n // Remove any \u0022-beta\u0022 suffix (case-insensitive) before splitting\r\n string cleaned = v.Replace(\u0022-beta\u0022, \u0022\u0022, StringComparison.OrdinalIgnoreCase);\r\n // Split on \u0027.\u0027, parse each segment to int\r\n var numericParts = cleaned\r\n .Split(\u0027.\u0027, StringSplitOptions.RemoveEmptyEntries)\r\n .Select(p =\u003E\r\n {\r\n if (!int.TryParse(p, out var n))\r\n throw new FormatException($\u0022Invalid numeric version component: \u0027{p}\u0027 in version \u0027{v}\u0027\u0022);\r\n return n;\r\n })\r\n .ToList();\r\n\r\n return (numericParts, isBeta);\r\n }\r\n\r\n public static string GetNeoForgeVersion(string mcVersion)\r\n {\r\n if (string.IsNullOrWhiteSpace(mcVersion))\r\n throw new ArgumentException(\u0022Version must not be empty\u0022, nameof(mcVersion));\r\n\r\n var parts = mcVersion.Split(\u0027.\u0027, StringSplitOptions.RemoveEmptyEntries);\r\n if (parts.Length \u003C 2)\r\n throw new ArgumentException(\r\n \u0022Invalid Minecraft version format. Use format like \u00271.21.1\u0027\u0022, nameof(mcVersion));\r\n\r\n // Skip the first component and re-join the rest\r\n return string.Join(\u0027.\u0027, parts.Skip(1));\r\n }\r\n\r\n private static int CompareVersionKeys(string a, string b)\r\n {\r\n var (pa, betaA) = VersionKey(a);\r\n var (pb, betaB) = VersionKey(b);\r\n\r\n int len = Math.Max(pa.Count, pb.Count);\r\n for (int i = 0; i \u003C len; i\u002B\u002B)\r\n {\r\n int na = i \u003C pa.Count ? pa[i] : 0;\r\n int nb = i \u003C pb.Count ? pb[i] : 0;\r\n int cmp = na.CompareTo(nb);\r\n if (cmp != 0)\r\n return cmp;\r\n }\r\n\r\n // if numeric parts equal, non-beta (false) comes before beta (true)\r\n return betaA.CompareTo(betaB);\r\n }\r\n\r\n public void Dispose()\r\n {\r\n _httpClient?.Dispose();\r\n }\r\n}", + "runImpersonated": false, + "stopService": false, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cg stroke-width=\u00220\u0022\u003E\u003C/g\u003E\u003Cg stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022\u003E\u003C/g\u003E\u003Cg\u003E\u003Cpath d=\u0022m6.307 5.581.391 1.675H0s.112.502.167.558c.168.279.335.614.559.837 1.06 1.228 2.902 1.73 4.409 2.009 1.06.224 2.121.28 3.181.335l1.228 3.293h.67l.391 1.061h-.558l-.949 3.07h9.321l-.949-3.07h-.558l.39-1.061h.67s.558-3.404 2.288-4.967C21.935 7.758 24 7.535 24 7.535V5.581H6.307zm9.377 8.428c-.447.279-.949.279-1.284.503-.223.111-.335.446-.335.446-.223-.502-.502-.67-.837-.781-.335-.112-.949-.056-1.786-.782-.558-.502-.614-1.172-.558-1.507v-.167c0-.056 0-.112.056-.168.111-.334.39-.669.948-.893 0 0-.39.559 0 1.117.224.335.67.502 1.061.279.167-.112.279-.335.335-.503.111-.39.111-.781-.224-1.06-.502-.446-.613-1.06-.279-1.451 0 0 .112.502.614.446.335 0 .335-.111.224-.223-.056-.167-.782-1.228.279-2.009 0 0 .669-.447 1.451-.391-.447.056-.949.335-1.116.782v.055c-.168.447-.056.949.279 1.396.223.335.502.614.614 1.06-.168-.056-.279 0-.391.112a.533.533 0 0 0-.112.502c.056.112.168.223.279.223h.168c.167-.055.279-.279.223-.446.112.111.167.391.112.558 0 .167-.112.335-.168.446-.056.112-.167.224-.223.335-.056.112-.112.224-.112.335 0 .112 0 .279.056.391.223.335.67 0 .782-.279.167-.335.111-.726-.112-1.061 0 0 .391.224.67 1.005.223.67-.168 1.451-.614 1.73z\u0022\u003E\u003C/path\u003E\u003C/g\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "49a002c6-020e-44df-8459-8b25081665ec" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [ + "CustomServiceIcon" + ], + "name": "Select Java version", + "description": "Select a specific version of Java or allow the control panel to automatically decide.", + "code": "//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing TCAdmin.SDK.DynamicInput.Controls;\r\n\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\nGlobals.ThisService.Variables.TryGetValue\u003Cstring\u003E(\u0022JavaVersion\u0022, out var currentVersion);\r\nGlobals.ScriptConsole.WriteLine($\u0022The Java version has been set to \\\u0022{ currentVersion }\\\u0022\u0022);\r\n\r\n/*\r\nvar versions = new List\u003Cstring\u003E() {\r\n \u0022Automatic\u0022,\r\n \u0022Java 8\u0022,\r\n \u0022Java 11\u0022,\r\n \u0022Java 16\u0022,\r\n \u0022Java 17\u0022,\r\n \u0022Java 21\u0022\r\n};\r\n\r\nvar versionsComboBox = new DynamicComboBox\r\n{\r\n Id = \u0022SelectedJavaVersion\u0022,\r\n Label = \u0022Select the Java version to use\u0022,\r\n Description = \u0022This value is required\u0022,\r\n DefaultValue = currentVersion,\r\n Required = true\r\n};\r\n\r\nforeach(var version in versions)\r\n{\r\n versionsComboBox.Items.Add(new DynamicComboBoxItem { Text = version, Value = version });\r\n}\r\n\r\nvar result = Globals.ThisScript.Input(false, versionsComboBox);\r\n\r\nGlobals.ThisGameService.Variables[\u0022JavaVersion\u0022] = result[\u0022SelectedJavaVersion\u0022];\r\nGlobals.ServiceManager.Update(Globals.ThisGameService.Service, true);*/", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cg\u003E\u003Cpath d=\u0022M0,0h24v24H0V0z\u0022 fill=\u0022none\u0022/\u003E\u003C/g\u003E\u003Cg\u003E\u003Cpath d=\u0022M18.5,3H6C4.9,3,4,3.9,4,5v5.71c0,3.83,2.95,7.18,6.78,7.29c3.96,0.12,7.22-3.06,7.22-7v-1h0.5c1.93,0,3.5-1.57,3.5-3.5 S20.43,3,18.5,3z M16,5v3H6V5H16z M18.5,8H18V5h0.5C19.33,5,20,5.67,20,6.5S19.33,8,18.5,8z M4,19h16v2H4V19z\u0022/\u003E\u003C/g\u003E", + "scriptInputVariables": [ + { + "variable": { + "name": "JavaVersion", + "defaultValue": "Automatic", + "required": true, + "requiredMessage": "The java version is required", + "scriptParameter": true, + "commandlineParameter": false, + "saveScriptParameter": true, + "syncCommandlineParameter": false, + "template": "${JavaVersion}", + "editor": { + "items": [ + { + "text": "", + "value": "Automatic" + }, + { + "text": "", + "value": "Java 8" + }, + { + "value": "Java 11" + }, + { + "value": "Java 16" + }, + { + "value": "Java 17" + }, + { + "value": "Java 21" + }, + { + "value": "Java 25" + } + ], + "id": "JavaVersion", + "defaultValue": "Automatic", + "label": "Select the Java version to use", + "description": "This value is required", + "required": true, + "valueType": "string", + "requiredMessage": "The java version is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-07-12T02:29:13.214", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + "variableId": 56 + } + ], + "scriptRoles": [ + { + "roleId": 3 + }, + { + "roleId": 2 + } + ], + "editor": [ + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "id": "Xms", + "defaultValue": "128", + "label": "Xms", + "description": "Min memory allocated by Java in megabytes", + "required": false, + "requiredMessage": "A value for Xms is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 128, + "id": "Xmx", + "defaultValue": "2048", + "label": "Xmx", + "description": "Max memory allocated by Java in megabytes", + "required": false, + "requiredMessage": "A value for Xmx is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "items": [ + { + "text": "", + "value": "Automatic" + }, + { + "text": "", + "value": "Java 8" + }, + { + "value": "Java 11" + }, + { + "value": "Java 16" + }, + { + "value": "Java 17" + }, + { + "value": "Java 21" + } + ], + "id": "JavaVersion", + "defaultValue": "Automatic", + "label": "Select the Java version to use", + "description": "This value is required", + "required": false, + "valueType": "string", + "requiredMessage": "The java version is required", + "controlType": "DynamicComboBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + } + ], + "identifier": "5f01bb82-e4ce-46c9-9989-3f00aa172351" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [ + "AfterCustomModUninstalled", + "BeforeServiceReinstalled" + ], + "name": "Uninstall modpack", + "code": "//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing TCAdmin.GameHosting.SDK.CustomMods;\r\nusing TCAdmin.GameHosting.SDK.Curse;\r\nusing TCAdmin.SDK.Misc.Extensions;\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\n// Not a mod pack\r\nvar providerId = Globals.Variables.Parse\u003Cint\u003E(\u0022ProviderId\u0022);\r\nif(providerId != 3) return;\r\n\r\nvar gameService = Globals.ThisService;\r\nvar cancellationToken = Globals.ThisCancellationToken;\r\n// Remove modpack command line and variables\r\nvar modpackCmdLineKey = $\u0022{MinecraftModpacksProvider.ModpacksPrefix}:CustomModsCmd\u0022;\r\nvar modpackTypeKey = $\u0022{MinecraftModpacksProvider.ModpacksPrefix}:Type\u0022;\r\nvar modpackInstalledKey = $\u0022{MinecraftModpacksProvider.ModpacksPrefix}:Installed\u0022;\r\nif (gameService.Variables.TryGetValue\u003Clong?\u003E(modpackCmdLineKey, out var modpackCmdLineId))\r\n{\r\n var cmd = gameService.CustomCommandLines.SingleOrDefault(x =\u003E x.CmdLineId == modpackCmdLineId);\r\n if (cmd != null) {\r\n gameService.CustomCommandLines.Remove(cmd);\r\n }\r\n\r\n if (gameService.Variables.TryGetValue\u003Cstring\u003E(\u0022__SelectedCmdLine\u0022, out var selectedCmd) \u0026\u0026 selectedCmd == $\u0022CUS:{modpackCmdLineId}\u0022)\r\n gameService.Variables.Remove(\u0022__SelectedCmdLine\u0022);\r\n}\r\n\r\ngameService.Variables.Remove(modpackTypeKey);\r\ngameService.Variables.Remove(modpackCmdLineKey);\r\ngameService.Variables.Remove(modpackInstalledKey);\r\n\r\nawait Globals.GameServiceManager.UpdateAsync(gameService, true, cancellationToken);\r\n\r\n// Apply the first predefined command line\r\nvar firstCommandline = Globals.ThisGame.CommandlineConfig.PredefinedCommandlines.FirstOrDefault();\r\nif(firstCommandline != null)\r\n{\r\n await Globals.ServiceManager.ApplyPredefinedCommandLine(null, Globals.ThisService.ServiceId, firstCommandline.Id, false);\r\n}\r\n\r\nWriteLog($\u0022{(Globals.ThisTaskStep?.DisplayName ?? \u0022Modpack\u0022)} was uninstalled sucessfully\u0022);\r\n\r\nvoid WriteLog(string s, int? progress = null, bool rewriteLastLog = false)\r\n{\r\n if(Globals.ThisTaskStepHandler != null)\r\n {\r\n Globals.ThisTaskStepHandler.UpdateProgress(progress: progress, log: s);\r\n }\r\n else if(Globals.ThisTaskStep!=null)\r\n {\r\n if(progress.HasValue)\r\n {\r\n Globals.ThisTaskStep.Progress = progress.Value;\r\n }\r\n\r\n if(rewriteLastLog)\r\n {\r\n Globals.ThisTaskStep.DisplayLog = s;\r\n Globals.ThisTaskStep.DisplayDebugLog = s;\r\n }\r\n else\r\n {\r\n Globals.ThisTaskStep.AddDebugLog(s, skipDuplicates:true);\r\n }\r\n Globals.TCATaskManager.Update(Globals.ThisTask, true);\r\n }\r\n else\r\n {\r\n Globals.ScriptConsole.WriteLine(s);\r\n }\r\n}", + "runImpersonated": true, + "stopService": true, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Csvg viewBox=\u00220 0 43 40\u0022 version=\u00221.1\u0022\u003E\r\n \u003Cpath d=\u0022m12.5 0l-12.5 8.1 8.7 7 12.5-7.8-8.7-7.3zm-12.5 21.9l12.5 8.2 8.7-7.3-12.5-7.7-8.7 6.8zm21.2 0.9l8.8 7.3 12.4-8.1-8.6-6.9-12.6 7.7zm21.2-14.7l-12.4-8.1-8.8 7.3 12.6 7.8 8.6-7zm-21.1 16.3l-8.8 7.3-3.7-2.5v2.8l12.5 7.5 12.5-7.5v-2.8l-3.8 2.5-8.7-7.3z\u0022/\u003E\r\n\u003C/svg\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "832127bd-b992-49b0-8cb0-145598cd1e39" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [], + "name": "Install modpack - FeedTheBeast", + "code": "//FTB-MODPACK-INSTALL\r\n//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Diagnostics;\r\nusing TCAdmin.SDK.Hubs.Interfaces.Server;\r\nusing System.IO;\r\nusing System.Text.Json;\r\nusing System.Net.Http;\r\nusing System.Text.RegularExpressions;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nusing TCAdmin.SDK.Misc;\r\nusing TCAdmin.SDK;\r\nusing TCAdmin.Web.Shared.Models.Enums;\r\nusing TCAdmin.GameHosting.SDK.CustomMods;\r\nusing TCAdmin.Web.Shared.Models;\r\nusing System.Runtime.InteropServices;\r\nusing TCAdmin.SDK.Misc.OperatingSystem;\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\n// Don\u0027t do anything if mod type is not ftb\r\nif(!Globals.Variables.TryGetValue\u003Cstring\u003E(\u0022ModType\u0022, out var modType) || modType!=\u0022ftb\u0022) return;\r\n\r\n// Main execution\r\nvar installer = new FTBModpacks(Globals);\r\nvar installerFilename = await installer.DownloadServerpackInstaller();\r\nawait installer.InstallModpack(installerFilename);\r\nawait installer.SetCommandLine();\r\n\r\nif (installer.DeleteWorld)\r\n{\r\n var propertiesFile = Path.Combine(Globals.ThisService.RootDirectory, \u0022server.properties\u0022);\r\n var levelName = installer.GetLevelName(propertiesFile);\r\n installer.DeleteWorldFolders(levelName);\r\n}\r\n\r\ninstaller.WriteLog($\u0022{Globals.ThisTaskStep.DisplayName} was installed sucessfully\u0022);\r\n\r\nclass FTBModpacks\r\n{\r\n private readonly MinecraftModpacksProvider _provider;\r\n public readonly TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals Globals;\r\n public string ModpackId { get; }\r\n public string ModpackVersion { get; }\r\n public string ModpackName { get; }\r\n public bool DeleteWorld { get; }\r\n \r\n public FTBModpacks(TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals globals)\r\n {\r\n Globals = globals;\r\n ModpackId = Globals.GetValue\u003Cstring\u003E(\u0022ModId\u0022);\r\n DeleteWorld = Globals.GetValue\u003Cbool\u003E(\u0022DeleteWorld\u0022);\r\n ModpackVersion = Globals.GetValue\u003Cstring\u003E(\u0022ModVersion\u0022);\r\n _provider = Globals.GetValue\u003CMinecraftModpacksProvider\u003E(\u0022ModProvider\u0022);\r\n ModpackName = Globals.ThisTaskStep.DisplayName;\r\n }\r\n\r\n public void WriteLog(string s, int? progress = null, bool rewriteLastLog = false)\r\n {\r\n if(Globals.ThisTaskStepHandler != null)\r\n {\r\n Globals.ThisTaskStepHandler.UpdateProgress(progress: progress, log: s);\r\n }\r\n else if(Globals.ThisTaskStep!=null)\r\n {\r\n if(progress.HasValue)\r\n {\r\n Globals.ThisTaskStep.Progress = progress.Value;\r\n }\r\n\r\n if(rewriteLastLog)\r\n {\r\n Globals.ThisTaskStep.DisplayLog = s;\r\n Globals.ThisTaskStep.DisplayDebugLog = s;\r\n }\r\n else\r\n {\r\n Globals.ThisTaskStep.AddDebugLog(s, skipDuplicates:true);\r\n }\r\n Globals.TCATaskManager.Update(Globals.ThisTask, true);\r\n }\r\n else\r\n {\r\n Globals.ScriptConsole.WriteLine(s);\r\n }\r\n }\r\n\r\n public void SetDisplayMessage(string s, int? progress = null)\r\n {\r\n if(Globals.ThisTaskStepHandler!=null)\r\n {\r\n Globals.ThisTaskStepHandler.UpdateProgress(progress: progress, log: s);\r\n }\r\n else if(Globals.ThisTaskStep!=null)\r\n {\r\n Globals.ThisTaskStep.DisplayLog = s;\r\n Globals.ThisTaskStep.DisplayDebugLog = s;\r\n if(progress.HasValue) Globals.ThisTaskStep.Progress = progress.Value;\r\n Globals.TCATaskManager.Update(Globals.ThisTask, true);\r\n }\r\n else\r\n {\r\n Globals.ScriptConsole.WriteLine(s);\r\n }\r\n }\r\n\r\n public void UpdateProgress(int progress)\r\n {\r\n if(Globals.ThisTaskStepHandler != null)\r\n {\r\n WriteLog(null, progress);\r\n }\r\n }\r\n \r\n public async Task\u003Cstring\u003E DownloadServerpackInstaller()\r\n {\r\n WriteLog($\u0022Downloading modpack installer for {ModpackName}\u0022);\r\n \r\n var isWindows = Globals.ThisServer.OperatingSystem == EOperatingSystem.Windows;\r\n var installerFilename = Path.Combine(\r\n Globals.ThisService.RootDirectory, \r\n isWindows ? \u0022ftb-serverpack-installer.exe\u0022 : \u0022ftb-serverpack-installer\u0022\r\n );\r\n \r\n var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? \u0022arm\u0022 : \u0022amd64\u0022;\r\n var os = isWindows ? \u0022windows\u0022 : \u0022linux\u0022;\r\n var osArg = $\u0022{arch}/{os}\u0022;\r\n //{\u0022valid_os_values\u0022:[\u0022windows\u0022,\u0022linux\u0022,\u0022darwin\u0022,\u0022freebsd\u0022,\u0022unix\u0022],\u0022valid_arch_values\u0022:[\u0022arm64\u0022,\u0022amd64\u0022,\u0022arm\u0022,\u0022x64\u0022],\u0022possible_values\u0022:[\u0022windows\u0022,\u0022arm64/windows\u0022,\u0022amd64/windows\u0022,\u0022linux\u0022,\u0022arm64/linux\u0022,\u0022amd64/linux\u0022,\u0022darwin\u0022,\u0022arm64/darwin\u0022,\u0022amd64/darwin\u0022,\u0022freebsd\u0022,\u0022arm64/freebsd\u0022,\u0022amd64/freebsd\u0022]}\r\n var downloadUrl = $\u0022https://api.feed-the-beast.com/v1/modpacks/public/modpack/{ModpackId}/{ModpackVersion}/server/{osArg}\u0022;\r\n\r\n // Download with progress reporting\r\n using var httpClient = new HttpClient();\r\n using var response = httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result;\r\n response.EnsureSuccessStatusCode();\r\n\r\n var contentLength = response.Content.Headers.ContentLength ?? -1L;\r\n using var downloadStream = response.Content.ReadAsStreamAsync().Result;\r\n using var fileStream = new FileStream(installerFilename, FileMode.Create, FileAccess.Write);\r\n \r\n var buffer = new byte[8192];\r\n var totalRead = 0L;\r\n var progress = 0;\r\n var lastProgress = -1;\r\n \r\n int bytesRead;\r\n while ((bytesRead = await downloadStream.ReadAsync(buffer, 0, buffer.Length, Globals.ThisCancellationToken)) \u003E 0)\r\n {\r\n if(Globals.ThisCancellationToken.IsCancellationRequested)\r\n {\r\n httpClient.CancelPendingRequests();\r\n Globals.ThisCancellationToken.ThrowIfCancellationRequested();\r\n }\r\n await fileStream.WriteAsync(buffer, 0, bytesRead, Globals.ThisCancellationToken);\r\n totalRead \u002B= bytesRead;\r\n \r\n if (contentLength \u003E 0)\r\n {\r\n progress = (int)((double)totalRead / contentLength * 100);\r\n if (progress != lastProgress \u0026\u0026 progress % 5 == 0)\r\n {\r\n SetDisplayMessage($\u0022Downloading modloader \\\u0022{Path.GetFileName(installerFilename)}\\\u0022 ({progress}%)\u0022, progress);\r\n lastProgress = progress;\r\n }\r\n }\r\n }\r\n\r\n UpdateProgress(progress);\r\n WriteLog(\u0022Modpack installer downloaded\u0022);\r\n return installerFilename;\r\n }\r\n\r\n public async Task InstallModpack(string installerFilename)\r\n {\r\n WriteLog($\u0022Installing modpack {ModpackName}\u0022, -1);\r\n \r\n var os = Globals.ThisServer.OperatingSystem;\r\n var isWindows = os == EOperatingSystem.Windows;\r\n\r\n if (Globals.ThisServer.OperatingSystem == EOperatingSystem.Linux)\r\n {\r\n var chmod = Process.Start(\u0022chmod\u0022, $\u0022\u002Bx {installerFilename}\u0022);\r\n chmod.WaitForExit();\r\n }\r\n\r\n var args = $\u0022--pack {ModpackId} --version {ModpackVersion} --auto --force --no-java\u0022; //https://github.com/FTBTeam/FTB-Modpack-Issues/issues/6312#issuecomment-2516899020\r\n\r\n //Get java version required by modpack\r\n var manifestUrl = $\u0022https://api.feed-the-beast.com/v1/modpacks/public/modpack/{ModpackId}/{ModpackVersion}\u0022;\r\n using var httpClient = new HttpClient();\r\n var manifest = await httpClient.GetStringAsync(manifestUrl);\r\n var root = JsonSerializer.Deserialize\u003CManifestRoot\u003E(manifest);\r\n var javaVersion = root.targets.FirstOrDefault(t =\u003E t.name == \u0022java\u0022)?.version.Split(\u0027.\u0027)[0];\r\n string javaPath = Path.Combine(Globals.ThisService.RootDirectory, \u0022java\u0022, $\u0022java{javaVersion}\u0022, \u0022bin\u0022, (isWindows? \u0022java.exe\u0022:\u0022java\u0022));\r\n if(!File.Exists(javaPath) \u0026 Environment.GetEnvironmentVariable($\u0022JAVA{javaVersion}\u0022) != null) // Use version installed separatelly\r\n {\r\n javaPath = Environment.GetEnvironmentVariable($\u0022JAVA{javaVersion}\u0022);\r\n }\r\n\r\n if(!File.Exists(javaPath)) throw new Exception($\u0022Could not find folder for Java version {javaVersion}\u0022);\r\n\r\n Process p;\r\n if (isWindows)\r\n {\r\n var startInfo = new ServiceToolStartOptions\r\n {\r\n Executable = installerFilename,\r\n WorkingDirectory = Globals.ThisService.RootDirectory,\r\n Arguments = args\r\n };\r\n startInfo.EnvironmentVariables.Add(\u0022PATH\u0022, Path.GetDirectoryName(javaPath));\r\n \r\n var startResult = await Globals.ServiceManagerService.StartService(Globals.ThisService.ServiceId, startInfo);\r\n\r\n if(!startResult.Success) throw new Exception(\u0022Could not start installer process\u0022);\r\n\r\n p = Process.GetProcessById(startResult.ProcessId);\r\n }\r\n else\r\n {\r\n var psi = new ProcessStartInfo\r\n {\r\n FileName = installerFilename,\r\n Arguments = args,\r\n WorkingDirectory = Globals.ThisService.RootDirectory,\r\n UseShellExecute = false\r\n };\r\n\r\n var runAs = Globals.ServiceManagerService.GetSafeAccessTokenAndCredential(Globals.ThisService.ServiceId).Credential;\r\n if(runAs != null \u0026\u0026 !string.IsNullOrEmpty(runAs.UserName))\r\n {\r\n psi.UserName = runAs.UserName;\r\n var owner = Globals.OperatingSystem.GetOwner(Globals.ThisService.RootDirectory);\r\n Globals.OperatingSystem.SetDirectoryOwner(Globals.ThisService.RootDirectory, string.Empty, owner, true);\r\n }\r\n\r\n psi.Environment[\u0022PATH\u0022] = Path.GetDirectoryName(javaPath);\r\n LinuxSystemUtility.MakeExecutable(psi.FileName);\r\n p = Process.Start(psi);\r\n if (p == null) throw new Exception(\u0022Could not start installer process\u0022);\r\n }\r\n\r\n p.EnableRaisingEvents=true;\r\n Globals.ThisCancellationToken.Register(() =\u003E {\r\n if(p != null \u0026\u0026 !p.HasExited) p.Kill(true);\r\n });\r\n await p.WaitForExitAsync(Globals.ThisCancellationToken);\r\n var exitCode = p.ExitCode;\r\n p = null;\r\n UpdateProgress(100);\r\n\r\n if (File.Exists(installerFilename)) File.Delete(installerFilename);\r\n \r\n WriteLog($\u0022Installer exit code: {exitCode}\u0022);\r\n if(exitCode!=0)\r\n {\r\n var installerLog = Path.Combine(Globals.ThisService.RootDirectory, \u0022.tca\u0022, \u0022console.log\u0022);\r\n if(File.Exists(installerLog))\r\n {\r\n var newLog = Path.Combine(Globals.ThisService.RootDirectory, Path.GetFileNameWithoutExtension(installerFilename) \u002B \u0022.log\u0022);\r\n await Task.Delay(3000);\r\n File.Move(installerLog, newLog, true);\r\n }\r\n throw new Exception($\u0022Installer failed with exit code {exitCode}. Check logs for details.\u0022);\r\n }\r\n \r\n WriteLog(\u0022Modpack installed\u0022);\r\n }\r\n\r\n public async Task SetCommandLine()\r\n { \r\n WriteLog(\u0022Configuring commandline\u0022);\r\n\r\n // Delete previous command line\r\n var prevCmdLineKey=$\u0022{_provider.InstallPrefix}:CustomModsCmd\u0022;\r\n if(Globals.ThisService.Variables.TryGetValue\u003Clong?\u003E(prevCmdLineKey, out var prevCmdLineId))\r\n {\r\n WriteLog(\u0022Deleting previous commandline\u0022);\r\n await Globals.ServiceManager.DeleteCustomCommandLine(Globals.ThisService.ServiceId, prevCmdLineId.Value);\r\n }\r\n\r\n // Find modloader jar in startup script\r\n var pattern = new Regex(@\u0022((-jar .*jar|@libraries/net/.*.txt|@libraries\\\\net\\\\.*.txt))\u0022);\r\n var neoforgePattern = new Regex(@\u0022^\\s*(?:set\\s\u002B)?NEOFORGE_VERSION\\s*=\\s*([0-9.]\u002B)\u0022);\r\n var neoForgeVersion = \u0022\u0022;\r\n var extension = Globals.ThisServer.OperatingSystem == EOperatingSystem.Windows ? \u0022bat\u0022 : \u0022sh\u0022;\r\n var startupScript = Path.Combine(Globals.ThisService.RootDirectory, $\u0022start.{extension}\u0022);\r\n \r\n if (!File.Exists(startupScript))\r\n startupScript = Path.Combine(Globals.ThisService.RootDirectory, $\u0022startserver.{extension}\u0022);\r\n \r\n if (!File.Exists(startupScript))\r\n startupScript = Path.Combine(Globals.ThisService.RootDirectory, $\u0022run.{extension}\u0022);\r\n\r\n string modloaderJar = null;\r\n foreach (var line in File.ReadLines(startupScript))\r\n {\r\n if (string.IsNullOrEmpty(neoForgeVersion))\r\n {\r\n var versionMatch = neoforgePattern.Match(line);\r\n if (versionMatch.Success)\r\n neoForgeVersion = versionMatch.Groups[1].Value;\r\n }\r\n var match = pattern.Match(line);\r\n if (match.Success)\r\n {\r\n modloaderJar = match.Groups[1].Value;\r\n break;\r\n }\r\n }\r\n\r\n var variables = new TCAdminVars() {\r\n {_provider.JarVariableName, modloaderJar}\r\n };\r\n\r\n var jarVar = $\u0022${{{_provider.JarVariableName}}}\u0022;\r\n var cmdLineId = await Globals.ServiceManager.UpdateCustomCommandLine(null, Globals.ThisService.ServiceId, 0, Globals.ThisTaskStep.DisplayName, variables);\r\n var cmdLine = await Globals.ServiceManager.GetCustomCommandLine(Globals.ThisService.ServiceId, cmdLineId);\r\n cmdLine.Name=Globals.ThisTaskStep.DisplayName;\r\n cmdLine.OverrideDefault=true;\r\n \r\n if (!modloaderJar.Contains(\u0022@\u0022))\r\n {\r\n cmdLine.CommandLine = cmdLine.CommandLine.Replace(\u0022-jar\u0022, string.Empty);\r\n }\r\n else\r\n {\r\n cmdLine.CommandLine = cmdLine.CommandLine.Replace($\u0022-jar {jarVar}\u0022, modloaderJar);\r\n }\r\n\r\n cmdLine.CommandLine=cmdLine.CommandLine.Replace(\u0022%NEOFORGE_VERSION%\u0022, neoForgeVersion);\r\n\r\n await Globals.ServiceManager.ApplyCustomCommandLine(null, Globals.ThisService.ServiceId, cmdLine.CmdLineId, false);\r\n // GameServiceManager \u2B06\uFE0F updates ThisGameService.Variables so we have to reload it for ServiceManager\r\n Globals.ThisService.Variables = Globals.ServiceManager.TCAdminDbContext.Services\r\n .Where(x =\u003E x.ServiceId == Globals.ThisService.ServiceId)\r\n .Select(x =\u003E x.Variables)\r\n .FirstOrDefault();\r\n\r\n Globals.ThisService.Variables[prevCmdLineKey] = cmdLine.CmdLineId;\r\n Globals.ThisService.Variables[$\u0022{_provider.InstallPrefix}:Type\u0022] = \u0022FTB\u0022;\r\n await Globals.GameServiceManager.UpdateAsync(Globals.ThisService, true, default);\r\n }\r\n\r\n public string GetLevelName(string propertiesFile)\r\n {\r\n if (!File.Exists(propertiesFile)) return null;\r\n \r\n foreach (var line in File.ReadLines(propertiesFile))\r\n {\r\n if (line.StartsWith(\u0022level-name=\u0022))\r\n {\r\n var levelName = line.Split(\u0027=\u0027, 2)[1].Trim();\r\n WriteLog($\u0022Level name detected as {levelName}\u0022);\r\n return levelName;\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n public void DeleteWorldFolders(string levelName)\r\n {\r\n if (string.IsNullOrEmpty(levelName))\r\n {\r\n WriteLog(\u0022Level name is blank. Cannot delete world folders\u0022);\r\n return;\r\n }\r\n \r\n WriteLog(\u0022Deleting world folders\u0022);\r\n var basePath = Path.Combine(Globals.ThisService.RootDirectory, levelName);\r\n var folders = new List\u003Cstring\u003E\r\n {\r\n basePath,\r\n basePath \u002B \u0022_the_end\u0022,\r\n basePath \u002B \u0022_nether\u0022\r\n };\r\n \r\n foreach (var folder in folders)\r\n {\r\n if (Directory.Exists(folder))\r\n {\r\n Directory.Delete(folder, true);\r\n WriteLog($\u0022Deleted folder and its contents: {folder}\u0022);\r\n }\r\n else\r\n {\r\n WriteLog($\u0022Folder does not exist: {folder}\u0022);\r\n }\r\n }\r\n }\r\n}\r\n\r\nclass ManifestRoot\r\n{\r\n public List\u003CTarget\u003E targets { get; set; }\r\n}\r\n\r\nclass Target\r\n{\r\n public string version { get; set; }\r\n public int id { get; set; }\r\n public string name { get; set; }\r\n public string type { get; set; }\r\n public long updated { get; set; }\r\n}", + "runImpersonated": false, + "stopService": false, + "ignoreErrors": false, + "order": 0, + "icon": " \u003Csvg xmlns=\u0022http://www.w3.org/2000/svg\u0022 fill=\u0022none\u0022 viewBox=\u00220 0 487 300\u0022\u003E\u003Cpath fill=\u0022#fff\u0022 d=\u0022M48.602 0 0 49.167V271.83l15.259 27.692H89.29l16.389-27.692v-69.512h40.69l16.389-28.256v-58.774h14.693V271.83l14.694 27.692h74.598l16.954-28.822V115.288h6.216v118.678l11.303 25.996 39.56 39.56h129.415l16.389-27.692V148.065l-12.433-8.477 4.521-9.042v-83.64L428.938 0H48.602Z\u0022/\u003E\u003Cpath fill=\u0022#322A2A\u0022 d=\u0022m463.271 141.829 5.685-13.238v-78.04L424.423 8.786H54.007l-43.38 43.63v216.221l12.71 23.016h63.01l12.709-23.016V193.29h43.564l12.71-23.017v-63.579h31.103v161.943l12.709 23.016h63.298l12.709-23.016V106.694h23.543v125.253l9.331 22.416 37.098 37.29h119.675l12.709-23.016V152.105l-14.234-10.276Z\u0022/\u003E\u003Cpath fill=\u0022#65ADB9\u0022 d=\u0022m78.647 180.157 7.333 85.084-7.333 13.279H31.045l-7.333-13.279 7.333-183.675 31.432-31.614h77.161l7.333 30.309-7.333 13.28h-60.99v43.877h56.273l7.333 29.46-7.333 13.279H78.647Z\u0022/\u003E\u003Cpath fill=\u0022#9FC41E\u0022 d=\u0022M297.661 93.56h-42.906l7.333 171.7-7.333 13.28h-47.89l-7.333-13.28 7.333-171.7h-44.492l-7.333-13.28 7.333-30.309h135.288l7.333 30.309-7.333 13.28Z\u0022/\u003E\u003Cpath fill=\u0022#E65014\u0022 d=\u0022m457.095 183.303 7.333 81.956-7.333 13.28H350.521l-31.433-31.614-7.333-17.589 7.333-179.345h97.285l32.174 30.163 7.333 45.764-7.333 17.112-22.328 18.003 30.876 22.291v-.021Zm-55.594-42.718V93.558h-34.79v47.027h34.79Zm7.704 94.635v-51.896h-42.473v51.896h42.473Z\u0022/\u003E\u003Cpath fill=\u0022#0787C1\u0022 d=\u0022M23.707 57.858v207.375h62.268V166.87h56.274v-57.468H85.975V80.253h60.991V21.935H59.424L23.707 57.858Z\u0022/\u003E\u003Cpath fill=\u0022#798B2F\u0022 d=\u0022M155.021 80.253h44.492v184.98h62.556V80.253h42.927V21.935H155.021v58.318Z\u0022/\u003E\u003Cpath fill=\u0022#B11917\u0022 d=\u0022m438.345 140 17.529-14.149V56.263l-36.603-34.328H311.77V229.29l35.737 35.923h116.936V158.832l-26.077-18.852-.021.02Zm-64.307 67.164v-37.166h27.828v37.166h-27.828Zm0-94.634V80.253h20.124v32.277h-20.124Z\u0022/\u003E\u003Cpath fill=\u0022#fff\u0022 d=\u0022M78.647 159.511v98.363H31.045V60.919l31.432-31.614h77.161v43.589H78.647v43.878h56.274v42.718H78.647v.021ZM297.666 72.913H254.76v184.959h-47.891V72.912h-44.492V29.326h135.289v43.588ZM457.095 162.658v95.214H350.52l-31.432-31.613V29.325h97.285l32.174 30.163v62.855l-22.328 18.003 30.876 22.291v.021Zm-55.594-42.718V72.913h-34.79v47.027h34.79Zm7.704 94.634v-51.895h-42.474v51.895h42.474Z\u0022/\u003E\u003C/svg\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "887301d0-c078-4beb-9e21-188f6ef84cc2" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [ + "AfterCustomModInstalled" + ], + "name": "Install Modpack", + "code": "//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing TCAdmin.SDK.Database.Entities;\r\nusing TCAdmin.Web.Shared.Models.Enums;\r\n\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\n// Not a mod pack\r\nvar providerId = Globals.Variables.Parse\u003Cint\u003E(\u0022ProviderId\u0022);\r\nif(providerId != 3) return;\r\n\r\nvar modType = Globals.Variables.Parse\u003Cstring\u003E(\u0022ModType\u0022);\r\n\r\nvar script = Globals.ThisGame.Scripts.SingleOrDefault(x=\u003E x.Code.IndexOf($\u0022{modType.ToUpper()}-MODPACK-INSTALL\u0022) !=-1);\r\n\r\nif(script== null) throw new Exception($\u0022Could not find install script for mod type {modType}\u0022);\r\n\r\ntry{\r\n await Globals.ScriptingEnvironment.ExecuteScript(Globals.ThisService, script, null, Globals.ThisCancellationToken);\r\n}\r\nfinally{\r\n // Set file permissions\r\n if (Globals.ThisServer.OperatingSystem == EOperatingSystem.Linux)\r\n {\r\n var owner = Globals.OperatingSystem.GetOwner(Globals.ThisService.RootDirectory);\r\n Globals.OperatingSystem.SetDirectoryOwner(Globals.ThisService.RootDirectory, string.Empty, owner, true);\r\n }\r\n}", + "runImpersonated": false, + "stopService": true, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cg stroke-width=\u00220\u0022\u003E\u003C/g\u003E\u003Cg stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022\u003E\u003C/g\u003E\u003Cg\u003E \u003Cg id=\u0022Continuous-Integration-Filled\u0022\u003E \u003Cpath d=\u0022M22.91,6.66v11.88L13,23.5V11.62L22.91,6.66z M12,9.88l9.88-4.94L12,0L2.12,4.94L12,9.88z M11,11.62L1.09,6.66v11.88 L11,23.5V11.62z\u0022\u003E\u003C/path\u003E \u003C/g\u003E \u003C/g\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "d4355da9-6efe-4119-b653-6e17b4b52c57" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Linux", + "Windows" + ], + "scriptEvents": [ + "DownloadGameFiles" + ], + "name": "Download game files", + "code": "//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll, System.IO.Compression.dll, System.Formats.Tar.dll\r\nusing System.Formats.Tar;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nusing System.IO;\r\nusing System.IO.Compression;\r\nusing System.Net.Http;\r\nusing TCAdmin.SDK.Misc.Extensions;\r\n\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\nasync Task Download(string url, string downloadFileName, CancellationToken cancellationToken)\r\n{\r\n using(HttpClient downloadClient = new HttpClient())\r\n {\r\n await downloadClient.DownloadFile(url, downloadFileName, cancellationToken);\r\n }\r\n}\r\n\r\n// download minecraft_server.jar\r\nvar saveFolder = Path.Combine(Globals.ThisServer.Configuration.GameFilesPath, Globals.Globals.ThisGame.FileAndDirectoryConfig.AutoSetupFolderName);\r\nvar filePath = Path.Combine(saveFolder, \u0022minecraft_server.jar\u0022);\r\nif(!Directory.Exists(saveFolder)) Directory.CreateDirectory(saveFolder);\r\nawait Download(\u0022http://downloads.tcadmin.net/games/minecraft.aspx\u0022, filePath, Globals.ThisCancellationToken);\r\nGlobals.ScriptConsole.WriteLine(\u0022Download of minecraft_server.jar completed successfully!\u0022);\r\n", + "runImpersonated": false, + "stopService": false, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cpath d=\u0022M0 0h24v24H0z\u0022 fill=\u0022none\u0022/\u003E\u003Cpath d=\u0022M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z\u0022/\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "314c7e75-9078-4292-b00a-3268721c878c" + } + } + ], + "customModProviderConfigs": [ + { + "providerId": 1, + "gameId": 3, + "enabled": false, + "configuration": { + "SingleIcon": false + } + }, + { + "providerId": 2, + "gameId": 3, + "enabled": false, + "configuration": {} + }, + { + "providerId": 3, + "gameId": 3, + "enabled": true, + "configuration": { + "Private.JarVariable": "Jar" + } + }, + { + "providerId": 4, + "gameId": 3, + "enabled": true, + "configuration": { + "ModsPath": "mods", + "Private.CurseGameId": 432 + } + } + ], + "gamesCustomLinks": [], + "updates": [], + "createdDate": "2026-01-14T20:59:33.248974", + "modifiedDate": "2026-06-17T21:41:35.087855", + "metadata": { + "Notes": "", + "notes": "Game imported from plugin TCA.Games.MC version 1.0.4.Game imported from plugin TCA.Games.MC version 1.0.6.Game imported from plugin TCA.Games.MC version 1.0.8.Game imported from plugin TCA.Games.MC version 1.0.10.Game imported from plugin TCA.Games.MC version 1.0.10.Game imported from plugin TCA.Games.MC version 1.0.11.Game imported from plugin TCA.Games.MC version 1.0.13.", + "PluginVersion": "1.0.13", + "PluginIdentifier": "TCA.Games.MC" + }, + "backupConfig": { + "enabled": true, + "backupType": "Incremental", + "compressBackup": true, + "includedPaths": [ + "server.properties", + "eula.txt", + "world/**/", + "world_nether/**/", + "world_the_end/**/", + "plugins/**/*.yml", + "plugins/**/*.json", + "plugins/**/*.db", + "ops.json", + "whitelist.json", + "banned-players.json", + "banned-ips.json", + "bukkit.yml", + "spigot.yml", + "paper-global.yml", + "paper-world-defaults.yml", + "permissions.yml", + "usercache.json" + ], + "maxBackupCount": 5 + }, + "rolePermissions": [ + { + "roleId": 2, + "module": "BpFeature", + "permission": "Control", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "ConfigFiles", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "PredefCmdlines", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "CustomCmdlines", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "FileManager", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "FTP", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "ScheduledTasks", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Console", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Logs", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "ServiceActivity", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "CpuStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "MemoryStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "NetworkStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "LiveStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Reinstall", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Mods", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "PlayerStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "ServiceSettings", + "moduleData": "", + "granted": false + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-WebRequest", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-SendCommand", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-FtpUpload", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-Extract", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-DiscordWebhook", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-DeleteFolder", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-DeleteFile", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-CreateFile", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-Compress", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "CustomScripts", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Backups", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "KillService", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-ServiceControl", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "ServiceActivity", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "ScheduledTasks", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Reinstall", + "moduleData": "", + "granted": false + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "PredefCmdlines", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "PlayerStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "NetworkStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Mods", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "MemoryStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Logs", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "LiveStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "FTP", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "FileManager", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "CustomCmdlines", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "CpuStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Control", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Console", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "ConfigFiles", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "ServiceSettings", + "moduleData": "", + "granted": false + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-WebRequest", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-SendCommand", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-FtpUpload", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-Extract", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-DiscordWebhook", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-DeleteFolder", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-DeleteFile", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-CreateFile", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-Compress", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "CustomScripts", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Backups", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "KillService", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-ServiceControl", + "moduleData": "", + "granted": true + } + ], + "mods": [], + "queryMonitoringConfig": { + "enabled": false, + "checkInterval": "00:05:00", + "startupGracePeriod": "00:10:00", + "retryDelay": "00:00:30", + "failureThreshold": 3, + "failureAction": "Restart", + "maxFailureAction": "Disable", + "logActivityOnFailure": true, + "slotDetectionEnabled": false, + "slotDetectionAction": "None", + "additionalAllowedSlots": 0, + "privateDetectionEnabled": false, + "privateDetectionAction": "None", + "brandDetectionEnabled": false, + "brandDetectionAction": "None", + "brandedText": "", + "brandedTextAtEnd": true, + "brandedTextAddSpace": true, + "brandRegex": "", + "brandRegexCaseInsensitive": true, + "rules": [] + }, + "fastDLConfig": { + "enabled": false, + "relativeRoot": "", + "urlExpression": "", + "includePatterns": [], + "excludePatterns": [], + "syncOnServiceCreate": false, + "autoSyncOnFileChange": false, + "compression": "None", + "requiresHttps": false, + "stripPaths": [] + }, + "environmentVariables": [] +} \ No newline at end of file diff --git a/manifests/games/Minecraft/1.0.14/Minecraft - Windows.json b/manifests/games/Minecraft/1.0.14/Minecraft - Windows.json new file mode 100644 index 0000000..b5872cc --- /dev/null +++ b/manifests/games/Minecraft/1.0.14/Minecraft - Windows.json @@ -0,0 +1,4098 @@ +{ + "__TCA:ExportVersion": "3.10.27.45532", + "__TCA:ExportedAt": "2026-06-17T22:59:24.4774595Z", + "name": "Minecraft", + "shortName": "Minecraft", + "description": "", + "operatingSystem": "Windows", + "iconImage": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Logo_Minecraft.png/960px-Logo_Minecraft.png", + "backgroundImage": "https://img.primaservers.dk/minecraft/index.php", + "minSlots": 1, + "maxSlots": 128, + "defaultSlots": 10, + "startedUntilResponding": true, + "startingTimeout": "00:03:00", + "defaultDiskSpace": 10737418240, + "categoryId": 1, + "editableExtensions": [ + ".txt", + ".cfg", + ".json", + ".xml" + ], + "logExtensions": [ + ".log" + ], + "queryRconProtocolConfig": { + "queryProtocol": "minecraft", + "rconProtocol": "", + "privateRule": "", + "privateRuleValue": "", + "hiddenRules": [] + }, + "consoleConfig": { + "enabled": true, + "outputSource": "Terminal", + "logFile": "logs/console.log", + "inputSource": "ConsoleCommands", + "printRconResponse": false, + "outputFilters": "", + "stopCommand": "stop", + "waitAfterStopCommand": 10000, + "screenCaptureTitleBar": true, + "screenCaptureQuality": 25, + "screenCaptureFPS": 10, + "screenCaptureAllowMouse": false, + "screenCaptureAllowKeys": true, + "screenCaptureMouseHardware": true + }, + "ipPortAllocationConfig": { + "usePrimaryIpOnly": false, + "useDefaultPortsOnly": false, + "supportsIpv6": false, + "uniquePort": "ServerIp", + "customPorts": [ + { + "id": "GamePort", + "port": 25565, + "expression": "" + }, + { + "id": "RConPort", + "port": 25575, + "expression": "" + }, + { + "id": "QueryPort", + "port": 25565, + "expression": "" + } + ], + "portIncrement": 10 + }, + "fileAndDirectoryConfig": { + "relativeExecutable": "minecraft_server.bat", + "autoSetupFolderName": "minecraft", + "failOnMissingExecutable": false, + "externalDownloadEnable": false + }, + "steamConfig": { + "steamUpdate": false, + "appId": 0, + "runAsServiceUser": false, + "branch": "public", + "storeId": 0, + "steamUsername": "anonymous", + "steamPassword": "", + "steamDownloadRetries": 5, + "updateAfterCreateOrReinstall": false, + "verifyAll": false, + "steamTool": "DepotDownloader" + }, + "gameCommandlineConfig": { + "privateCommandline": "-Xmx${Xmx}M -Xms${Xms}M -XX:\u002BUseG1GC -XX:\u002BParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:\u002BUnlockExperimentalVMOptions -XX:\u002BDisableExplicitGC -XX:\u002BAlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:\u002BPerfDisableSharedMem -XX:MaxTenuringThreshold=1 -jar ${Jar} nogui" + }, + "commandlineConfig": { + "enableSelection": false, + "defaultCommandline": "-Xmx${Xmx}M -Xms${Xms}M -XX:\u002BUseG1GC -XX:\u002BParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:\u002BUnlockExperimentalVMOptions -XX:\u002BDisableExplicitGC -XX:\u002BAlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:\u002BPerfDisableSharedMem -XX:MaxTenuringThreshold=1 -jar ${Jar} nogui", + "defaultCustomCommandline": "", + "predefinedCommandlines": [ + { + "id": 1, + "name": "Default", + "commandLine": "-Xmx${Xmx}M -Xms${Xms}M -XX:\u002BUseG1GC -XX:\u002BParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:\u002BUnlockExperimentalVMOptions -XX:\u002BDisableExplicitGC -XX:\u002BAlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:\u002BPerfDisableSharedMem -XX:MaxTenuringThreshold=1 -jar ${Jar} nogui" + } + ] + }, + "runAsConfig": { + "runAs": "TCAGame", + "interactWithDesktop": false, + "elevated": false + }, + "gameVariables": [ + { + "name": "JavaVersion", + "defaultValue": "Automatic", + "required": true, + "requiredMessage": "The java version is required", + "scriptParameter": true, + "commandlineParameter": false, + "saveScriptParameter": true, + "syncCommandlineParameter": false, + "template": "${JavaVersion}", + "editor": { + "items": [ + { + "text": "", + "value": "Automatic" + }, + { + "text": "", + "value": "Java 8" + }, + { + "value": "Java 11" + }, + { + "value": "Java 16" + }, + { + "value": "Java 17" + }, + { + "text": "", + "value": "Java 21" + }, + { + "value": "Java 25" + } + ], + "id": "JavaVersion", + "defaultValue": "Automatic", + "label": "Select the Java version to use", + "description": "This value is required", + "required": true, + "valueType": "string", + "requiredMessage": "The java version is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-07-12T02:29:13.214", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "Xmx", + "defaultValue": "2048", + "required": true, + "requiredMessage": "A value for Xmx is required", + "scriptParameter": false, + "commandlineParameter": true, + "saveScriptParameter": false, + "syncCommandlineParameter": true, + "template": "${Xmx}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 128, + "id": "Xmx", + "defaultValue": 1024, + "label": "Xmx", + "description": "Max memory allocated by Java in megabytes", + "required": true, + "requiredMessage": "A value for Xmx is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:21:16.005", + "metadata": { + "notes": "" + }, + "variableRoles": [] + }, + { + "name": "Xms", + "defaultValue": "128", + "required": true, + "requiredMessage": "A value for Xms is required", + "scriptParameter": false, + "commandlineParameter": true, + "saveScriptParameter": false, + "syncCommandlineParameter": true, + "template": "${Xms}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "id": "Xms", + "defaultValue": "128", + "label": "Xms", + "description": "Min memory allocated by Java in megabytes", + "required": true, + "requiredMessage": "A value for Xms is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:23:14.911", + "metadata": { + "notes": "" + }, + "variableRoles": [] + }, + { + "name": "AllowFlight", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${AllowFlight}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "AllowFlight", + "defaultValue": "false", + "label": "Allow Flight", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:44:59.965", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "AllowNether", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${AllowNether}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "AllowNether", + "defaultValue": "true", + "label": "Allow Nether", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:46:37.852", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "BroadcastConsoleToOps", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${BroadcastConsoleToOps}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "BroadcastConsoleToOps", + "defaultValue": "true", + "label": "Broadcast Console To Ops", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:47:19.034", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "BroadcastRconToOps", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${BroadcastRconToOps}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "BroadcastRconToOps", + "defaultValue": "true", + "label": "Broadcast Rcon To Ops", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:48:30.508", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "Difficulty", + "defaultValue": "easy", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${Difficulty}", + "editor": { + "items": [ + { + "text": "peaceful" + }, + { + "text": "easy" + }, + { + "text": "normal" + }, + { + "text": "hard" + } + ], + "id": "Difficulty", + "defaultValue": "easy", + "label": "Difficulty", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:49:42.518", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EnableCommandBlock", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EnableCommandBlock}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableCommandBlock", + "defaultValue": "false", + "label": "Enable Command Block", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:50:16.927", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EnableJMXMonitoring", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EnableJMXMonitoring}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableJMXMonitoring", + "defaultValue": "false", + "label": "Enable JMX Monitoring", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:50:46.921", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EnableRcon", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EnableRcon}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableRcon", + "defaultValue": "false", + "label": "Enable Rcon", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:52:29.229", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EnableStatus", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EnableStatus}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableStatus", + "defaultValue": "true", + "label": "Enable Status", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:53:19.974", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EnforceWhiteList", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EnforceWhiteList}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnforceWhiteList", + "defaultValue": "false", + "label": "Enforce WhiteList", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:53:40.708", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EntityBroadcastRangePercentage", + "defaultValue": "100", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EntityBroadcastRangePercentage}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "maxValue": 100, + "id": "EntityBroadcastRangePercentage", + "defaultValue": "100", + "label": "Entity Broadcast Range Percentage", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:54:40.903", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "SpawnProtection", + "defaultValue": "16", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${SpawnProtection}", + "editor": { + "items": [ + { + "text": "Disabled", + "value": "0" + }, + { + "text": "3x3 area around the spawn point", + "value": "1" + }, + { + "text": "5x5 area around the spawn point", + "value": "2" + }, + { + "text": "7x7 area around the spawn point", + "value": "3" + }, + { + "text": "33x33 area around the spawn point", + "value": "16" + } + ], + "id": "SpawnProtection", + "defaultValue": "16", + "label": "Spawn Protection", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:55:56.796", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "MaxTickTime", + "defaultValue": "60000", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${MaxTickTime}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "MaxTickTime", + "defaultValue": "60000", + "label": "Max Tick Time", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T05:59:36.963", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "GeneratorSettings", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${GeneratorSettings}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "GeneratorSettings", + "label": "Generator Settings", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:02:07.457", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "SyncChunkWrites", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${SyncChunkWrites}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SyncChunkWrites", + "label": "Sync Chunk Writes", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:03:21.227", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "ForceGamemode", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${ForceGamemode}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "ForceGamemode", + "label": "Force Game mode", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:03:57.103", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "Gamemode", + "defaultValue": "0", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${Gamemode}", + "editor": { + "items": [ + { + "text": "Survival", + "value": "0" + }, + { + "text": "Creative", + "value": "1" + }, + { + "text": "Adventure", + "value": "2" + }, + { + "text": "Spectator", + "value": "3" + } + ], + "id": "Gamemode", + "defaultValue": "0", + "label": "Game Mode", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:04:32.185", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "PlayerIdleTimeout", + "defaultValue": "0", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${PlayerIdleTimeout}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "id": "PlayerIdleTimeout", + "defaultValue": "0", + "label": "Player Idle Timeout", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:09:12.013", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "TextFilteringConfig", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${TextFilteringConfig}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "TextFilteringConfig", + "label": "Text Filtering Config", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:10:23.327", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "SpawnMonsters", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${SpawnMonsters}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SpawnMonsters", + "defaultValue": "true", + "label": "Spawn Monsters", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:10:55.09", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "OpPermissionLevel", + "defaultValue": "4", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${OpPermissionLevel}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "minValue": 1, + "maxValue": 4, + "id": "OpPermissionLevel", + "defaultValue": "4", + "label": "Op Permission Level", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:11:29.749", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "PVP", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${PVP}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "PVP", + "label": "PVP", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:14:04.215", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "LevelType", + "defaultValue": "minecraft:normal", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${LevelType}", + "editor": { + "items": [ + { + "text": "minecraft:normal" + }, + { + "text": "minecraft:flat" + }, + { + "text": "minecraft:large_biomes" + }, + { + "text": "minecraft:amplified" + }, + { + "text": "minecraft:single_biome_surface" + } + ], + "id": "LevelType", + "defaultValue": "minecraft:normal", + "label": "Level Type", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:14:41.27", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "ResourcePackPrompt", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${ResourcePackPrompt}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "ResourcePackPrompt", + "label": "Resource Pack Prompt", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:15:05.743", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "Hardcore", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${Hardcore}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "Hardcore", + "defaultValue": "false", + "label": "Hardcore", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:15:47.202", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "NetworkCompressionThreshold", + "defaultValue": "256", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${NetworkCompressionThreshold}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "minValue": -1, + "maxValue": 512, + "id": "NetworkCompressionThreshold", + "defaultValue": "256", + "label": "Network Compression Threshold", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:16:28.751", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "MaxWorldSize", + "defaultValue": "29999984", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${MaxWorldSize}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "id": "MaxWorldSize", + "defaultValue": "29999984", + "label": "Max World Size", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:18:01.866", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "ResourcePackSHA1", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${ResourcePackSHA1}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "ResourcePackSHA1", + "label": "Resource Pack SHA1", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:18:41.876", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "FunctionPermissionLevel", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${FunctionPermissionLevel}", + "editor": { + "items": [ + { + "text": "All", + "value": "0" + }, + { + "text": "Moderator", + "value": "2" + }, + { + "text": "Admin", + "value": "3" + }, + { + "text": "Owner", + "value": "4" + } + ], + "id": "FunctionPermissionLevel", + "label": "Function Permission Level", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:19:35.944", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "SpawnNPCs", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${SpawnNPCs}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SpawnNPCs", + "defaultValue": "true", + "label": "Spawn NPCs", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:21:32.915", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "RequireResourcePack", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${RequireResourcePack}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "RequireResourcePack", + "defaultValue": "false", + "label": "Require Resource Pack", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:22:08.535", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "LevelName", + "defaultValue": "world", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${LevelName}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "LevelName", + "defaultValue": "world", + "label": "Level Name", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:22:53.826", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "ViewDistance", + "defaultValue": "10", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${ViewDistance}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "minValue": 1, + "maxValue": 32, + "id": "ViewDistance", + "defaultValue": "10", + "label": "View Distance", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:23:20.377", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "ResourcePack", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${ResourcePack}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "ResourcePack", + "label": "Resource Pack URL", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:24:29.712", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "SpawnAnimals", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${SpawnAnimals}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SpawnAnimals", + "defaultValue": "true", + "label": "Spawn Animals", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:25:03.163", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "WhiteList", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${WhiteList}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "WhiteList", + "defaultValue": "false", + "label": "White List", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:25:29.473", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "GenerateStructures", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${GenerateStructures}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "GenerateStructures", + "defaultValue": "true", + "label": "Generate Structures", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:26:37.263", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "OnlineMode", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${OnlineMode}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "OnlineMode", + "defaultValue": "true", + "label": "Online Mode", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:27:18.719", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "LevelSeed", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${LevelSeed}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "id": "LevelSeed", + "label": "Level Seed", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:27:45.9", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "PreventProxyConnections", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${PreventProxyConnections}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "PreventProxyConnections", + "defaultValue": "false", + "label": "Prevent Proxy Connections", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:28:33.688", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "UseNativeTransport", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${UseNativeTransport}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "UseNativeTransport", + "defaultValue": "true", + "label": "Use Native Transport", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:29:09.022", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "MOTD", + "defaultValue": "A Minecraft Server ", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${MOTD}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "MOTD", + "defaultValue": "A Minecraft Server ", + "label": "Message Of The Day", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:29:53.054", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "RateLimit", + "defaultValue": "0", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${RateLimit}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "RateLimit", + "defaultValue": "0", + "label": "Rate Limit", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:34:00.818", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "EnforceSecureProfile", + "defaultValue": "true", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${EnforceSecureProfile}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnforceSecureProfile", + "defaultValue": "true", + "label": "Enforce Secure Profile", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:35:26.511", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "MaxChainedNeighborUpdates", + "defaultValue": "1000000", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${MaxChainedNeighborUpdates}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "id": "MaxChainedNeighborUpdates", + "defaultValue": "1000000", + "label": "Max Chained Neighbor Updates", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:36:05.199", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "HideOnlinePlayers", + "defaultValue": "false", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${HideOnlinePlayers}", + "editor": { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "HideOnlinePlayers", + "defaultValue": "false", + "label": "Hide Online Players", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:37:05.86", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "SimulationDistance", + "defaultValue": "10", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": false, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${SimulationDistance}", + "editor": { + "decimals": 0, + "step": 1, + "spinner": true, + "valueType": "double", + "minValue": 1, + "maxValue": 16, + "id": "SimulationDistance", + "defaultValue": "10", + "label": "Simulation Distance", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-01-07T06:37:50.322", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + { + "name": "Jar", + "defaultValue": "minecraft_server.jar", + "required": false, + "requiredMessage": "Value is required", + "scriptParameter": false, + "commandlineParameter": true, + "saveScriptParameter": false, + "syncCommandlineParameter": false, + "template": "${Jar}", + "editor": { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "Jar", + "defaultValue": "minecraft_server.jar", + "label": "Jar", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-06-26T00:58:37.667", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + } + ], + "configFiles": [ + { + "id": 3, + "relativePath": "eula.txt", + "description": "End user license agreement", + "template": "eula=true", + "order": 2, + "enableEditor": false, + "editor": [] + }, + { + "id": 4, + "relativePath": "server.properties", + "description": "Main configuration file", + "template": "server-name=${Hostname}\r\nallow-flight=${AllowFlight}\r\nallow-nether=${AllowNether}\r\nbroadcast-console-to-ops=${BroadcastConsoleToOps}\r\nbroadcast-rcon-to-ops=${BroadcastRconToOps}\r\ndifficulty=${Difficulty}\r\nenable-command-block=${EnableCommandBlock}\r\nenable-jmx-monitoring=${EnableJMXMonitoring}\r\nenable-query=true\r\nenable-rcon=${EnableRcon}\r\nenable-status=${EnableStatus}\r\nenforce-whitelist=${EnforceWhiteList}\r\nentity-broadcast-range-percentage=${EntityBroadcastRangePercentage}\r\nspawn-protection=${SpawnProtection}\r\nmax-tick-time=${MaxTickTime}\r\nquery.port=${QueryPort}\r\ngenerator-settings=${GeneratorSettings}\r\nsync-chunk-writes=${SyncChunkWrites}\r\nforce-gamemode=${ForceGamemode}\r\ngamemode=${Gamemode}\r\nplayer-idle-timeout=${PlayerIdleTimeout}\r\ntext-filtering-config=${TextFilteringConfig}\r\nspawn-monsters=${SpawnMonsters}\r\nop-permission-level=${OpPermissionLevel}\r\npvp=${PVP}\r\nlevel-type=${LevelType}\r\nresource-pack-prompt=${ResourcePackPrompt}\r\nhardcore=${Hardcore}\r\nnetwork-compression-threshold=${NetworkCompressionThreshold}\r\nmax-players=${Slots}\r\nmax-world-size=${MaxWorldSize}\r\nresource-pack-sha1=${ResourcePackSHA1}\r\nfunction-permission-level=${FunctionPermissionLevel}\r\nrcon.port=${RConPort}\r\nserver-port=${GamePort}\r\nserver-ip=${IPAddress}\r\nspawn-npcs=${SpawnNPCs}\r\nrequire-resource-pack=${RequireResourcePack}\r\nlevel-name=${LevelName}\r\nview-distance=${ViewDistance}\r\nresource-pack=${ResourcePack}\r\nspawn-animals=${SpawnAnimals}\r\nwhite-list=${WhiteList}\r\nrcon.password=${RconPassword}\r\ngenerate-structures=${GenerateStructures}\r\nonline-mode=${OnlineMode}\r\nlevel-seed=${LevelSeed}\r\nprevent-proxy-connections=${PreventProxyConnections}\r\nuse-native-transport=${UseNativeTransport}\r\nmotd=${MOTD}\r\nrate-limit=${RateLimit}\r\nenforce-secure-profile=${EnforceSecureProfile}\r\nmax-chained-neighbor-updates=${MaxChainedNeighborUpdates}\r\nhide-online-players=${HideOnlinePlayers}\r\nsimulation-distance=${SimulationDistance}", + "order": 1, + "enableEditor": true, + "editor": [ + { + "id": "Header-27045", + "label": "General Settings", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicHeader", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "Hostname", + "label": "Hostname", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "MOTD", + "defaultValue": "A Minecraft Server ", + "label": "Message Of The Day", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "items": [ + { + "text": "Survival", + "value": "0" + }, + { + "text": "Creative", + "value": "1" + }, + { + "text": "Adventure", + "value": "2" + }, + { + "text": "Spectator", + "value": "3" + } + ], + "id": "Gamemode", + "defaultValue": "0", + "label": "Game Mode", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "ForceGamemode", + "label": "Force Game mode", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "Difficulty", + "defaultValue": "easy", + "label": "Difficulty", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "maxValue": 32, + "id": "ViewDistance", + "defaultValue": "10", + "label": "View Distance", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "PVP", + "label": "PVP", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "Hardcore", + "defaultValue": "false", + "label": "Hardcore", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "OnlineMode", + "defaultValue": "true", + "label": "Online Mode", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableStatus", + "defaultValue": "true", + "label": "Enable Status", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableRcon", + "defaultValue": "false", + "label": "Enable Rcon", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "RconPassword", + "label": "Rcon Password", + "description": "Remote console password.", + "required": false, + "valueType": "string", + "requiredMessage": "", + "controlType": "DynamicTextBox", + "sm": 4, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "id": "Header-40113", + "label": "World Settings", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicHeader", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "LevelName", + "defaultValue": "world", + "label": "Level Name", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "LevelSeed", + "label": "Level Seed", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "LevelType", + "defaultValue": "minecraft:normal", + "label": "Level Type", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "GenerateStructures", + "defaultValue": "true", + "label": "Generate Structures", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "GeneratorSettings", + "label": "Generator Settings", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "MaxWorldSize", + "defaultValue": "29999984", + "label": "Max World Size", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SpawnAnimals", + "defaultValue": "true", + "label": "Spawn Animals", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SpawnMonsters", + "defaultValue": "true", + "label": "Spawn Monsters", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SpawnNPCs", + "defaultValue": "true", + "label": "Spawn NPCs", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "AllowNether", + "defaultValue": "true", + "label": "Allow Nether", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "AllowFlight", + "defaultValue": "false", + "label": "Allow Flight", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "items": [ + { + "text": "Disabled", + "value": "0" + }, + { + "text": "3x3 area around the spawn point", + "value": "1" + }, + { + "text": "2", + "value": "5x5 area around the spawn point" + }, + { + "text": "3", + "value": "7x7 area around the spawn point" + }, + { + "text": "33x33 area around the spawn point", + "value": "16" + } + ], + "id": "SpawnProtection", + "defaultValue": "33", + "label": "Spawn Protection", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "id": "Header-86727", + "label": "Whitelist", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicHeader", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "WhiteList", + "defaultValue": "false", + "label": "White List", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnforceWhiteList", + "defaultValue": "false", + "label": "Enforce WhiteList", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "id": "Header-65045", + "label": "Resource Pack", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicHeader", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "RequireResourcePack", + "defaultValue": "false", + "label": "Require Resource Pack", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "ResourcePack", + "label": "Resource Pack", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "ResourcePackPrompt", + "label": "Resource Pack Prompt", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "id": "Header-79389", + "label": "Miscellaneous Settings", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicHeader", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "BroadcastConsoleToOps", + "defaultValue": "true", + "label": "Broadcast Console To Ops", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "BroadcastRconToOps", + "defaultValue": "true", + "label": "Broadcast Rcon To Ops", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableCommandBlock", + "defaultValue": "false", + "label": "Enable Command Block", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnableJMXMonitoring", + "defaultValue": "false", + "label": "Enable JMX Monitoring", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "SyncChunkWrites", + "label": "Sync Chunk Writes", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "maxValue": 100, + "id": "EntityBroadcastRangePercentage", + "defaultValue": "100", + "label": "Entity Broadcast Range Percentage", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "items": [ + { + "text": "All", + "value": "0" + }, + { + "text": "Moderator", + "value": "2" + }, + { + "text": "Admin", + "value": "3" + }, + { + "text": "Owner", + "value": "4" + } + ], + "id": "FunctionPermissionLevel", + "label": "Function Permission Level", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicComboBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "MaxTickTime", + "defaultValue": "60000", + "label": "Max Tick Time", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "EnforceSecureProfile", + "defaultValue": "true", + "label": "Enforce Secure Profile", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "HideOnlinePlayers", + "defaultValue": "false", + "label": "Hide Online Players", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "PlayerIdleTimeout", + "defaultValue": "0", + "label": "Player Idle Timeout", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "maxLength": 0, + "lines": 1, + "denyCharacters": [], + "regExValidation": "", + "inputType": 0, + "id": "TextFilteringConfig", + "label": "Text Filtering Config", + "description": "", + "required": false, + "valueType": "string", + "requiredMessage": "Value is required", + "controlType": "DynamicTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "maxValue": 4, + "id": "OpPermissionLevel", + "defaultValue": "4", + "label": "Op Permission Level", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": -1, + "maxValue": 512, + "id": "NetworkCompressionThreshold", + "defaultValue": "256", + "label": "Network Compression Threshold", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "PreventProxyConnections", + "defaultValue": "false", + "label": "Prevent Proxy Connections", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "valueType": "bool", + "checkedValue": "true", + "uncheckedValue": "false", + "id": "UseNativeTransport", + "defaultValue": "true", + "label": "Use Native Transport", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicCheckBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "RateLimit", + "defaultValue": "0", + "label": "Rate Limit", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "id": "MaxChainedNeighborUpdates", + "defaultValue": "1000000", + "label": "Max Chained Neighbor Updates", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "maxValue": 16, + "id": "SimulationDistance", + "defaultValue": "10", + "label": "Simulation Distance", + "description": "", + "required": false, + "requiredMessage": "Value is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + } + ] + } + ], + "fileSystemPermissions": [ + { + "roleId": 2, + "rootPermission": { + "permissionMode": "Basic", + "defaultPermissions": "Write, Read, Delete", + "skipListingSecurityCheck": false, + "additionalPermissions": [] + } + }, + { + "roleId": 3, + "rootPermission": { + "permissionMode": "Basic", + "defaultPermissions": "Write, Read, Delete", + "skipListingSecurityCheck": false, + "additionalPermissions": [ + { + "pathOrFilters": "Modpack-*.data", + "permissionType": "File", + "permissionMode": "None", + "permissions": "None" + }, + { + "pathOrFilters": "java", + "permissionType": "FilePath", + "permissionMode": "None", + "permissions": "None" + } + ] + } + } + ], + "gamesScripts": [ + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "IronPython3", + "operatingSystem": [ + "Windows" + ], + "scriptEvents": [ + "CustomServiceAction", + "ScheduledTask", + "AfterServiceCreated", + "AfterServiceReinstalled" + ], + "name": "Download/Update Java", + "description": "Downloads and installs Java binaries from Adoptium", + "code": "# UPDATE-JAVA\r\n# This script is also run on After Created and After Reinstall. If you need to make any changes, make them in this script.\r\nimport clr\r\nclr.AddReference(\u0027System.IO\u0027)\r\nclr.AddReference(\u0027System.IO.Compression.ZipFile\u0027)\r\nclr.AddReference(\u0027System.IO.Compression\u0027)\r\nclr.AddReference(\u0022System\u0022)\r\nclr.AddReference(\u0022TCAdmin.SDK\u0022)\r\nclr.AddReference(\u0022System.Net.Http\u0022) # \u003C-- required for HttpClient types\r\n\r\n# For .NET 7\u002B tar support\r\ntry:\r\n clr.AddReference(\u0027System.Formats.Tar\u0027)\r\n TAR_SUPPORT = True\r\nexcept:\r\n TAR_SUPPORT = False\r\n\r\nfrom datetime import datetime\r\nfrom System.Net.Http import HttpClient, HttpClientHandler, HttpCompletionOption\r\nfrom System.IO import Path, Directory, File, FileStream, FileMode, FileAccess\r\nfrom System.IO.Compression import GZipStream, CompressionMode, ZipArchive, ZipArchiveMode\r\n# keep both: Python zipfile for listing members, and .NET ZipFile for ExtractToDirectory\r\nfrom zipfile import ZipFile as ZF\r\nfrom System.IO.Compression import ZipFile\r\nfrom System import Array, Byte\r\n\r\nif TAR_SUPPORT:\r\n from System.Formats.Tar import TarReader, TarEntryType\r\n\r\nfrom os import environ\r\nfrom System import Environment, PlatformID, TimeSpan\r\nfrom System.Runtime.InteropServices import RuntimeInformation\r\n\r\ndef WriteLog(s, progress=None, rewriteLastLog=False):\r\n if \u0027ThisTaskStepHandler\u0027 in globals() and ThisTaskStepHandler is not None:\r\n ThisTaskStepHandler.UpdateProgress(progress=progress, log=s)\r\n elif \u0027ThisTaskStep\u0027 in globals() and ThisTaskStep is not None:\r\n if progress is not None:\r\n ThisTaskStep.Progress = progress\r\n \r\n if rewriteLastLog:\r\n ThisTaskStep.DisplayLog = s\r\n ThisTaskStep.DisplayDebugLog = s\r\n else:\r\n ThisTaskStep.AddDebugLog(s, skipDuplicates=True)\r\n TCATaskManager.Update(ThisTask, True)\r\n else:\r\n if s is not None:\r\n print(s)\r\n #elif progress is not None:\r\n # print(str(progress) \u002B \u0027%\u0027)\r\n\r\ndef check_cancellation():\r\n \u0022\u0022\u0022Check if cancellation has been requested and raise if so.\u0022\u0022\u0022\r\n if \u0027ThisCancellationToken\u0027 in globals() and ThisCancellationToken is not None:\r\n if ThisCancellationToken.IsCancellationRequested:\r\n raise Exception(\u0022Operation cancelled\u0022)\r\n\r\ndef format_duration(seconds):\r\n \u0022\u0022\u0022Format duration in seconds to a human-readable string.\u0022\u0022\u0022\r\n if seconds \u003C 60:\r\n return \u0022{:.1f}s\u0022.format(seconds)\r\n elif seconds \u003C 3600:\r\n mins = int(seconds // 60)\r\n secs = int(seconds % 60)\r\n return \u0022{}m {}s\u0022.format(mins, secs)\r\n else:\r\n hours = int(seconds // 3600)\r\n mins = int((seconds % 3600) // 60)\r\n secs = int(seconds % 60)\r\n return \u0022{}h {}m {}s\u0022.format(hours, mins, secs)\r\n\r\nstart_time = datetime.now()\r\nWriteLog(\u0022\uD83D\uDE80 Starting at: \u0022 \u002B start_time.strftime(\u0022%H:%M:%S\u0022))\r\n\r\n# Set service to processing\r\nThisGameService.Variables[\u0027Processing\u0027] = \u0022True\u0022\r\n#ThisService.Configure()\r\n\r\njava_folder = Path.Combine(ThisService.RootDirectory, \u0027java\u0027)\r\n\r\nos = \u0027windows\u0027 if Environment.OSVersion.Platform == PlatformID.Win32NT else \u0027linux\u0027\r\narch = \u0027aarch64\u0027 if str(RuntimeInformation.ProcessArchitecture) == \u0027Arm64\u0027 else \u0027x64\u0027\r\n#WriteLog(\u0022Operating system is {0} Architecture is {1}\u0022.format(os, arch))\r\n\r\n# Should you want to use another API to grab the Java binaries, replace the urls in the following section\r\njava8 = {\r\n \u0027version\u0027: \u00278\u0027,\r\n \u0027url\u0027: \u0027https://api.adoptium.net/v3/binary/latest/8/ga/{0}/{1}/jdk/hotspot/normal/eclipse?project=jdk\u0027.format(os, arch)\r\n}\r\njava11 = {\r\n \u0027version\u0027: \u002711\u0027,\r\n \u0027url\u0027: \u0027https://api.adoptium.net/v3/binary/latest/11/ga/{0}/{1}/jdk/hotspot/normal/eclipse?project=jdk\u0027.format(os, arch)\r\n}\r\njava16 = {\r\n \u0027version\u0027: \u002716\u0027,\r\n \u0027url\u0027: \u0027https://api.adoptium.net/v3/binary/latest/16/ga/{0}/{1}/jdk/hotspot/normal/eclipse?project=jdk\u0027.format(os, arch)\r\n}\r\njava17 = {\r\n \u0027version\u0027: \u002717\u0027,\r\n \u0027url\u0027: \u0027https://api.adoptium.net/v3/binary/latest/17/ga/{0}/{1}/jdk/hotspot/normal/eclipse?project=jdk\u0027.format(os, arch)\r\n}\r\njava21 = {\r\n \u0027version\u0027: \u002721\u0027,\r\n \u0027url\u0027: \u0027https://api.adoptium.net/v3/binary/latest/21/ga/{0}/{1}/jdk/hotspot/normal/eclipse?project=jdk\u0027.format(os, arch)\r\n}\r\njava25 = {\r\n \u0027version\u0027: \u002725\u0027,\r\n \u0027url\u0027: \u0027https://api.adoptium.net/v3/binary/latest/25/ga/{0}/{1}/jdk/hotspot/normal/eclipse?project=jdk\u0027.format(os, arch)\r\n}\r\nurls = [java8, java11, java16, java17, java21, java25]\r\n\r\n# Create the java folder if it doesn\u0027t exist\r\nif not Directory.Exists(java_folder):\r\n Directory.CreateDirectory(java_folder)\r\n\r\n# Create HttpClientHandler with UseProxy = False.\r\nhandler = HttpClientHandler()\r\nhandler.UseProxy = False\r\n# defensive: try to clear Proxy object if allowed\r\ntry:\r\n handler.Proxy = None\r\nexcept Exception:\r\n pass\r\n\r\nclient = HttpClient(handler)\r\n# set a User-Agent \r\nclient.DefaultRequestHeaders.TryAddWithoutValidation(\u0022User-Agent\u0022, \u0022MyApp/1.0 (IronPython)\u0022)\r\n# set a reasonable timeout (adjust as needed)\r\nclient.Timeout = TimeSpan.FromMinutes(10)\r\n\r\n# helper to download a file synchronously with progress reporting\r\ndef download_file_to_path(url, destination_path):\r\n check_cancellation()\r\n \r\n resp = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result\r\n resp.EnsureSuccessStatusCode()\r\n \r\n # Get content length for progress reporting\r\n # IronPython auto-unwraps Nullable\u003CT\u003E to value or None\r\n content_length = resp.Content.Headers.ContentLength\r\n total_bytes = content_length if content_length is not None else 0\r\n \r\n # ensure parent dir exists\r\n parent = Path.GetDirectoryName(destination_path)\r\n if parent and not Directory.Exists(parent):\r\n Directory.CreateDirectory(parent)\r\n \r\n # Stream to file with progress\r\n stream = resp.Content.ReadAsStreamAsync().Result\r\n try:\r\n fs = FileStream(destination_path, FileMode.Create, FileAccess.Write)\r\n try:\r\n buffer = Array.CreateInstance(Byte, 81920) # 80KB buffer\r\n total_read = 0\r\n last_pct = -1\r\n \r\n bytes_read = stream.Read(buffer, 0, buffer.Length)\r\n while bytes_read \u003E 0:\r\n check_cancellation()\r\n \r\n fs.Write(buffer, 0, bytes_read)\r\n total_read \u002B= bytes_read\r\n \r\n # Report progress (whole numbers only, no repeats)\r\n if total_bytes \u003E 0:\r\n pct = int((total_read * 100) / total_bytes)\r\n if pct != last_pct:\r\n WriteLog(None, pct)\r\n last_pct = pct\r\n \r\n bytes_read = stream.Read(buffer, 0, buffer.Length)\r\n finally:\r\n fs.Dispose()\r\n finally:\r\n stream.Dispose()\r\n\r\ndef extract_zip(archive_path, destination_folder):\r\n \u0022\u0022\u0022\r\n Extract a .zip file using .NET ZipArchive with progress reporting and cancellation support.\r\n Returns the name of the root folder in the archive.\r\n \u0022\u0022\u0022\r\n root_folder_name = None\r\n \r\n # Get folder name using Python zipfile (quick peek)\r\n with ZF(archive_path, \u0027r\u0027) as zipObj:\r\n root_folder_name = zipObj.infolist()[0].filename\r\n \r\n # Extract using .NET ZipArchive for progress reporting and cancellation\r\n zipFs = FileStream(archive_path, FileMode.Open, FileAccess.Read)\r\n try:\r\n archive = ZipArchive(zipFs, ZipArchiveMode.Read)\r\n try:\r\n entries = list(archive.Entries)\r\n total_entries = len(entries)\r\n last_pct = -1\r\n \r\n for i, entry in enumerate(entries):\r\n check_cancellation()\r\n \r\n # Build destination path\r\n entry_path = Path.Combine(destination_folder, entry.FullName.replace(\u0027/\u0027, Path.DirectorySeparatorChar.ToString()))\r\n \r\n # Directory entries typically end with /\r\n if entry.FullName.endswith(\u0027/\u0027) or (entry.Length == 0 and not entry.Name):\r\n if not Directory.Exists(entry_path):\r\n Directory.CreateDirectory(entry_path)\r\n else:\r\n # Ensure parent directory exists\r\n parent_dir = Path.GetDirectoryName(entry_path)\r\n if parent_dir and not Directory.Exists(parent_dir):\r\n Directory.CreateDirectory(parent_dir)\r\n # Extract file by copying stream\r\n entryStream = entry.Open()\r\n try:\r\n outFs = FileStream(entry_path, FileMode.Create, FileAccess.Write)\r\n try:\r\n entryStream.CopyTo(outFs)\r\n finally:\r\n outFs.Dispose()\r\n finally:\r\n entryStream.Dispose()\r\n \r\n # Report progress (whole numbers only, no repeats)\r\n pct = int(((i \u002B 1) * 100) / total_entries)\r\n if pct != last_pct:\r\n WriteLog(None, pct)\r\n last_pct = pct\r\n finally:\r\n archive.Dispose()\r\n finally:\r\n zipFs.Dispose()\r\n \r\n return root_folder_name\r\n\r\ndef extract_tar_gz_native(archive_path, destination_folder):\r\n \u0022\u0022\u0022\r\n Extract a .tar.gz file using native .NET classes (System.IO.Compression.GZipStream \r\n and System.Formats.Tar.TarReader).\r\n Returns the name of the root folder in the archive.\r\n \u0022\u0022\u0022\r\n root_folder_name = None\r\n \r\n # First pass: count entries for progress reporting\r\n entry_count = 0\r\n gzipFileStream = FileStream(archive_path, FileMode.Open, FileAccess.Read)\r\n try:\r\n gzipStream = GZipStream(gzipFileStream, CompressionMode.Decompress)\r\n try:\r\n tarReader = TarReader(gzipStream)\r\n try:\r\n entry = tarReader.GetNextEntry()\r\n while entry is not None:\r\n check_cancellation()\r\n entry_count \u002B= 1\r\n entry = tarReader.GetNextEntry()\r\n finally:\r\n tarReader.Dispose()\r\n finally:\r\n gzipStream.Dispose()\r\n finally:\r\n gzipFileStream.Dispose()\r\n \r\n # Second pass: extract with progress reporting\r\n gzipFileStream = FileStream(archive_path, FileMode.Open, FileAccess.Read)\r\n try:\r\n gzipStream = GZipStream(gzipFileStream, CompressionMode.Decompress)\r\n try:\r\n tarReader = TarReader(gzipStream)\r\n try:\r\n current_entry = 0\r\n last_pct = -1\r\n \r\n entry = tarReader.GetNextEntry()\r\n while entry is not None:\r\n check_cancellation()\r\n \r\n # Capture the root folder name from the first entry\r\n if root_folder_name is None and entry.Name:\r\n name_parts = entry.Name.replace(\u0027\\\\\u0027, \u0027/\u0027).split(\u0027/\u0027)\r\n if name_parts and name_parts[0]:\r\n root_folder_name = name_parts[0]\r\n \r\n # Build the destination path\r\n entry_path = Path.Combine(destination_folder, entry.Name.replace(\u0027/\u0027, Path.DirectorySeparatorChar.ToString()))\r\n \r\n # Handle different entry types\r\n if entry.EntryType == TarEntryType.Directory:\r\n if not Directory.Exists(entry_path):\r\n Directory.CreateDirectory(entry_path)\r\n elif entry.EntryType == TarEntryType.RegularFile:\r\n parent_dir = Path.GetDirectoryName(entry_path)\r\n if parent_dir and not Directory.Exists(parent_dir):\r\n Directory.CreateDirectory(parent_dir)\r\n entry.ExtractToFile(entry_path, True)\r\n elif entry.EntryType == TarEntryType.SymbolicLink:\r\n pass\r\n \r\n # Report progress (whole numbers only, no repeats)\r\n current_entry \u002B= 1\r\n if entry_count \u003E 0:\r\n pct = int((current_entry * 100) / entry_count)\r\n if pct != last_pct:\r\n WriteLog(None, pct)\r\n last_pct = pct\r\n \r\n entry = tarReader.GetNextEntry()\r\n finally:\r\n tarReader.Dispose()\r\n finally:\r\n gzipStream.Dispose()\r\n finally:\r\n gzipFileStream.Dispose()\r\n \r\n return root_folder_name\r\n\r\ntry:\r\n for adoptium in urls:\r\n check_cancellation()\r\n \r\n # Only download binaries for non-installed versions of Java (environment variables must be set as JAVA8, JAVA11 and JAVA16)\r\n if \u0027JAVA{version}\u0027.format(version=adoptium[\u0027version\u0027]) in environ:\r\n WriteLog(\u0027\u23ED\uFE0F Skipping Java \u0027 \u002B adoptium[\u0027version\u0027] \u002B \u0027 (already installed)\u0027)\r\n continue\r\n\r\n # Delete folder if it exists (ie /java/jre16) when we need to update\r\n target_dir = Path.Combine(java_folder, \u0027java\u0027 \u002B adoptium[\u0027version\u0027])\r\n if Directory.Exists(target_dir):\r\n Directory.Delete(target_dir, True)\r\n WriteLog(\u0027\uD83D\uDDD1\uFE0F Deleted \u0027 \u002B Path.Combine(\u0027java\u0027, \u0027java\u0027 \u002B adoptium[\u0027version\u0027]))\r\n\r\n archive_type = \u0027.zip\u0027 if Environment.OSVersion.Platform == PlatformID.Win32NT else \u0027.tar.gz\u0027\r\n archive_name = \u0027adoptium\u0027 \u002B adoptium[\u0027version\u0027] \u002B archive_type\r\n archive_path = Path.Combine(java_folder, archive_name)\r\n\r\n WriteLog(\u0027\uD83D\uDCE5 Downloading \u0027 \u002B archive_name)\r\n\r\n # Download archive file\r\n try:\r\n download_file_to_path(adoptium[\u0027url\u0027], archive_path)\r\n except Exception as e:\r\n if \u0027cancelled\u0027 in str(e).lower():\r\n raise\r\n WriteLog(\u0027\u274C Download failed for {}: {}\u0027.format(adoptium[\u0027url\u0027], e))\r\n # skip to next version (don\u0027t try to extract)\r\n continue\r\n\r\n check_cancellation()\r\n\r\n # Extract archive (Windows / zip)\r\n if archive_name.endswith(\u0022.zip\u0022):\r\n if Directory.Exists(Path.Combine(java_folder, \u0027jdk\u0027)):\r\n Directory.Delete(Path.Combine(java_folder, \u0027jdk\u0027), True)\r\n \r\n WriteLog(\u0027\uD83D\uDCE6 Extracting \u0027 \u002B archive_name)\r\n foldername = extract_zip(archive_path, java_folder)\r\n WriteLog(\u0027\u2705 Extraction complete\u0027)\r\n \r\n WriteLog(\u0027\uD83D\uDCC1 Renaming to \u0027 \u002B Path.Combine(\u0027java\u0027, \u0027java\u0027 \u002B adoptium[\u0027version\u0027]))\r\n # foldername may have trailing slash, clean it\r\n foldername = foldername.rstrip(\u0027/\u0027)\r\n Directory.Move(Path.Combine(java_folder, foldername), Path.Combine(java_folder, \u0027java\u0027 \u002B adoptium[\u0027version\u0027]))\r\n\r\n # Extract archive (Linux / tar.gz) using native .NET\r\n elif archive_name.endswith(\u0022tar.gz\u0022):\r\n if not TAR_SUPPORT:\r\n raise Exception(\u0022System.Formats.Tar is not available. Please ensure you are running on .NET 7 or later.\u0022)\r\n \r\n WriteLog(\u0027\uD83D\uDCE6 Extracting \u0027 \u002B archive_name)\r\n \r\n # Extract and get the root folder name\r\n foldername = extract_tar_gz_native(archive_path, java_folder)\r\n \r\n if not foldername:\r\n raise Exception(\u0022Could not determine root folder name from tar archive\u0022)\r\n \r\n WriteLog(\u0027\u2705 Extraction complete\u0027)\r\n \r\n # Check if the extracted folder exists and rename it\r\n extracted_path = Path.Combine(java_folder, foldername)\r\n final_path = Path.Combine(java_folder, \u0027java\u0027 \u002B adoptium[\u0027version\u0027])\r\n \r\n if Directory.Exists(extracted_path):\r\n WriteLog(\u0027\uD83D\uDCC1 Renaming to \u0027 \u002B Path.Combine(\u0027java\u0027, \u0027java\u0027 \u002B adoptium[\u0027version\u0027]))\r\n Directory.Move(extracted_path, final_path)\r\n else:\r\n WriteLog(\u0027\u26A0\uFE0F Warning: Expected folder {} not found\u0027.format(foldername))\r\n\r\n # Delete archive file\r\n if File.Exists(archive_path):\r\n try:\r\n File.Delete(archive_path)\r\n WriteLog(\u0027\uD83E\uDDF9 Cleaned up \u0027 \u002B archive_name)\r\n except Exception as e:\r\n WriteLog(\u0027\u26A0\uFE0F Could not delete %s: %s\u0027 % (archive_name, e))\r\n\r\n# If anything goes wrong, set Processing to False\r\nexcept Exception as e:\r\n WriteLog(\u0027\u274C Error: {}\u0027.format(e))\r\n ThisGameService.Variables[\u0027Processing\u0027] = \u0022False\u0022\r\n #ThisService.Configure()\r\n\r\n# Set processing to false\r\nThisGameService.Variables[\u0027Processing\u0027] = \u0022False\u0022\r\n#ThisService.Configure()\r\n\r\nend_time = datetime.now()\r\nelapsed = (end_time - start_time).total_seconds()\r\nWriteLog(\u0022\u2705 Java configuration finished at {} (took {}) \uD83D\uDC4D\u0022.format(end_time.strftime(\u0022%H:%M:%S\u0022), format_duration(elapsed)))", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cg\u003E\u003Cpath d=\u0022M0,0h24v24H0V0z\u0022 fill=\u0022none\u0022/\u003E\u003C/g\u003E\u003Cg\u003E\u003Cpath d=\u0022M18.5,3H6C4.9,3,4,3.9,4,5v5.71c0,3.83,2.95,7.18,6.78,7.29c3.96,0.12,7.22-3.06,7.22-7v-1h0.5c1.93,0,3.5-1.57,3.5-3.5 S20.43,3,18.5,3z M16,5v3H6V5H16z M18.5,8H18V5h0.5C19.33,5,20,5.67,20,6.5S19.33,8,18.5,8z M4,19h16v2H4V19z\u0022/\u003E\u003C/g\u003E", + "scriptInputVariables": [], + "scriptRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ], + "editor": [], + "identifier": "b9f8ba99-c8cd-4181-816f-f95de8f2e064" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows" + ], + "scriptEvents": [ + "BeforeServiceStarted" + ], + "name": "EULA Check", + "description": "Check if EULA is set to true before start", + "code": "using System;\r\nusing System.IO;\r\nusing System.Text.RegularExpressions;\r\n\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\nstring eulaFile = Path.Combine(Globals.ThisService.RootDirectory, \u0022eula.txt\u0022);\r\n\r\nstring pattern = @\u0022eula=(?\u003Ceula\u003E(true|false)*)?\u0022;\r\nstring fileContents = File.ReadAllText(eulaFile);\r\nMatch match = Regex.Match(fileContents, pattern, RegexOptions.IgnoreCase);\r\n\r\nif (match.Groups[\u0022eula\u0022].Value != \u0022true\u0022)\r\n{\r\n throw new Exception(\u0022You need to accept the EULA. Open eula.txt and set eula=true in the file.\u0022);\r\n}\r\nelse\r\n{\r\n Console.WriteLine(\u0022EULA is OK \uD83D\uDC4D\u0022);\r\n}", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": false, + "order": 1, + "icon": "\u003Cg\u003E\u003Crect fill=\u0022none\u0022 height=\u002224\u0022 width=\u002224\u0022/\u003E\u003C/g\u003E\u003Cg\u003E\u003Cpath d=\u0022M23,12l-2.44-2.79l0.34-3.69l-3.61-0.82L15.4,1.5L12,2.96L8.6,1.5L6.71,4.69L3.1,5.5L3.44,9.2L1,12l2.44,2.79l-0.34,3.7 l3.61,0.82L8.6,22.5l3.4-1.47l3.4,1.46l1.89-3.19l3.61-0.82l-0.34-3.69L23,12z M10.09,16.72l-3.8-3.81l1.48-1.48l2.32,2.33 l5.85-5.87l1.48,1.48L10.09,16.72z\u0022/\u003E\u003C/g\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "2d59a23d-52aa-4563-a00e-038c5c9e4e0a" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "IronPython3", + "operatingSystem": [ + "Windows" + ], + "scriptEvents": [ + "AfterServiceReinstalled", + "AfterServiceCreated" + ], + "name": "Create executable", + "description": "Create minecraft_server.(sh|bat)", + "code": "import clr\r\nfrom System import Environment, PlatformID\r\nfrom System.IO import Path\r\nfrom platform import system\r\nimport os\r\n\r\nentrypointMappings = {\r\n \u0022Java 8\u0022: \u0022java8\u0022,\r\n \u0022Java 11\u0022: \u0022java11\u0022,\r\n \u0022Java 16\u0022: \u0022java16\u0022,\r\n}\r\n\r\njava_folder = os.path.join(ThisService.RootDirectory, \u0027java\u0027)\r\nexecutable = \u0027.exe\u0027 if system() == \u0027Windows\u0027 else \u0027\u0027\r\ncontent = \u0027 %*\u0027 if system() == \u0027Windows\u0027 else \u0027 $*\u0027\r\nbash_shebang = \u0027#!/bin/bash\\n\u0027 if system() == \u0027Linux\u0027 else \u0027\u0027 #On Linux, we need to add a shebang at the top of the .sh file\r\n\r\nif entrypointMappings[\u0027Java 16\u0027].upper() in os.environ: #If environment variables exists, use them\r\n java_path = \u0027\\\u0022%\u0027\u002BentrypointMappings[\u0027Java 16\u0027].upper()\u002B\u0027%\\\u0022\u0027 if system() == \u0027Windows\u0027 else \u0027\\\u0022${\u0027\u002BentrypointMappings[\u0027Java 16\u0027].upper()\u002B\u0027}\\\u0022\u0027\r\nelif entrypointMappings[\u0027Java 11\u0027].upper() in os.environ:\r\n java_path = \u0027\\\u0022%\u0027\u002BentrypointMappings[\u0027Java 11\u0027].upper()\u002B\u0027%\\\u0022\u0027 if system() == \u0027Windows\u0027 else \u0027\\\u0022${\u0027\u002BentrypointMappings[\u0027Java 11\u0027].upper()\u002B\u0027}\\\u0022\u0027\r\nelif entrypointMappings[\u0027Java 8\u0027].upper() in os.environ:\r\n java_path = \u0027\\\u0022%\u0027\u002BentrypointMappings[\u0027Java 8\u0027].upper()\u002B\u0027%\\\u0022\u0027 if system() == \u0027Windows\u0027 else \u0027\\\u0022${\u0027\u002BentrypointMappings[\u0027Java 8\u0027].upper()\u002B\u0027}\\\u0022\u0027\r\nelif os.path.isfile(os.path.join(java_folder, entrypointMappings[\u0027Java 16\u0027], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)): #If no environment variables exists, check if Java binaries exists on the service\r\n java_path = os.path.join(java_folder, entrypointMappings[\u0027Java 16\u0027], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)\r\nelif os.path.isfile(os.path.join(java_folder, entrypointMappings[\u0027Java 11\u0027], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)):\r\n java_path = os.path.join(java_folder, entrypointMappings[\u0027Java 11\u0027], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)\r\nelif os.path.isfile(os.path.join(java_folder, entrypointMappings[\u0027Java 8\u0027], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)):\r\n java_path = os.path.join(java_folder, entrypointMappings[\u0027Java 8\u0027], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)\r\nelse: #Fallback to using \u0027java\u0027\r\n java_path = \u0027java\u0027\r\n\r\n#Write the java path to the executable file\r\nexe = Path.Combine(ThisService.RootDirectory, ThisService.Executable)\r\nwith open(exe, \u0027w\u0027) as file:\r\n file.write(bash_shebang \u002B java_path \u002B content)\r\n\r\nprint(\u0022Created \u0022 \u002B exe)", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cpath d=\u0022M0 0h24v24H0z\u0022 fill=\u0022none\u0022/\u003E\u003Cpath d=\u0022M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-3.6 13.9l1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2 7 1.4z\u0022/\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "e2ca1a81-f21e-4dad-8edf-571cce800c55" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "IronPython3", + "operatingSystem": [ + "Windows" + ], + "scriptEvents": [ + "CustomServiceAction", + "BeforeServiceStarted" + ], + "name": "Detect java version", + "description": "Read Minecraft version from .jar file", + "code": "import os\r\nimport zipfile\r\nimport hashlib\r\nimport json\r\nfrom io import BytesIO\r\nimport re\r\nfrom platform import system \r\n\r\ndefault = \u0022Java 25\u0022\r\n# Set Automatic Java version if the variable doesn\u0027t exist.\r\nif not ThisGameService.Variables.ContainsKey(\u0022JavaVersion\u0022):\r\n ThisGameService.Variables[\u0022JavaVersion\u0022] = \u0027Automatic\u0027\r\n\r\n# Used for mapping Java versions to environment variables\r\nentrypointMappings = {\r\n \u0022Java 8\u0022: \u0022java8\u0022,\r\n \u0022Java 11\u0022: \u0022java11\u0022,\r\n \u0022Java 16\u0022: \u0022java16\u0022,\r\n \u0022Java 17\u0022: \u0022java17\u0022,\r\n \u0022Java 21\u0022: \u0022java21\u0022,\r\n \u0022Java 25\u0022: \u0022java25\u0022\r\n} \r\n \r\n# Function to compute SHA-256 checksum of a file\r\ndef compute_sha256(file_path):\r\n sha256 = hashlib.sha256()\r\n with open(file_path, \u0027rb\u0027) as f:\r\n for chunk in iter(lambda: f.read(4096), b\u0022\u0022):\r\n sha256.update(chunk)\r\n return sha256.hexdigest()\r\n\r\n# Function to read the manifest file and return the version\r\ndef read_manifest(jar_file):\r\n try:\r\n with zipfile.ZipFile(jar_file, \u0027r\u0027) as jar:\r\n if \u0027META-INF/MANIFEST.MF\u0027 in jar.namelist():\r\n manifest = jar.read(\u0027META-INF/MANIFEST.MF\u0027).decode(\u0027utf-8\u0027).splitlines()\r\n for line in manifest:\r\n if line.startswith(\u0027Build-Jdk\u0027):\r\n return parse_build_jdk(line)\r\n elif line.startswith(\u0027Main-Class\u0027):\r\n main_class = line.split(\u0027: \u0027)[1].strip().replace(\u0027.\u0027, \u0027/\u0027)\r\n return check_main_class(jar, main_class)\r\n except Exception as e:\r\n print(\u0022Error reading MANIFEST.MF: {0}\u0022.format(e))\r\n return None\r\n\r\n# Helper to parse Build-Jdk entry in META-INF/MANIFEST.MF\r\ndef parse_build_jdk(line):\r\n build_jdk = line.split(\u0027: \u0027)[1].strip()\r\n major_version = \u0027.\u0027.join(build_jdk.split(\u0027.\u0027)[:2])\r\n return \u0022Java \u0022 \u002B str(major_version)\r\n\r\n# Function to check Main-Class if found in MANIFEST.MF\r\ndef check_main_class(jar, main_class_path):\r\n main_class_file = main_class_path \u002B \u0022.class\u0022\r\n if main_class_file in jar.namelist():\r\n return extract_java_version_from_class(jar.read(main_class_file))\r\n return None\r\n\r\n# Function to extract Java version from a .class file\r\ndef extract_java_version_from_class(class_data):\r\n if len(class_data) \u003E= 8:\r\n major_version = int.from_bytes(class_data[6:8], byteorder=\u0027big\u0027)\r\n return {\r\n 45: \u0027Java 1.1\u0027,\r\n 46: \u0027Java 1.2\u0027,\r\n 47: \u0027Java 1.3\u0027,\r\n 48: \u0027Java 1.4\u0027,\r\n 49: \u0027Java 5\u0027,\r\n 50: \u0027Java 6\u0027,\r\n 51: \u0027Java 7\u0027,\r\n 52: \u0027Java 8\u0027,\r\n 53: \u0027Java 9\u0027,\r\n 54: \u0027Java 10\u0027,\r\n 55: \u0027Java 11\u0027,\r\n 56: \u0027Java 12\u0027,\r\n 57: \u0027Java 13\u0027,\r\n 58: \u0027Java 14\u0027,\r\n 59: \u0027Java 15\u0027,\r\n 60: \u0027Java 16\u0027,\r\n 61: \u0027Java 17\u0027,\r\n 62: \u0027Java 18\u0027,\r\n 63: \u0027Java 19\u0027,\r\n 64: \u0027Java 20\u0027,\r\n 65: \u0027Java 21\u0027,\r\n 66: \u0027Java 22\u0027,\r\n 67: \u0027Java 23\u0027,\r\n 68: \u0027Java 24\u0027,\r\n 69: \u0027Java 25\u0027\r\n }.get(major_version, \u0027Unknown Java version\u0027)\r\n return \u0027Unknown Java version\u0027\r\n\r\n# Function to check version.json in a nested jar\r\ndef check_version_json(jar_file):\r\n try:\r\n with zipfile.ZipFile(jar_file, \u0027r\u0027) as jar:\r\n return check_root_version_json(jar) or check_nested_version_json(jar)\r\n except Exception as e:\r\n print(\u0022Error reading version.json: {0}\u0022.format(e))\r\n return None\r\n\r\n# Helper to check the root version.json\r\ndef check_root_version_json(jar):\r\n if \u0027version.json\u0027 in jar.namelist():\r\n with jar.open(\u0027version.json\u0027) as version_file:\r\n return extract_java_version_from_json(version_file)\r\n\r\n# Helper to check nested jars for version.json\r\ndef check_nested_version_json(jar):\r\n for name in jar.namelist():\r\n if name.startswith(\u0027META-INF/versions/\u0027) and name.endswith(\u0027.jar\u0027):\r\n with jar.open(name) as nested_jar_file:\r\n with zipfile.ZipFile(BytesIO(nested_jar_file.read())) as nested_jar:\r\n if \u0027version.json\u0027 in nested_jar.namelist():\r\n with nested_jar.open(\u0027version.json\u0027) as version_file:\r\n return extract_java_version_from_json(version_file)\r\n\r\n# Function to extract Java version from version.json\r\ndef extract_java_version_from_json(version_file):\r\n version_data = json.loads(version_file.read().decode(\u0027utf-8\u0027))\r\n java_version = version_data.get(\u0027java_version\u0027)\r\n if java_version:\r\n print(\u0022Found version in version.json: Java {0}\u0022.format(java_version)) \r\n return \u0022Java \u0022 \u002B str(java_version)\r\n return None\r\n\r\n# Function to process a single jar file\r\ndef process_jar(jar_file):\r\n jar_sha256 = compute_sha256(jar_file)\r\n sha256_folder = os.path.join(ThisService.RootDirectory, \u0027Temp\u0027, \u0027MinecraftSHA256\u0027)\r\n sha256_file = os.path.join(sha256_folder, jar_sha256)\r\n\r\n # Check if SHA-256 checksum exists and read the Java version from it\r\n \r\n if not os.path.isdir(sha256_folder):\r\n os.mkdir(sha256_folder)\r\n if os.path.exists(sha256_file):\r\n with open(sha256_file, \u0027r\u0027) as f:\r\n saved_java_version = f.read().strip()\r\n print(\u0022SHA-256 checksum matched ({0}), using cached Java version: {1}\u0022.format(sha256_file, saved_java_version))\r\n return saved_java_version\r\n\r\n # Process jar and determine highest Java version\r\n print(\u0022Processing: {0}\u0022.format(jar_file))\r\n\r\n java_versions = [\r\n read_manifest(jar_file),\r\n check_version_json(jar_file)\r\n ]\r\n\r\n with zipfile.ZipFile(jar_file, \u0027r\u0027) as jar:\r\n class_files = [name for name in jar.namelist() if name.endswith(\u0027.class\u0027)]\r\n java_versions.extend([extract_java_version_from_class(jar.read(class_file)) for class_file in class_files])\r\n\r\n highest_version = determine_highest_java_version(java_versions)\r\n print(\u0022Highest Java version: {0}\u0022.format(highest_version))\r\n # Save the SHA-256 and highest Java version in a file\r\n with open(sha256_file, \u0027w\u0027) as f:\r\n f.write(\u0022{0}\u0022.format(highest_version))\r\n\r\n return highest_version\r\n\r\n# Determine the highest Java version with version grouping logic\r\ndef determine_highest_java_version(java_versions):\r\n adjusted_versions = []\r\n for version in java_versions:\r\n if version and version.startswith(\u0022Java \u0022) and version != \u0022Unknown Java version\u0022:\r\n version_number = version.split(\u0022 \u0022)[1]\r\n if float(version_number) \u003C= 8:\r\n adjusted_versions.append(\u0022Java 8\u0022)\r\n elif float(version_number) \u003C= 11:\r\n adjusted_versions.append(\u0022Java 11\u0022)\r\n elif float(version_number) \u003C= 16:\r\n adjusted_versions.append(\u0022Java 16\u0022)\r\n elif float(version_number) == 17:\r\n adjusted_versions.append(\u0022Java 17\u0022)\r\n elif float(version_number) \u003C= 21:\r\n adjusted_versions.append(\u0022Java 21\u0022)\r\n else:\r\n adjusted_versions.append(\u0022Java 25\u0022)\r\n\r\n return max(adjusted_versions, key=lambda v: list(map(int, v.split(\u0022 \u0022)[1].split(\u0027.\u0027))), default=\u0027Java 21\u0027)\r\n\r\n# Parse major version from version string\r\ndef parse_major_version(version_number):\r\n try:\r\n return int(version_number.split(\u0022.\u0022)[0]) if len(version_number.split(\u0022.\u0022)) == 1 else int(version_number.split(\u0022.\u0022)[1])\r\n except ValueError:\r\n return 0 # Fallback to 0 for invalid versions\r\n\r\n# Function to extract the version number from a filename\r\ndef extract_version_from_filename(filename):\r\n version_match = re.search(r\u0022(\\d\u002B\\.\\d\u002B(\\.\\d\u002B)?)\u0022, filename)\r\n return version_match.group(0) if version_match else None\r\n\r\ndef search_jars(directory=\u0027.\u0027, extension=\u0027\u0027):\r\n extension = extension.lower()\r\n for dirpath, dirnames, files in os.walk(directory):\r\n for name in files:\r\n if extension and name.lower().endswith(extension):\r\n return os.path.join(dirpath, name)\r\n elif not extension:\r\n return os.path.join(dirpath, name) \r\n \r\nstartup = ThisService.Commandline\r\ndef getJarFromStartup():\r\n splitted_commandline = startup.split()\r\n if not \u0027-jar\u0027 in startup:\r\n # Find the path segment starting with \u0022@\u0022 and ending with \u0022.txt\u0022 - takes care of Minecraft Forge and NeoForge\r\n print(\u0027Jar not found in commandline\u0027)\r\n for segment in splitted_commandline:\r\n if segment.startswith(\u0022@\u0022) and segment.endswith(\u0022.txt\u0022):\r\n print(\u0027Modloader arguments detected\u0027)\r\n path = segment[1:].rsplit(\u0022/\u0022, 1)[0] # Remove the @ and extract the directory\r\n print(\u0022Searching {}\u0022.format(os.path.join(ThisService.RootDirectory, os.path.dirname(path))))\r\n return search_jars(os.path.join(ThisService.RootDirectory, os.path.dirname(path)), \u0027.jar\u0027)\r\n else:\r\n return None # If no matching segment found\r\n # Search for \u0022server.jar\u0022 in the same directory\r\n for file in os.listdir(os.path.join(ThisService.RootDirectory, path)):\r\n if file.endswith(\u0022server.jar\u0022):\r\n print(\u0022Found {}\u0022.format(file))\r\n return os.path.join(path, file)\r\n return None\r\n \r\n \r\n for x in splitted_commandline:\r\n # Take care of Fabric. It relies on a separate \u0022server.jar\u0022 file in the root directory\r\n if x.startswith(\u0022fabric-server-launch\u0022) and x.endswith(\u0022.jar\u0022):\r\n return os.path.join(ThisService.RootDirectory, \u0027server.jar\u0027)\r\n # Every other .jar file\r\n if x.endswith(\u0027.jar\u0027):\r\n return x.strip()\r\n\r\ndef main():\r\n if ThisGameService.Variables.Parse[str](\u0022JavaVersion\u0022, \u0022Automatic\u0022) == \u0027Automatic\u0027:\r\n java_folder = os.path.join(ThisService.RootDirectory, \u0027java\u0027)\r\n executable = \u0027.exe\u0027 if system() == \u0027Windows\u0027 else \u0027\u0027\r\n content = \u0027 %*\u0027 if system() == \u0027Windows\u0027 else \u0027 $*\u0027\r\n bash_shebang = \u0027#!/bin/bash\\n\u0027 if system() == \u0027Linux\u0027 else \u0027\u0027 #On Linux, we need to add a shebang at the top of the .sh file\r\n jar_file = getJarFromStartup()\r\n if not jar_file:\r\n raise Exception(\u0027Your commandline does not include a jar file. Please select one.\u0027)\r\n print(\u0027Found jar file {}\u0027.format(jar_file))\r\n java_version = process_jar(os.path.join(ThisService.RootDirectory, jar_file))\r\n print(\u0027test 1\u0027 \u002B str(os.environ))\r\n if entrypointMappings[java_version].upper() in os.environ:\r\n print(\u0027test 2\u0027)\r\n java_path = \u0027\\\u0022%\u0027\u002BentrypointMappings[java_version].upper()\u002B\u0027%\\\u0022\u0027 if system() == \u0027Windows\u0027 else \u0027\\\u0022${\u0027\u002BentrypointMappings[java_version].upper()\u002B\u0027}\\\u0022\u0027\r\n elif os.path.isfile(os.path.join(java_folder, entrypointMappings[java_version], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)):\r\n print(\u0027test 3\u0027)\r\n java_path = os.path.join(java_folder, entrypointMappings[java_version], \u0027bin\u0027, \u0027java\u0027\u002Bexecutable)\r\n else:\r\n print(\u0027test 4\u0027)\r\n java_path = \u0027java\u0027\r\n with open(os.path.join(ThisService.RootDirectory, ThisService.Executable), \u0027w\u0027) as file:\r\n file.write(bash_shebang \u002B java_path \u002B content) \r\n else:\r\n print(\u0022Automatic detection is disabled. Configured to use {0}\u0022.format(ThisGameService.Variables[\u0022JavaVersion\u0022]));\r\n\r\n \r\nif __name__ == \u0022\u003Cmodule\u003E\u0022:\r\n main()", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": false, + "order": 2, + "icon": "\u003Cpath d=\u0022M0 0h24v24H0z\u0022 fill=\u0022none\u0022/\u003E\u003Cpath d=\u0022M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm8.94 3c-.46-4.17-3.77-7.48-7.94-7.94V1h-2v2.06C6.83 3.52 3.52 6.83 3.06 11H1v2h2.06c.46 4.17 3.77 7.48 7.94 7.94V23h2v-2.06c4.17-.46 7.48-3.77 7.94-7.94H23v-2h-2.06zM12 19c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z\u0022/\u003E", + "scriptInputVariables": [], + "scriptRoles": [ + { + "roleId": 3 + }, + { + "roleId": 2 + } + ], + "editor": [], + "identifier": "71e62194-d283-423a-a5a0-01bd094fc4d7" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows" + ], + "scriptEvents": [ + "AfterServiceCreated" + ], + "name": "Create Java task", + "description": "Create Scheduled Task for updating Adoptium", + "code": "//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll\r\n// Check if we need to create the task\r\n// The task will be created if any one of the Java versions does not have an environment variable\r\nusing TCAdmin.Scripting.Engines.Addons;\r\nusing System;\r\nusing System.Linq;\r\nusing System.Collections.Generic;\r\nusing Microsoft.EntityFrameworkCore;\r\nusing TCAdmin.SDK.Database.Entities;\r\nusing TCAdmin.GameHosting.SDK;\r\nusing TCAdmin.SDK.Models;\r\nusing TCAdmin.Web.Shared.Models;\r\nusing Quartz;\r\nusing Quartz.Impl.Triggers;\r\n\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\nvar javaVersions = new List\u003Cint\u003E { 8, 11, 16, 17, 21 };\r\nbool createTask = false;\r\nforeach (int javaVersion in javaVersions)\r\n{\r\n if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(String.Format(\u0022JAVA{0}\u0022, javaVersion))))\r\n {\r\n Globals.ScriptConsole.WriteLine(\u0022Creating scheduled task to update Java versions\u0022);\r\n createTask = true;\r\n break;\r\n }\r\n}\r\n\r\nif (createTask)\r\n{\r\n //var javaUpdateScript = Globals.RecurringTaskManager.TCAdminDbContext.Scripts.AsNoTracking().SingleOrDefault(x=\u003Ex.Code.StartsWith(\u0022# UPDATE-JAVA\u0022));\r\n var javaUpdateScript = Globals.ThisGame.Scripts.SingleOrDefault(x=\u003Ex.Code.StartsWith(\u0022# UPDATE-JAVA\u0022));\r\n if(javaUpdateScript == null)\r\n {\r\n throw new Exception(\u0022Java update script not found!\u0022);\r\n }\r\n var rtask = new RecurringTask()\r\n {\r\n OwnerId = Globals.ThisService.OwnerId,\r\n ServerId = Globals.ThisService.ServerId,\r\n ServiceId = Globals.ThisService.ServiceId,\r\n DisplayName = \u0022Update Java\u0022,\r\n Enabled = true,\r\n };\r\n\r\n var rstep = new RecurringTaskStep()\r\n {\r\n DisplayName = rtask.DisplayName,\r\n ModuleId = GameHostingModule.ModuleId,\r\n ProcessId = \u0022ScheduledGameScript\u0022,\r\n ServerId = rtask.ServerId,\r\n Variables = new TCAdminVars()\r\n {\r\n {\u0022ScriptId\u0022, javaUpdateScript.ScriptId},\r\n {\u0022ServiceId\u0022, Globals.ThisService.ServiceId}\r\n }\r\n };\r\n\r\n rtask.Steps = new List\u003CRecurringTaskStep\u003E() { rstep };\r\n\r\n var timeOfDay = new TimeOfDay(0, 0);\r\n var trigger = Globals.RecurringTaskManager.CreateDailyIntervalTrigger(1, IntervalUnit.Hour, timeOfDay, timeOfDay, DayOfWeek.Monday);\r\n\r\n rtask.Triggers = new() { (AbstractTrigger)trigger };\r\n\r\n await Globals.RecurringTaskManager.CreateAsync(rtask);\r\n\r\n await Globals.RecurringTaskManager.ReloadRecurringTask(rtask);\r\n\r\n Globals.ScriptConsole.WriteLine(\u0022Sucess!\u0022);\r\n}else\r\n{\r\n Globals.ScriptConsole.WriteLine(\u0022Not created!\u0022);\r\n}", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cg\u003E\u003Crect fill=\u0022none\u0022 height=\u002224\u0022 width=\u002224\u0022/\u003E\u003C/g\u003E\u003Cg\u003E\u003Cpath d=\u0022M19,4h-1V2h-2v2H8V2H6v2H5C3.89,4,3.01,4.9,3.01,6L3,20c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4z M19,20 H5V10h14V20z M9,14H7v-2h2V14z M13,14h-2v-2h2V14z M17,14h-2v-2h2V14z M9,18H7v-2h2V18z M13,18h-2v-2h2V18z M17,18h-2v-2h2V18z\u0022/\u003E\u003C/g\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "5fcba68a-8575-4815-9d72-1dd3ee3e6e12" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "IronPython3", + "operatingSystem": [ + "Windows" + ], + "scriptEvents": [ + "BeforeServiceStarted" + ], + "name": "Log4jPatcher securing", + "code": "import clr\r\nimport json, os, re\r\nclr.AddReference(\u0022System\u0022)\r\nclr.AddReference(\u0022System.Net.Http\u0022) # required for HttpClient types\r\n\r\nfrom System import Version, TimeSpan\r\nfrom System.Net import ServicePointManager, SecurityProtocolType\r\nfrom System.Net.Http import HttpClient, HttpClientHandler, HttpCompletionOption\r\n\r\n# ensure TLS1.2 (important for GitHub on older .NET versions)\r\nServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12\r\n\r\n# Create an HttpClientHandler with UseProxy = False\r\nhandler = HttpClientHandler()\r\nhandler.UseProxy = False\r\n# explicitly clear proxy object (defensive)\r\ntry:\r\n handler.Proxy = None\r\nexcept Exception:\r\n # some platform/versions may not allow setting Proxy, ignore if so\r\n pass\r\n\r\n# Create HttpClient using the handler\r\nclient = HttpClient(handler)\r\n# Add a User-Agent header\r\nclient.DefaultRequestHeaders.TryAddWithoutValidation(\u0022User-Agent\u0022, \u0022MyApp/1.0 (IronPython)\u0022)\r\n\r\n# Optional: set a reasonable timeout (adjust if you want)\r\nclient.Timeout = TimeSpan.FromSeconds(100)\r\n\r\n# helper to perform a GET and return the response content as text\r\ndef http_get_text(url):\r\n resp = client.GetAsync(url).Result\r\n resp.EnsureSuccessStatusCode()\r\n return resp.Content.ReadAsStringAsync().Result\r\n\r\n# helper to download bytes (for files)\r\ndef http_get_bytes(url):\r\n # use ResponseHeadersRead to start streaming once headers are available\r\n resp = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result\r\n resp.EnsureSuccessStatusCode()\r\n return resp.Content.ReadAsByteArrayAsync().Result\r\n\r\nurl = \u0027https://api.github.com/repos/CreeperHost/Log4jPatcher/releases/latest\u0027\r\njson_text = http_get_text(url)\r\njson_info = json.loads(json_text)\r\n\r\n# extract a numeric version if the \u0027name\u0027 contains a \u0022v\u0022 or other text:\r\nname = json_info.get(\u0027name\u0027, \u0027\u0027)\r\nm = re.search(r\u0027(\\d\u002B(?:\\.\\d\u002B)\u002B)\u0027, name)\r\nver_str = m.group(1) if m else name\r\n\r\ntry:\r\n log4jpatcher_version_latest = Version(ver_str)\r\nexcept Exception:\r\n # fallback if Version(...) can\u0027t parse the string\r\n log4jpatcher_version_latest = Version(\u00270.0.0\u0027)\r\n\r\nlog4jpatcher_version_installed = Version(\u00270.0.0\u0027) # adjust as needed\r\n\r\nif log4jpatcher_version_installed.CompareTo(log4jpatcher_version_latest) \u003C 0:\r\n print(\u0027Service {} - Updating Log4jPatcher before startup\u0027.format(ThisService.ServiceId))\r\n assets = [] # ensure variable exists even if an exception occurs\r\n try:\r\n assets = json_info.get(\u0027assets\u0027) or []\r\n if not assets:\r\n raise Exception(\u0027No assets found in release JSON\u0027)\r\n download_url = assets[0][\u0027browser_download_url\u0027]\r\n\r\n destination_file = os.path.join(ThisService.RootDirectory, \u0027Log4jPatcher\u0027, \u0027Log4jPatcher.jar\u0027)\r\n destination_path = os.path.dirname(destination_file)\r\n if not os.path.exists(destination_path):\r\n os.makedirs(destination_path)\r\n\r\n # Download file bytes and write to disk\r\n file_bytes = http_get_bytes(download_url)\r\n with open(destination_file, \u0027wb\u0027) as f:\r\n f.write(file_bytes)\r\n\r\n ThisService.Commandline = \u0027-javaagent:Log4jPatcher/Log4jPatcher.jar \u0027 \u002B ThisService.Commandline\r\n print(\u0027Service {} - Log4jPatcher has been updated\u0027.format(ThisService.ServiceId))\r\n print(ThisService.Commandline)\r\n except Exception as e:\r\n asset_name = (assets[0][\u0027name\u0027] if assets else \u0027\u003Cunknown\u003E\u0027)\r\n print(\u0027Service {} - Failed to download {}: {}\u0027.format(ThisService.ServiceId, asset_name, e))\r\n", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": true, + "order": 3, + "icon": "\u003Crect fill=\u0022none\u0022 height=\u002224\u0022 width=\u002224\u0022/\u003E\u003Cpath d=\u0022M12,1L3,5v6c0,5.55,3.84,10.74,9,12c5.16-1.26,9-6.45,9-12V5L12,1z M14.5,12.59l0.9,3.88L12,14.42l-3.4,2.05l0.9-3.87 l-3-2.59l3.96-0.34L12,6.02l1.54,3.64L17.5,10L14.5,12.59z\u0022/\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "48743c56-e62c-41f9-925f-d711797319af" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [], + "name": "Install modpack - Curseforge ", + "code": "//CURSE-MODPACK-INSTALL\r\n//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Diagnostics;\r\nusing System.IO;\r\nusing System.Linq;\r\nusing System.Net.Http;\r\nusing System.Text;\r\nusing System.Text.Json;\r\nusing System.Text.RegularExpressions;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nusing System.Xml.Linq;\r\nusing Microsoft.EntityFrameworkCore;\r\nusing Version = System.Version;\r\nusing TCAdmin.SDK;\r\nusing TCAdmin.SDK.Misc;\r\nusing TCAdmin.Scripting.Engines.Addons;\r\nusing TCAdmin.SDK.Misc.Compression;\r\nusing TCAdmin.SDK.Misc.Extensions;\r\nusing TCAdmin.SDK.Database.Entities;\r\nusing TCAdmin.SDK.Hubs.Interfaces.Server;\r\nusing TCAdmin.Web.Shared.Models;\r\nusing TCAdmin.Web.Shared.Models.Enums;\r\nusing TCAdmin.Web.Shared.Models.CustomMods.Curse;\r\nusing TCAdmin.GameHosting.SDK.CustomMods;\r\nusing TCAdmin.GameHosting.SDK.Curse;\r\nusing TCAdmin.GameHosting.SDK.Curse;\r\n\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\n// Don\u0027t do anything if mod type is not curse\r\nif(!Globals.Variables.TryGetValue\u003Cstring\u003E(\u0022ModType\u0022, out var modType) || modType!=\u0022curse\u0022) return;\r\n\r\n// Main execution\r\nCurseForgeModpacks installer = null;\r\nvar deleteWorld = Globals.GetValue\u003Cbool\u003E(\u0022DeleteWorld\u0022);\r\nvar totalSteps = deleteWorld ? 6 : 5;\r\ninstaller = new CurseForgeModpacks(Globals, totalSteps);\r\n\r\nvar modpackInfo = await installer.GetModpackInformation();\r\nvar fileName = installer.DownloadModpack(modpackInfo).Result;\r\nvar (installerJar, specificVersion) = await installer.ExtractModpack(fileName);\r\n\r\nif(installerJar!=null \u0026\u0026 installerJar.IndexOf(\u0022packwiz\u0022) != -1) installerJar = null;\r\n\r\nvar (modloaderInfo, isNeo) = await installer.InstallModloader(modpackInfo, installerJar, specificVersion);\r\nawait installer.SetCommandLine(modloaderInfo, isNeo);\r\nif (deleteWorld)\r\n{\r\n var propertiesFile = Path.Combine(Globals.ThisService.RootDirectory, \u0022server.properties\u0022);\r\n var levelName = installer.GetLevelName(propertiesFile);\r\n installer.DeleteWorldFolders(levelName);\r\n}\r\n\r\n// Set file permissions\r\nif (Globals.ThisServer.OperatingSystem == EOperatingSystem.Linux)\r\n{\r\n var owner = Globals.OperatingSystem.GetOwner(Globals.ThisService.RootDirectory);\r\n Globals.OperatingSystem.SetDirectoryOwner(Globals.ThisService.RootDirectory, string.Empty, owner, true);\r\n}\r\n\r\ninstaller.WriteLog($\u0022{Globals.ThisTaskStep.DisplayName} was installed sucessfully\u0022);\r\ninstaller.Dispose();\r\ninstaller=null;\r\n\r\n// CurseForge class\r\nclass CurseForgeModpacks : IDisposable\r\n{\r\n private readonly string _modpackId;\r\n private readonly string _modpackVersion;\r\n private readonly string _modLoaderType;\r\n private readonly string _gameVersion;\r\n private readonly bool _deleteWorldFolders;\r\n private readonly string _apiKey;\r\n private readonly int _totalSteps;\r\n private readonly HttpClient _httpClient;\r\n private readonly string _tcAdminFolder;\r\n private readonly MinecraftModpacksProvider _provider;\r\n private readonly CSharpGameGlobals Globals;\r\n\r\n public CurseForgeModpacks(CSharpGameGlobals globals, int totalSteps)\r\n {\r\n Globals = globals;\r\n _tcAdminFolder = Globals.TCAdminConfig.TCAdminPath;\r\n _modpackId = Globals.GetValue\u003Cstring\u003E(\u0022ModId\u0022);\r\n _modpackVersion = Globals.GetValue\u003Cstring\u003E(\u0022ModVersion\u0022);\r\n _modLoaderType = Globals.GetValue\u003Cstring\u003E(\u0022LoaderType\u0022);\r\n _gameVersion = Globals.GetValue\u003Cstring\u003E(\u0022MinecraftVersion\u0022);\r\n _deleteWorldFolders = Globals.GetValue\u003Cbool\u003E(\u0022DeleteWorld\u0022);\r\n _provider = Globals.GetValue\u003CMinecraftModpacksProvider\u003E(\u0022ModProvider\u0022);\r\n _apiKey = CurseBrowser.CURSE_API_KEY;\r\n _totalSteps = _deleteWorldFolders ? totalSteps - 2 : totalSteps - 1;\r\n _httpClient = new HttpClient();\r\n _httpClient.DefaultRequestHeaders.Add(\u0022x-api-key\u0022, _apiKey);\r\n }\r\n\r\n public void WriteLog(string s, int? progress = null, bool rewriteLastLog = false)\r\n {\r\n if(Globals.ThisTaskStepHandler != null)\r\n {\r\n Globals.ThisTaskStepHandler.UpdateProgress(progress: progress, log: s);\r\n }\r\n else if(Globals.ThisTaskStep!=null)\r\n {\r\n if(progress.HasValue)\r\n {\r\n Globals.ThisTaskStep.Progress = progress.Value;\r\n }\r\n\r\n if(rewriteLastLog)\r\n {\r\n Globals.ThisTaskStep.DisplayLog = s;\r\n Globals.ThisTaskStep.DisplayDebugLog = s;\r\n }\r\n else\r\n {\r\n Globals.ThisTaskStep.AddDebugLog(s, skipDuplicates:true);\r\n }\r\n Globals.TCATaskManager.Update(Globals.ThisTask, true);\r\n }\r\n else\r\n {\r\n Globals.ScriptConsole.WriteLine(s);\r\n }\r\n }\r\n\r\n public void SetDisplayMessage(string s)\r\n {\r\n if(Globals.ThisTaskStep!=null)\r\n {\r\n Globals.ThisTaskStep.DisplayLog = s;\r\n Globals.ThisTaskStep.DisplayDebugLog = s;\r\n Globals.TCATaskManager.Update(Globals.ThisTask, true);\r\n }\r\n else\r\n {\r\n Globals.ScriptConsole.WriteLine(s);\r\n }\r\n }\r\n\r\n public void UpdateProgress(int progress)\r\n {\r\n if(Globals.ThisTaskStep != null \u0026\u0026 progress!=Globals.ThisTaskStep.Progress)\r\n {\r\n Globals.ThisTaskStep.Progress=progress;\r\n Globals.TCATaskManager.Update(Globals.ThisTask, true);\r\n }\r\n }\r\n\r\n public async Task\u003CLatestFile\u003E GetModpackInformation()\r\n {\r\n WriteLog($\u00221/{_totalSteps} - Getting information about the modpack ID {_modpackId} version {_modpackVersion}...\u0022);\r\n \r\n var modpackInfo = await CurseBrowser.GetFile(_modpackId, _modpackVersion);\r\n \r\n if (!modpackInfo.IsServerPack \u0026\u0026 modpackInfo.ServerPackFileId \u003E 0)\r\n {\r\n var serverPackId = modpackInfo.ServerPackFileId.ToString();\r\n WriteLog($\u00221/{_totalSteps} - Serverpack ID: {serverPackId}\u0022);\r\n var serverpackInfo = await CurseBrowser.GetFile(_modpackId, serverPackId);\r\n \r\n if (serverpackInfo.GameVersions.Count == 0)\r\n {\r\n //modpackInfo = serverpackInfo.Clone();\r\n serverpackInfo.GameVersions = modpackInfo.GameVersions;\r\n serverpackInfo.SortableGameVersions = modpackInfo.SortableGameVersions;\r\n modpackInfo = serverpackInfo;\r\n }\r\n else\r\n {\r\n modpackInfo = serverpackInfo;\r\n }\r\n WriteLog($\u00221/{_totalSteps} - Serverpack information received\u0022);\r\n }\r\n \r\n UpdateProgress(33);\r\n return modpackInfo;\r\n }\r\n\r\n public async Task\u003Cstring\u003E DownloadModpack(LatestFile modpackInfo)\r\n {\r\n var fileName = modpackInfo.FileName;\r\n var fileSize = modpackInfo.FileLength / (1024 * 1024);\r\n var downloadUrl = modpackInfo.DownloadUrl;\r\n \r\n if (string.IsNullOrEmpty(downloadUrl))\r\n {\r\n var fileId = modpackInfo.Id;\r\n downloadUrl = $\u0022https://edge.forgecdn.net/files/{fileId.Substring(0, 4)}/{fileId.Substring(fileId.Length - 3)}/{fileName}\u0022;\r\n }\r\n\r\n WriteLog($\u00222/{_totalSteps} - Downloading modpack \\\u0022{fileName}\\\u0022 ({fileSize}MB)\u0022);\r\n var filePath = Path.Combine(Globals.ThisService.RootDirectory, fileName);\r\n \r\n \r\n using (var response = await _httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead, Globals.ThisCancellationToken))\r\n using (var stream = await response.Content.ReadAsStreamAsync())\r\n using (var fileStream = new FileStream(filePath, FileMode.Create))\r\n {\r\n var totalBytes = response.Content.Headers.ContentLength ?? 0;\r\n var buffer = new byte[8192];\r\n var bytesRead = 0;\r\n var totalRead = 0L;\r\n \r\n while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) \u003E 0)\r\n {\r\n Globals.ThisCancellationToken.ThrowIfCancellationRequested();\r\n await fileStream.WriteAsync(buffer, 0, bytesRead);\r\n totalRead \u002B= bytesRead;\r\n var progress = (int)((double)totalRead / totalBytes * 100);\r\n if(progress % 5 == 0)\r\n {\r\n UpdateProgress(progress);\r\n //WriteLog($\u00222/{_totalSteps} - Downloading modpack \\\u0022{fileName}\\\u0022 ({fileSize}MB, {progress}%)\u0022, progress, true);\r\n }\r\n }\r\n }\r\n \r\n UpdateProgress(100);\r\n return fileName;\r\n }\r\n\r\n public async Task\u003C(string installerJar, string specificModloaderVersion)\u003E ExtractModpack(string fileName)\r\n {\r\n UpdateProgress(0);\r\n var specificModloaderVersion = (string)null;\r\n WriteLog($\u00223/{_totalSteps} - Extracting \\\u0022{fileName}\\\u0022\u0022);\r\n \r\n var zipFile = Path.Combine(Globals.ThisService.RootDirectory, fileName);\r\n var tmpDir = Path.Combine(Globals.ThisService.RootDirectory, $\u0022{_modpackId}-tmp\u0022);\r\n if(!Directory.Exists(tmpDir)) Directory.CreateDirectory(tmpDir);\r\n\r\n var c = new CompressionTools(Globals.OperatingSystem)\r\n {\r\n ThrowException = true,\r\n KeepListOfExtractedFiles = true,\r\n SetFileOwnerAutomatically = true\r\n };\r\n c.Extracting\u002B=(o, e) =\u003E {\r\n var progress = (int)e.Progress;\r\n if(progress % 5 == 0)\r\n {\r\n UpdateProgress(progress);\r\n }\r\n };\r\n c.Decompress(zipFile, tmpDir, Globals.ThisCancellationToken);\r\n \r\n WriteLog($\u00223/{_totalSteps} - Extraction complete\u0022);\r\n var extractedFiles = c.ExtractedFiles;\r\n var regex = Globals.ThisServer.OperatingSystem == EOperatingSystem.Windows\r\n ? new Regex(@\u0022^.*[\\\\/]\u002Bmods[\\\\/]\u002B.*\\.jar$\u0022)\r\n : new Regex(@\u0022^.*/mods/.*\\.jar$\u0022);\r\n\r\n var modsFound = extractedFiles.Any(f =\u003E regex.IsMatch(f));\r\n string installerJar = null;\r\n\r\n if (!modsFound \u0026\u0026 File.Exists(Path.Combine(tmpDir, \u0022mods.csv\u0022)))\r\n {\r\n WriteLog($\u00223/{_totalSteps} - No mods folder, but mods.csv available. Downloading files...\u0022);\r\n var downloadFiles = File.ReadAllLines(Path.Combine(tmpDir, \u0022mods.csv\u0022))\r\n .Select(line =\u003E line.Split(\u0027,\u0027))\r\n .Where(parts =\u003E parts.Length \u003E= 2)\r\n .ToList();\r\n \r\n var totalFiles = downloadFiles.Count;\r\n for (var i = 0; i \u003C totalFiles; i\u002B\u002B)\r\n {\r\n Globals.ThisCancellationToken.ThrowIfCancellationRequested();\r\n var parts = downloadFiles[i];\r\n var fileUrl = parts[0];\r\n var modFileName = parts[1];\r\n var modFilePath = Path.Combine(Globals.ThisService.RootDirectory, modFileName);\r\n var modDir = Path.GetDirectoryName(modFilePath);\r\n \r\n if (!Directory.Exists(modDir))\r\n Directory.CreateDirectory(modDir);\r\n \r\n var progress = (int)((double)i / totalFiles * 100);\r\n WriteLog($\u00223/{_totalSteps} - Downloading file {i\u002B1}/{totalFiles} ({progress}%)\u0022, progress, true);\r\n \r\n using (var response = await _httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead, Globals.ThisCancellationToken))\r\n {\r\n response.EnsureSuccessStatusCode();\r\n using (var stream = await response.Content.ReadAsStreamAsync())\r\n using (var fileStream = new FileStream(modFilePath, FileMode.Create, FileAccess.Write, FileShare.None))\r\n {\r\n await stream.CopyToAsync(fileStream);\r\n }\r\n }\r\n \r\n if (modFileName.Contains(\u0022-installer.jar\u0022))\r\n installerJar = Path.GetFileName(modFileName);\r\n }\r\n }\r\n else if (modsFound)\r\n {\r\n var modsFile = extractedFiles.First(f =\u003E regex.IsMatch(f));\r\n var modsDir = Path.GetDirectoryName(modsFile);\r\n var basePath = Globals.ThisServer.OperatingSystem == EOperatingSystem.Windows\r\n ? modsDir.Replace(\u0022\\\\mods\u0022, \u0022\u0022)\r\n : modsDir.Replace(\u0022/mods\u0022, \u0022\u0022);\r\n \r\n installerJar = extractedFiles\r\n .FirstOrDefault(f =\u003E f.EndsWith(\u0022-installer.jar\u0022, StringComparison.OrdinalIgnoreCase));\r\n installerJar = installerJar != null ? Path.GetFileName(installerJar) : null;\r\n\r\n var totalFiles = extractedFiles.Count;\r\n for (var i = 0; i \u003C totalFiles; i\u002B\u002B)\r\n {\r\n Globals.ThisCancellationToken.ThrowIfCancellationRequested();\r\n var file = extractedFiles[i];\r\n if (Path.GetFileName(file) == \u0022eula.txt\u0022) continue;\r\n \r\n var dest = Path.Combine(Globals.ThisService.RootDirectory, file.Replace(basePath, \u0022\u0022).TrimStart(Path.DirectorySeparatorChar));\r\n var destDir = Path.GetDirectoryName(dest);\r\n \r\n if (Path.GetFileName(file) == \u0022variables.txt\u0022)\r\n {\r\n WriteLog($\u00223/{_totalSteps} - variables.txt found. Loading modloader version...\u0022);\r\n specificModloaderVersion = File.ReadAllLines(file)\r\n .Where(line =\u003E !line.Trim().StartsWith(\u0022#\u0022) \u0026\u0026 line.Contains(\u0027=\u0027))\r\n .Select(line =\u003E line.Split(\u0027=\u0027))\r\n .Where(parts =\u003E parts.Length \u003E 1 \u0026\u0026 parts[0].Trim() == \u0022MODLOADER_VERSION\u0022)\r\n .Select(parts =\u003E parts[1].Trim())\r\n .FirstOrDefault();\r\n \r\n WriteLog($\u00223/{_totalSteps} - Modloader version: {specificModloaderVersion}\u0022);\r\n continue;\r\n }\r\n \r\n if (!Directory.Exists(destDir))\r\n Directory.CreateDirectory(destDir);\r\n \r\n File.Move(file, dest, true);\r\n var progress = 50 \u002B (int)((double)i / totalFiles * 50);\r\n if(progress % 10 == 0) WriteLog($\u00223/{_totalSteps} - Moving files ({i\u002B1}/{totalFiles})\u0022, progress, true);\r\n }\r\n \r\n WriteLog($\u00223/{_totalSteps} - All files moved\u0022);\r\n while (true)\r\n {\r\n Globals.ThisCancellationToken.ThrowIfCancellationRequested();\r\n try\r\n {\r\n File.Delete(zipFile);\r\n var fabricProps = Path.Combine(Globals.ThisService.RootDirectory, \u0022fabric-server-launcher.properties\u0022);\r\n if (File.Exists(fabricProps)) File.Delete(fabricProps);\r\n if (Directory.Exists(tmpDir)) Directory.Delete(tmpDir, true);\r\n break;\r\n }\r\n catch\r\n {\r\n WriteLog($\u00223/{_totalSteps} - File still in use, retrying...\u0022);\r\n Thread.Sleep(1000);\r\n }\r\n }\r\n }\r\n else\r\n {\r\n throw new Exception($\u00223/{_totalSteps} - No mods available in the pack\u0022);\r\n }\r\n \r\n UpdateProgress(100);\r\n return (installerJar, specificModloaderVersion);\r\n }\r\n\r\n public async Task\u003C(string[] modloaderInfo, bool isNeo)\u003E InstallModloader(LatestFile modloader, string includedInstallerJar, string specificModloaderVersion)\r\n {\r\n WriteLog($\u00224/{_totalSteps} - Beginning modloader installation\u0022);\r\n UpdateProgress(0);\r\n \r\n var modloaderType = modloader.SortableGameVersions\r\n .FirstOrDefault(v =\u003E v.GameVersionTypeId == 68441)?.GameVersionName;\r\n\r\n var gameVersions = modloader.GameVersions.ToList();\r\n\r\n if (gameVersions.Count \u003C 2 \u0026\u0026 string.IsNullOrEmpty(_modLoaderType))\r\n throw new Exception(\u0022You need to specify a modloader to install\u0022);\r\n\r\n gameVersions.Sort();\r\n var gameVersion = !string.IsNullOrEmpty(_gameVersion) ? _gameVersion : gameVersions[0];\r\n var isNeo = false;\r\n string modloaderVersion = null;\r\n string installerJar = null;\r\n string modloaderJar = null;\r\n\r\n if (string.IsNullOrEmpty(includedInstallerJar) || specificModloaderVersion != null)\r\n {\r\n if (_modLoaderType == \u0022Forge\u0022 || _modLoaderType == \u0022NeoForge\u0022 || \r\n modloaderType == \u0022Forge\u0022 || modloaderType == \u0022NeoForge\u0022)\r\n {\r\n isNeo = _modLoaderType == \u0022NeoForge\u0022 || modloaderType == \u0022NeoForge\u0022;\r\n var loaderResponse = _httpClient.GetAsync($\u0022https://api.curseforge.com/v1/minecraft/modloader?version={gameVersion}\u0022, Globals.ThisCancellationToken).Result;\r\n loaderResponse.EnsureSuccessStatusCode();\r\n var loaderData = JsonDocument.Parse(loaderResponse.Content.ReadAsStringAsync().Result);\r\n var latestVersions = loaderData.RootElement.GetProperty(\u0022data\u0022).EnumerateArray()\r\n .Where(v =\u003E v.GetProperty(\u0022latest\u0022).GetBoolean())\r\n .Select(v =\u003E v.GetProperty(\u0022name\u0022).GetString())\r\n .ToList();\r\n \r\n modloaderVersion = latestVersions.Max();\r\n var versionResponse = _httpClient.GetAsync($\u0022https://api.curseforge.com/v1/minecraft/modloader/{modloaderVersion}\u0022, Globals.ThisCancellationToken).Result;\r\n versionResponse.EnsureSuccessStatusCode();\r\n var versionData = JsonDocument.Parse(versionResponse.Content.ReadAsStringAsync().Result);\r\n var forgeVersion = versionData.RootElement.GetProperty(\u0022data\u0022).GetProperty(\u0022forgeVersion\u0022).GetString();\r\n modloaderVersion = specificModloaderVersion ?? forgeVersion;\r\n \r\n // Handle old Forge versions\r\n if ((_modLoaderType == \u0022Forge\u0022 || modloaderType == \u0022Forge\u0022) \u0026\u0026 \r\n new Version(modloaderVersion).CompareTo(new Version(\u002212.18.0.2008\u0022)) \u003C 0)\r\n {\r\n modloaderVersion \u002B= \u0022-\u0022 \u002B gameVersion;\r\n }\r\n \r\n if (isNeo)\r\n {\r\n var mcVersion = versionData.RootElement.GetProperty(\u0022data\u0022).GetProperty(\u0022minecraftVersion\u0022).GetString();\r\n if(new Version(mcVersion).CompareTo(new Version(\u00221.20.1\u0022)) \u003E 0)\r\n {\r\n var neoforgePrefix = GetNeoForgeVersion(gameVersion);\r\n\r\n // Step 1: Download the XML\r\n var MetadataUrl = \u0022https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml\u0022;\r\n using var client = new HttpClient();\r\n var xmlData = await client.GetStringAsync(MetadataUrl);\r\n \r\n // Step 2: Parse the XML\r\n var doc = XDocument.Parse(xmlData);\r\n var versions = doc\r\n .Root\r\n .Element(\u0022versioning\u0022)\r\n .Element(\u0022versions\u0022)\r\n .Elements(\u0022version\u0022)\r\n .Select(x =\u003E x.Value);\r\n\r\n // Step 3: Filter by prefix\r\n var matching = versions\r\n .Where(v =\u003E v.StartsWith(neoforgePrefix, StringComparison.OrdinalIgnoreCase))\r\n .ToList();\r\n\r\n if (matching.Count == 0)\r\n throw new InvalidOperationException(\r\n $\u0022Could not find a Neoforge version starting with \u0027{neoforgePrefix}\u0027.\u0022);\r\n\r\n // Step 4: Sort and pick latest\r\n matching.Sort(CompareVersionKeys);\r\n modloaderVersion = matching.SingleOrDefault(x=\u003Ex == modloaderVersion) ?? matching[^1]; // last element\r\n\r\n installerJar = $\u0022https://maven.neoforged.net/releases/net/neoforged/neoforge/{modloaderVersion}/neoforge-{modloaderVersion}-installer.jar\u0022;\r\n }\r\n else\r\n {\r\n WriteLog(\u0022NeoForge version is smaller than 1.20.1\u0022);\r\n\r\n installerJar = $\u0022https://maven.neoforged.net/releases/net/neoforged/forge/{gameVersion}-{modloaderVersion}/forge-{gameVersion}-{modloaderVersion}-installer.jar\u0022;\r\n\r\n }\r\n }\r\n\r\n if(!isNeo)\r\n {\r\n installerJar = $\u0022https://maven.minecraftforge.net/net/minecraftforge/forge/{gameVersion}-{modloaderVersion}/forge-{gameVersion}-{modloaderVersion}-installer.jar\u0022;\r\n }\r\n \r\n modloaderJar = versionData.RootElement.GetProperty(\u0022data\u0022).GetProperty(\u0022filename\u0022).GetString();\r\n }\r\n else if (_modLoaderType == \u0022Fabric\u0022 || modloaderType == \u0022Fabric\u0022)\r\n {\r\n var fabricMetadata = _httpClient.GetStringAsync(\u0022https://maven.fabricmc.net/net/fabricmc/fabric-installer/maven-metadata.xml\u0022).Result;\r\n var doc = XDocument.Parse(fabricMetadata);\r\n modloaderVersion = doc.Descendants(\u0022release\u0022).First().Value;\r\n installerJar = $\u0022https://maven.fabricmc.net/net/fabricmc/fabric-installer/{modloaderVersion}/fabric-installer-{modloaderVersion}.jar\u0022;\r\n modloaderJar = \u0022fabric-server-launch.jar\u0022;\r\n }\r\n \r\n var installerPath = Path.Combine(Globals.ThisService.RootDirectory, Path.GetFileName(installerJar));\r\n\r\n using (var response = await _httpClient.GetAsync(installerJar, HttpCompletionOption.ResponseHeadersRead, Globals.ThisCancellationToken))\r\n {\r\n response.EnsureSuccessStatusCode();\r\n var totalBytes = response.Content.Headers.ContentLength ?? -1L;\r\n var canReportProgress = totalBytes != -1;\r\n\r\n using (var contentStream = await response.Content.ReadAsStreamAsync())\r\n using (var fileStream = new FileStream(installerPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))\r\n {\r\n var buffer = new byte[8192];\r\n long totalRead = 0;\r\n int read;\r\n WriteLog($\u00224/{_totalSteps} - Downloading modloader \\\u0022{installerJar}\\\u0022\u0022);\r\n\r\n while ((read = await contentStream.ReadAsync(buffer, 0, buffer.Length)) \u003E 0)\r\n {\r\n Globals.ThisCancellationToken.ThrowIfCancellationRequested();\r\n await fileStream.WriteAsync(buffer, 0, read);\r\n totalRead \u002B= read;\r\n\r\n if (canReportProgress)\r\n {\r\n var progress = (int)((totalRead * 100L) / totalBytes);\r\n if(progress % 5 == 0) UpdateProgress(progress);\r\n }\r\n }\r\n }\r\n }\r\n \r\n installerJar = Path.GetFileName(installerJar);\r\n }\r\n else\r\n {\r\n isNeo = _modLoaderType == \u0022NeoForge\u0022 || modloaderType == \u0022NeoForge\u0022;\r\n installerJar = includedInstallerJar;\r\n WriteLog($\u00224/{_totalSteps} - Modloader installer found in modpack ({installerJar})\u0022);\r\n modloaderJar = installerJar.Replace(\u0022-installer\u0022, \u0022\u0022);\r\n \r\n if (modloaderJar.Contains(\u0022fabric\u0022))\r\n {\r\n modloaderVersion = modloaderJar.Replace(\u0022fabric-\u0022, \u0022\u0022).Replace(\u0022.jar\u0022, \u0022\u0022).Replace(\u0022-\u0022, \u0022\u0022);\r\n modloaderJar = \u0022fabric-server-launch.jar\u0022;\r\n }\r\n else\r\n {\r\n modloaderVersion = modloaderJar\r\n .Replace($\u0022forge-{gameVersion}-\u0022, \u0022\u0022)\r\n .Replace(\u0022neoforge-\u0022, \u0022\u0022)\r\n .Replace(\u0022.jar\u0022, \u0022\u0022);\r\n }\r\n }\r\n\r\n WriteLog($\u00224/{_totalSteps} - Installing modloader\u0022);\r\n var args = (_modLoaderType.EndsWith(\u0022Forge\u0022) || _modLoaderType.EndsWith(\u0022Forge\u0022) || isNeo)\r\n ? $\u0022-jar {installerJar} --installServer\u0022\r\n : $\u0022-jar {installerJar} server -downloadMinecraft -mcversion {gameVersion} -dir {Globals.ThisService.RootDirectory}\u0022;\r\n \r\n Process p;\r\n if (Globals.ThisServer.OperatingSystem == EOperatingSystem.Windows)\r\n {\r\n var startInfo = new ServiceToolStartOptions\r\n {\r\n Executable = Path.Combine(Globals.ThisService.RootDirectory, Globals.ThisService.Executable),\r\n WorkingDirectory = Globals.ThisService.RootDirectory,\r\n Arguments = args\r\n };\r\n\r\n var startResult = await Globals.ServiceManagerService.StartService(Globals.ThisService.ServiceId, startInfo);\r\n\r\n if(!startResult.Success) throw new Exception(\u0022Could not start installer process\u0022);\r\n\r\n p = Process.GetProcessById(startResult.ProcessId);\r\n }\r\n else\r\n {\r\n var psi = new ProcessStartInfo\r\n {\r\n FileName = Path.Combine(Globals.ThisService.RootDirectory, Globals.ThisService.Executable),\r\n Arguments = args,\r\n WorkingDirectory = Globals.ThisService.RootDirectory,\r\n UseShellExecute = false\r\n };\r\n p = Process.Start(psi);\r\n if (p == null) throw new Exception(\u0022Could not start installer process\u0022);\r\n }\r\n p.EnableRaisingEvents = true;\r\n UpdateProgress(-1);\r\n Globals.Globals.ThisCancellationToken.Register(() =\u003E {\r\n if(p != null \u0026\u0026 !p.HasExited) p.Kill(true);\r\n });\r\n await p.WaitForExitAsync(Globals.Globals.ThisCancellationToken);\r\n UpdateProgress(100);\r\n \r\n if (p.ExitCode != 0)\r\n {\r\n if ((_modLoaderType == \u0022Fabric\u0022 || modloaderType == \u0022Fabric\u0022) \u0026\u0026 !string.IsNullOrEmpty(includedInstallerJar))\r\n throw new Exception(\u0022Fabric modloader installation failed. Try another version/modloader\u0022);\r\n \r\n if ((_modLoaderType.EndsWith(\u0022Forge\u0022) || modloaderType.EndsWith(\u0022Forge\u0022)) \u0026\u0026 !string.IsNullOrEmpty(includedInstallerJar))\r\n {\r\n WriteLog($\u00224/{_totalSteps} - Included Forge failed. Trying latest for {gameVersion}\u0022);\r\n return await InstallModloader(modloader, null, null);\r\n }\r\n p=null;\r\n throw new Exception(\u0022Modloader installation failed. Check logs for details.\u0022);\r\n }\r\n else\r\n {\r\n p=null;\r\n }\r\n \r\n // Check for universal jar\r\n if ((_modLoaderType == \u0022Forge\u0022 || modloaderType == \u0022Forge\u0022) \u0026\u0026 !isNeo)\r\n {\r\n var universalJar = Path.Combine(Globals.ThisService.RootDirectory, installerJar.Replace(\u0022-installer\u0022, \u0022-universal\u0022));\r\n WriteLog($\u00224/{_totalSteps} - Checking if Forge installer created universal jar {universalJar}\u0022);\r\n if (File.Exists(universalJar))\r\n {\r\n modloaderJar = Path.GetFileName(universalJar);\r\n WriteLog($\u00224/{_totalSteps} - Using universal jar: {modloaderJar}\u0022);\r\n }\r\n }\r\n \r\n UpdateProgress(90);\r\n WriteLog($\u00224/{_totalSteps} - Modloader installed\u0022);\r\n \r\n // Cleanup\r\n var installerPathToDelete = Path.Combine(Globals.ThisService.RootDirectory, installerJar);\r\n if (File.Exists(installerPathToDelete)) File.Delete(installerPathToDelete);\r\n \r\n UpdateProgress(100);\r\n\r\n return (new[] { modloaderJar, gameVersion, modloaderVersion }, isNeo);\r\n }\r\n\r\n public async Task SetCommandLine(string[] modloaderInfo, bool isNeo)\r\n {\r\n WriteLog($\u00225/{_totalSteps} - Configuring commandline\u0022);\r\n var variables = new TCAdminVars() {\r\n {_provider.JarVariableName, modloaderInfo[0].Replace(Globals.ThisService.RootDirectory, \u0022\u0022)}\r\n };\r\n\r\n // Delete previous command line\r\n var prevCmdLineKey=$\u0022{_provider.InstallPrefix}:CustomModsCmd\u0022;\r\n if(Globals.ThisService.Variables.TryGetValue\u003Clong?\u003E(prevCmdLineKey, out var prevCmdLineId))\r\n {\r\n WriteLog($\u00225/{_totalSteps} - Deleting previous commandline\u0022);\r\n var prevCmdLine = Globals.ThisService.CustomCommandLines.SingleOrDefault(x=\u003Ex.CmdLineId==prevCmdLineId);\r\n if(prevCmdLine!=null) Globals.ThisService.CustomCommandLines.Remove(prevCmdLine);\r\n }\r\n\r\n var cmdLine = new ServiceCustomCommandLine\r\n { \r\n Name=Globals.ThisTaskStep.DisplayName,\r\n CommandLine = Globals.ThisGame.CommandlineConfig.DefaultCommandline,\r\n Variables = variables\r\n };\r\n Globals.ThisService.CustomCommandLines.Add(cmdLine);\r\n\r\n if (Version.TryParse($\u0022{modloaderInfo[2].Split(\u0027.\u0027)[0]}.0\u0022, out var parsed) \u0026\u0026 parsed.CompareTo(new Version(\u002237.0\u0022)) \u003C 0 \u0026\u0026 !isNeo)\r\n {\r\n // Nothing to do?\r\n }\r\n else\r\n {\r\n var argsOs = Globals.ThisServer.OperatingSystem == EOperatingSystem.Windows ? \u0022win\u0022 : \u0022unix\u0022;\r\n var forgeType = isNeo ? \u0022neoforged\u0022 : \u0022minecraftforge\u0022;\r\n var jarVar = $\u0022-jar ${{{_provider.JarVariableName}}}\u0022;\r\n \r\n // WriteLog($\u00225/{_totalSteps} - Detected isNeo: {isNeo} forgeType: {forgeType} jarVar: {jarVar} modloaderInfo: {modloaderInfo.ToJson()}\u0022);\r\n cmdLine.OverrideDefault = true;\r\n if (isNeo \u0026\u0026 new Version(modloaderInfo[1]).CompareTo(new Version(\u00221.20.1\u0022)) \u003E 0)\r\n {\r\n cmdLine.CommandLine = cmdLine.CommandLine.Replace(jarVar, $\u0022@libraries/net/{forgeType}/neoforge/{modloaderInfo[2]}/{argsOs}_args.txt\u0022);\r\n }\r\n else\r\n {\r\n cmdLine.CommandLine=cmdLine.CommandLine.Replace(jarVar, $\u0022@libraries/net/{forgeType}/forge/{modloaderInfo[1]}-{modloaderInfo[2]}/{argsOs}_args.txt\u0022);\r\n }\r\n }\r\n\r\n await Globals.ServiceManager.UpdateAsync(Globals.ThisService, true, default);\r\n Globals.ThisService.Variables[prevCmdLineKey] = cmdLine.CmdLineId;\r\n Globals.ThisService.Variables[$\u0022{_provider.InstallPrefix}:Type\u0022] = \u0022Curse\u0022;\r\n Globals.ThisService.Variables[\u0022__SelectedCmdLine\u0022] = $\u0022CUS:{cmdLine.CmdLineId}\u0022;\r\n \r\n var appliedCmdline = await Globals.ServiceManager.ApplyCustomCommandLine(null, Globals.ThisService.ServiceId, cmdLine.CmdLineId, false);\r\n if(!appliedCmdline) throw new Exception($\u0022Could not apply command line id {cmdLine.CmdLineId}\u0022);\r\n\r\n await Globals.ServiceManager.UpdateAsync(Globals.ThisService, true, default);\r\n }\r\n\r\n public string GetLevelName(string filePath)\r\n {\r\n foreach (var line in File.ReadLines(filePath))\r\n {\r\n if (line.StartsWith(\u0022level-name=\u0022))\r\n {\r\n var levelName = line.Split(\u0027=\u0027)[1].Trim();\r\n WriteLog($\u00226/{_totalSteps} - Level name detected: {levelName}\u0022);\r\n return levelName;\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n public void DeleteWorldFolders(string levelName)\r\n {\r\n if (string.IsNullOrEmpty(levelName))\r\n {\r\n WriteLog($\u00226/{_totalSteps} - Level name empty. Skipping deletion\u0022);\r\n return;\r\n }\r\n \r\n WriteLog($\u00226/{_totalSteps} - Deleting world folders\u0022);\r\n var basePath = Path.Combine(Globals.ThisService.RootDirectory, levelName);\r\n var folders = new[]\r\n {\r\n basePath,\r\n basePath \u002B \u0022_the_end\u0022,\r\n basePath \u002B \u0022_nether\u0022\r\n };\r\n \r\n foreach (var folder in folders)\r\n {\r\n if (Directory.Exists(folder))\r\n {\r\n Directory.Delete(folder, true);\r\n WriteLog($\u00226/{_totalSteps} - Deleted: {folder}\u0022);\r\n }\r\n else\r\n {\r\n WriteLog($\u00226/{_totalSteps} - Not found: {folder}\u0022);\r\n }\r\n }\r\n }\r\n\r\n public static (List\u003Cint\u003E NumericParts, bool IsBeta) VersionKey(string v)\r\n {\r\n if (v == null) throw new ArgumentNullException(nameof(v));\r\n\r\n bool isBeta = v.Contains(\u0022-beta\u0022, StringComparison.OrdinalIgnoreCase);\r\n // Remove any \u0022-beta\u0022 suffix (case-insensitive) before splitting\r\n string cleaned = v.Replace(\u0022-beta\u0022, \u0022\u0022, StringComparison.OrdinalIgnoreCase);\r\n // Split on \u0027.\u0027, parse each segment to int\r\n var numericParts = cleaned\r\n .Split(\u0027.\u0027, StringSplitOptions.RemoveEmptyEntries)\r\n .Select(p =\u003E\r\n {\r\n if (!int.TryParse(p, out var n))\r\n throw new FormatException($\u0022Invalid numeric version component: \u0027{p}\u0027 in version \u0027{v}\u0027\u0022);\r\n return n;\r\n })\r\n .ToList();\r\n\r\n return (numericParts, isBeta);\r\n }\r\n\r\n public static string GetNeoForgeVersion(string mcVersion)\r\n {\r\n if (string.IsNullOrWhiteSpace(mcVersion))\r\n throw new ArgumentException(\u0022Version must not be empty\u0022, nameof(mcVersion));\r\n\r\n var parts = mcVersion.Split(\u0027.\u0027, StringSplitOptions.RemoveEmptyEntries);\r\n if (parts.Length \u003C 2)\r\n throw new ArgumentException(\r\n \u0022Invalid Minecraft version format. Use format like \u00271.21.1\u0027\u0022, nameof(mcVersion));\r\n\r\n // Skip the first component and re-join the rest\r\n return string.Join(\u0027.\u0027, parts.Skip(1));\r\n }\r\n\r\n private static int CompareVersionKeys(string a, string b)\r\n {\r\n var (pa, betaA) = VersionKey(a);\r\n var (pb, betaB) = VersionKey(b);\r\n\r\n int len = Math.Max(pa.Count, pb.Count);\r\n for (int i = 0; i \u003C len; i\u002B\u002B)\r\n {\r\n int na = i \u003C pa.Count ? pa[i] : 0;\r\n int nb = i \u003C pb.Count ? pb[i] : 0;\r\n int cmp = na.CompareTo(nb);\r\n if (cmp != 0)\r\n return cmp;\r\n }\r\n\r\n // if numeric parts equal, non-beta (false) comes before beta (true)\r\n return betaA.CompareTo(betaB);\r\n }\r\n\r\n public void Dispose()\r\n {\r\n _httpClient?.Dispose();\r\n }\r\n}", + "runImpersonated": false, + "stopService": true, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cg stroke-width=\u00220\u0022\u003E\u003C/g\u003E\u003Cg stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022\u003E\u003C/g\u003E\u003Cg\u003E\u003Cpath d=\u0022m6.307 5.581.391 1.675H0s.112.502.167.558c.168.279.335.614.559.837 1.06 1.228 2.902 1.73 4.409 2.009 1.06.224 2.121.28 3.181.335l1.228 3.293h.67l.391 1.061h-.558l-.949 3.07h9.321l-.949-3.07h-.558l.39-1.061h.67s.558-3.404 2.288-4.967C21.935 7.758 24 7.535 24 7.535V5.581H6.307zm9.377 8.428c-.447.279-.949.279-1.284.503-.223.111-.335.446-.335.446-.223-.502-.502-.67-.837-.781-.335-.112-.949-.056-1.786-.782-.558-.502-.614-1.172-.558-1.507v-.167c0-.056 0-.112.056-.168.111-.334.39-.669.948-.893 0 0-.39.559 0 1.117.224.335.67.502 1.061.279.167-.112.279-.335.335-.503.111-.39.111-.781-.224-1.06-.502-.446-.613-1.06-.279-1.451 0 0 .112.502.614.446.335 0 .335-.111.224-.223-.056-.167-.782-1.228.279-2.009 0 0 .669-.447 1.451-.391-.447.056-.949.335-1.116.782v.055c-.168.447-.056.949.279 1.396.223.335.502.614.614 1.06-.168-.056-.279 0-.391.112a.533.533 0 0 0-.112.502c.056.112.168.223.279.223h.168c.167-.055.279-.279.223-.446.112.111.167.391.112.558 0 .167-.112.335-.168.446-.056.112-.167.224-.223.335-.056.112-.112.224-.112.335 0 .112 0 .279.056.391.223.335.67 0 .782-.279.167-.335.111-.726-.112-1.061 0 0 .391.224.67 1.005.223.67-.168 1.451-.614 1.73z\u0022\u003E\u003C/path\u003E\u003C/g\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "49a002c6-020e-44df-8459-8b25081665ec" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [ + "CustomServiceIcon" + ], + "name": "Select Java version", + "description": "Select a specific version of Java or allow the control panel to automatically decide.", + "code": "//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing TCAdmin.SDK.DynamicInput.Controls;\r\n\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\nGlobals.ThisService.Variables.TryGetValue\u003Cstring\u003E(\u0022JavaVersion\u0022, out var currentVersion);\r\nGlobals.ScriptConsole.WriteLine($\u0022The Java version has been set to \\\u0022{ currentVersion }\\\u0022\u0022);\r\n\r\n/*\r\nvar versions = new List\u003Cstring\u003E() {\r\n \u0022Automatic\u0022,\r\n \u0022Java 8\u0022,\r\n \u0022Java 11\u0022,\r\n \u0022Java 16\u0022,\r\n \u0022Java 17\u0022,\r\n \u0022Java 21\u0022\r\n};\r\n\r\nvar versionsComboBox = new DynamicComboBox\r\n{\r\n Id = \u0022SelectedJavaVersion\u0022,\r\n Label = \u0022Select the Java version to use\u0022,\r\n Description = \u0022This value is required\u0022,\r\n DefaultValue = currentVersion,\r\n Required = true\r\n};\r\n\r\nforeach(var version in versions)\r\n{\r\n versionsComboBox.Items.Add(new DynamicComboBoxItem { Text = version, Value = version });\r\n}\r\n\r\nvar result = Globals.ThisScript.Input(false, versionsComboBox);\r\n\r\nGlobals.ThisGameService.Variables[\u0022JavaVersion\u0022] = result[\u0022SelectedJavaVersion\u0022];\r\nGlobals.ServiceManager.Update(Globals.ThisGameService.Service, true);*/", + "runImpersonated": true, + "stopService": false, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cg\u003E\u003Cpath d=\u0022M0,0h24v24H0V0z\u0022 fill=\u0022none\u0022/\u003E\u003C/g\u003E\u003Cg\u003E\u003Cpath d=\u0022M18.5,3H6C4.9,3,4,3.9,4,5v5.71c0,3.83,2.95,7.18,6.78,7.29c3.96,0.12,7.22-3.06,7.22-7v-1h0.5c1.93,0,3.5-1.57,3.5-3.5 S20.43,3,18.5,3z M16,5v3H6V5H16z M18.5,8H18V5h0.5C19.33,5,20,5.67,20,6.5S19.33,8,18.5,8z M4,19h16v2H4V19z\u0022/\u003E\u003C/g\u003E", + "scriptInputVariables": [ + { + "variable": { + "name": "JavaVersion", + "defaultValue": "Automatic", + "required": true, + "requiredMessage": "The java version is required", + "scriptParameter": true, + "commandlineParameter": false, + "saveScriptParameter": true, + "syncCommandlineParameter": false, + "template": "${JavaVersion}", + "editor": { + "items": [ + { + "text": "", + "value": "Automatic" + }, + { + "text": "", + "value": "Java 8" + }, + { + "value": "Java 11" + }, + { + "value": "Java 16" + }, + { + "value": "Java 17" + }, + { + "text": "", + "value": "Java 21" + }, + { + "value": "Java 25" + } + ], + "id": "JavaVersion", + "defaultValue": "Automatic", + "label": "Select the Java version to use", + "description": "This value is required", + "required": true, + "valueType": "string", + "requiredMessage": "The java version is required", + "controlType": "DynamicComboBox", + "sm": 6, + "xs": 12, + "requiresAnyVisible": [] + }, + "createdDate": "2025-07-12T02:29:13.214", + "metadata": { + "notes": "" + }, + "variableRoles": [ + { + "roleId": 2 + }, + { + "roleId": 3 + } + ] + }, + "variableId": 5 + } + ], + "scriptRoles": [ + { + "roleId": 3 + }, + { + "roleId": 2 + } + ], + "editor": [ + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 1, + "id": "Xms", + "defaultValue": "128", + "label": "Xms", + "description": "Min memory allocated by Java in megabytes", + "required": false, + "requiredMessage": "A value for Xms is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "decimals": 0, + "step": 1, + "spinner": false, + "valueType": "double", + "minValue": 128, + "id": "Xmx", + "defaultValue": "2048", + "label": "Xmx", + "description": "Max memory allocated by Java in megabytes", + "required": false, + "requiredMessage": "A value for Xmx is required", + "controlType": "DynamicNumericTextBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + }, + { + "items": [ + { + "text": "", + "value": "Automatic" + }, + { + "text": "", + "value": "Java 8" + }, + { + "value": "Java 11" + }, + { + "value": "Java 16" + }, + { + "value": "Java 17" + }, + { + "value": "Java 21" + } + ], + "id": "JavaVersion", + "defaultValue": "Automatic", + "label": "Select the Java version to use", + "description": "This value is required", + "required": false, + "valueType": "string", + "requiredMessage": "The java version is required", + "controlType": "DynamicComboBox", + "sm": 12, + "xs": 12, + "requiresAnyVisible": [] + } + ], + "identifier": "5f01bb82-e4ce-46c9-9989-3f00aa172351" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows" + ], + "scriptEvents": [ + "AfterCustomModUninstalled", + "BeforeServiceReinstalled" + ], + "name": "Uninstall modpack", + "code": "//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing TCAdmin.GameHosting.SDK.CustomMods;\r\nusing TCAdmin.GameHosting.SDK.Curse;\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\n// Not a mod pack\r\nvar providerId = Globals.Variables.Parse\u003Cint\u003E(\u0022ProviderId\u0022);\r\nif(providerId != 3) return;\r\n\r\nvar gameService = Globals.ThisService;\r\nvar cancellationToken = Globals.ThisCancellationToken;\r\n// Remove modpack command line and variables\r\nvar modpackCmdLineKey = $\u0022{MinecraftModpacksProvider.ModpacksPrefix}:CustomModsCmd\u0022;\r\nvar modpackTypeKey = $\u0022{MinecraftModpacksProvider.ModpacksPrefix}:Type\u0022;\r\nvar modpackInstalledKey = $\u0022{MinecraftModpacksProvider.ModpacksPrefix}:Installed\u0022;\r\nif (gameService.Variables.TryGetValue\u003Clong?\u003E(modpackCmdLineKey, out var modpackCmdLineId))\r\n{\r\n var cmd = gameService.CustomCommandLines.SingleOrDefault(x =\u003E x.CmdLineId == modpackCmdLineId);\r\n if (cmd != null) gameService.CustomCommandLines.Remove(cmd);\r\n if (gameService.Variables.TryGetValue\u003Cstring\u003E(\u0022__SelectedCmdLine\u0022, out var selectedCmd) \u0026\u0026 selectedCmd == $\u0022CUS:{modpackCmdLineId}\u0022)\r\n gameService.Variables.Remove(\u0022__SelectedCmdLine\u0022);\r\n}\r\ngameService.Variables.Remove(modpackTypeKey);\r\ngameService.Variables.Remove(modpackCmdLineKey);\r\ngameService.Variables.Remove(modpackInstalledKey);\r\n\r\n// Apply the first predefined command line\r\nvar firstCommandline = Globals.ThisGame.CommandlineConfig.PredefinedCommandlines.FirstOrDefault();\r\nif(firstCommandline != null)\r\n{\r\n await Globals.ServiceManager.ApplyPredefinedCommandLine(null, Globals.ThisService.ServiceId, firstCommandline.Id, false);\r\n}\r\n\r\nawait Globals.GameServiceManager.UpdateAsync(gameService, true, cancellationToken);\r\n\r\nWriteLog($\u0022{Globals.ThisTaskStep.DisplayName} was uninstalled sucessfully\u0022);\r\n\r\n\r\nvoid WriteLog(string s, int? progress = null, bool rewriteLastLog = false)\r\n{\r\n if(Globals.ThisTaskStep!=null)\r\n {\r\n if(progress.HasValue)\r\n {\r\n Globals.ThisTaskStep.Progress = progress.Value;\r\n }\r\n\r\n if(rewriteLastLog)\r\n {\r\n Globals.ThisTaskStep.DisplayLog = s;\r\n Globals.ThisTaskStep.DisplayDebugLog = s;\r\n }\r\n else\r\n {\r\n Globals.ThisTaskStep.AddDebugLog(s, skipDuplicates:true);\r\n }\r\n Globals.TCATaskManager.Update(Globals.ThisTask, true);\r\n }\r\n else\r\n {\r\n Globals.ScriptConsole.WriteLine(s);\r\n }\r\n}", + "runImpersonated": true, + "stopService": true, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Csvg viewBox=\u00220 0 43 40\u0022 version=\u00221.1\u0022\u003E\r\n \u003Cpath d=\u0022m12.5 0l-12.5 8.1 8.7 7 12.5-7.8-8.7-7.3zm-12.5 21.9l12.5 8.2 8.7-7.3-12.5-7.7-8.7 6.8zm21.2 0.9l8.8 7.3 12.4-8.1-8.6-6.9-12.6 7.7zm21.2-14.7l-12.4-8.1-8.8 7.3 12.6 7.8 8.6-7zm-21.1 16.3l-8.8 7.3-3.7-2.5v2.8l12.5 7.5 12.5-7.5v-2.8l-3.8 2.5-8.7-7.3z\u0022/\u003E\r\n\u003C/svg\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "832127bd-b992-49b0-8cb0-145598cd1e39" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [], + "name": "Install modpack - FeedTheBeast", + "code": "//FTB-MODPACK-INSTALL\r\n//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Diagnostics;\r\nusing TCAdmin.SDK.Hubs.Interfaces.Server;\r\nusing System.IO;\r\nusing System.Text.Json;\r\nusing System.Net.Http;\r\nusing System.Text.RegularExpressions;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nusing TCAdmin.SDK.Misc;\r\nusing TCAdmin.SDK;\r\nusing TCAdmin.Web.Shared.Models.Enums;\r\nusing TCAdmin.GameHosting.SDK.CustomMods;\r\nusing TCAdmin.Web.Shared.Models;\r\nusing System.Runtime.InteropServices;\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\n// Don\u0027t do anything if mod type is not ftb\r\nif(!Globals.Variables.TryGetValue\u003Cstring\u003E(\u0022ModType\u0022, out var modType) || modType!=\u0022ftb\u0022) return;\r\n\r\n// Main execution\r\nvar installer = new FTBModpacks(Globals);\r\nvar installerFilename = await installer.DownloadServerpackInstaller();\r\nawait installer.InstallModpack(installerFilename);\r\nawait installer.SetCommandLine();\r\n\r\nif (installer.DeleteWorld)\r\n{\r\n var propertiesFile = Path.Combine(Globals.ThisService.RootDirectory, \u0022server.properties\u0022);\r\n var levelName = installer.GetLevelName(propertiesFile);\r\n installer.DeleteWorldFolders(levelName);\r\n}\r\n\r\n// Set file permissions\r\nif (Globals.ThisServer.OperatingSystem == EOperatingSystem.Linux)\r\n{\r\n var owner = Globals.OperatingSystem.GetOwner(Globals.ThisService.RootDirectory);\r\n Globals.OperatingSystem.SetDirectoryOwner(Globals.ThisService.RootDirectory, string.Empty, owner, true);\r\n}\r\n\r\ninstaller.WriteLog($\u0022{Globals.ThisTaskStep.DisplayName} was installed sucessfully\u0022);\r\n\r\nclass FTBModpacks\r\n{\r\n private readonly MinecraftModpacksProvider _provider;\r\n public readonly TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals Globals;\r\n public string ModpackId { get; }\r\n public string ModpackVersion { get; }\r\n public string ModpackName { get; }\r\n public bool DeleteWorld { get; }\r\n \r\n public FTBModpacks(TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals globals)\r\n {\r\n Globals = globals;\r\n ModpackId = Globals.GetValue\u003Cstring\u003E(\u0022ModId\u0022);\r\n DeleteWorld = Globals.GetValue\u003Cbool\u003E(\u0022DeleteWorld\u0022);\r\n ModpackVersion = Globals.GetValue\u003Cstring\u003E(\u0022ModVersion\u0022);\r\n _provider = Globals.GetValue\u003CMinecraftModpacksProvider\u003E(\u0022ModProvider\u0022);\r\n ModpackName = Globals.ThisTaskStep.DisplayName;\r\n }\r\n\r\n public void WriteLog(string s, int? progress = null, bool rewriteLastLog = false)\r\n {\r\n if(Globals.ThisTaskStepHandler != null)\r\n {\r\n Globals.ThisTaskStepHandler.UpdateProgress(progress: progress, log: s);\r\n }\r\n else if(Globals.ThisTaskStep!=null)\r\n {\r\n if(progress.HasValue)\r\n {\r\n Globals.ThisTaskStep.Progress = progress.Value;\r\n }\r\n\r\n if(rewriteLastLog)\r\n {\r\n Globals.ThisTaskStep.DisplayLog = s;\r\n Globals.ThisTaskStep.DisplayDebugLog = s;\r\n }\r\n else\r\n {\r\n Globals.ThisTaskStep.AddDebugLog(s, skipDuplicates:true);\r\n }\r\n Globals.TCATaskManager.Update(Globals.ThisTask, true);\r\n }\r\n else\r\n {\r\n Globals.ScriptConsole.WriteLine(s);\r\n }\r\n }\r\n\r\n public void SetDisplayMessage(string s, int? progress = null)\r\n {\r\n if(Globals.ThisTaskStepHandler!=null)\r\n {\r\n Globals.ThisTaskStepHandler.UpdateProgress(progress: progress, log: s);\r\n }\r\n else if(Globals.ThisTaskStep!=null)\r\n {\r\n Globals.ThisTaskStep.DisplayLog = s;\r\n Globals.ThisTaskStep.DisplayDebugLog = s;\r\n if(progress.HasValue) Globals.ThisTaskStep.Progress = progress.Value;\r\n Globals.TCATaskManager.Update(Globals.ThisTask, true);\r\n }\r\n else\r\n {\r\n Globals.ScriptConsole.WriteLine(s);\r\n }\r\n }\r\n\r\n public void UpdateProgress(int progress)\r\n {\r\n if(Globals.ThisTaskStep != null \u0026\u0026 progress!=Globals.ThisTaskStep.Progress)\r\n {\r\n Globals.ThisTaskStep.Progress=progress;\r\n Globals.TCATaskManager.Update(Globals.ThisTask, true);\r\n }\r\n }\r\n \r\n public async Task\u003Cstring\u003E DownloadServerpackInstaller()\r\n {\r\n WriteLog($\u0022Downloading modpack installer for {ModpackName}\u0022);\r\n \r\n var isWindows = Globals.ThisServer.OperatingSystem == EOperatingSystem.Windows;\r\n var installerFilename = Path.Combine(\r\n Globals.ThisService.RootDirectory, \r\n isWindows ? \u0022ftb-serverpack-installer.exe\u0022 : \u0022ftb-serverpack-installer\u0022\r\n );\r\n \r\n var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? \u0022arm\u0022 : \u0022amd64\u0022;\r\n var os = isWindows ? \u0022windows\u0022 : \u0022linux\u0022;\r\n var osArg = $\u0022{arch}/{os}\u0022;\r\n //{\u0022valid_os_values\u0022:[\u0022windows\u0022,\u0022linux\u0022,\u0022darwin\u0022,\u0022freebsd\u0022,\u0022unix\u0022],\u0022valid_arch_values\u0022:[\u0022arm64\u0022,\u0022amd64\u0022,\u0022arm\u0022,\u0022x64\u0022],\u0022possible_values\u0022:[\u0022windows\u0022,\u0022arm64/windows\u0022,\u0022amd64/windows\u0022,\u0022linux\u0022,\u0022arm64/linux\u0022,\u0022amd64/linux\u0022,\u0022darwin\u0022,\u0022arm64/darwin\u0022,\u0022amd64/darwin\u0022,\u0022freebsd\u0022,\u0022arm64/freebsd\u0022,\u0022amd64/freebsd\u0022]}\r\n var downloadUrl = $\u0022https://api.feed-the-beast.com/v1/modpacks/public/modpack/{ModpackId}/{ModpackVersion}/server/{osArg}\u0022;\r\n\r\n // Download with progress reporting\r\n using var httpClient = new HttpClient();\r\n using var response = httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result;\r\n response.EnsureSuccessStatusCode();\r\n\r\n var contentLength = response.Content.Headers.ContentLength ?? -1L;\r\n using var downloadStream = response.Content.ReadAsStreamAsync().Result;\r\n using var fileStream = new FileStream(installerFilename, FileMode.Create, FileAccess.Write);\r\n \r\n var buffer = new byte[8192];\r\n var totalRead = 0L;\r\n var progress = 0;\r\n var lastProgress = -1;\r\n \r\n int bytesRead;\r\n while ((bytesRead = await downloadStream.ReadAsync(buffer, 0, buffer.Length, Globals.ThisCancellationToken)) \u003E 0)\r\n {\r\n if(Globals.ThisCancellationToken.IsCancellationRequested)\r\n {\r\n httpClient.CancelPendingRequests();\r\n Globals.ThisCancellationToken.ThrowIfCancellationRequested();\r\n }\r\n await fileStream.WriteAsync(buffer, 0, bytesRead, Globals.ThisCancellationToken);\r\n totalRead \u002B= bytesRead;\r\n \r\n if (contentLength \u003E 0)\r\n {\r\n progress = (int)((double)totalRead / contentLength * 100);\r\n if (progress != lastProgress \u0026\u0026 progress % 5 == 0)\r\n {\r\n SetDisplayMessage($\u0022Downloading modloader \\\u0022{Path.GetFileName(installerFilename)}\\\u0022 ({progress}%)\u0022, progress);\r\n lastProgress = progress;\r\n }\r\n }\r\n }\r\n\r\n UpdateProgress(progress);\r\n WriteLog(\u0022Modpack installer downloaded\u0022);\r\n return installerFilename;\r\n }\r\n\r\n public async Task InstallModpack(string installerFilename)\r\n {\r\n WriteLog($\u0022Installing modpack {ModpackName}\u0022, -1);\r\n \r\n var os = Globals.ThisServer.OperatingSystem;\r\n var isWindows = os == EOperatingSystem.Windows;\r\n\r\n if (Globals.ThisServer.OperatingSystem == EOperatingSystem.Linux)\r\n {\r\n var chmod = Process.Start(\u0022chmod\u0022, $\u0022\u002Bx {installerFilename}\u0022);\r\n chmod.WaitForExit();\r\n }\r\n\r\n var args = $\u0022--pack {ModpackId} --version {ModpackVersion} --auto --force --no-java\u0022; //https://github.com/FTBTeam/FTB-Modpack-Issues/issues/6312#issuecomment-2516899020\r\n\r\n //Get java version required by modpack\r\n var manifestUrl = $\u0022https://api.feed-the-beast.com/v1/modpacks/public/modpack/{ModpackId}/{ModpackVersion}\u0022;\r\n using var httpClient = new HttpClient();\r\n var manifest = await httpClient.GetStringAsync(manifestUrl);\r\n var root = JsonSerializer.Deserialize\u003CManifestRoot\u003E(manifest);\r\n var javaVersion = root.targets.FirstOrDefault(t =\u003E t.name == \u0022java\u0022)?.version.Split(\u0027.\u0027)[0];\r\n string javaPath = Path.Combine(Globals.ThisService.RootDirectory, \u0022java\u0022, $\u0022java{javaVersion}\u0022, \u0022bin\u0022, (isWindows? \u0022java.exe\u0022:\u0022java\u0022));\r\n if(!File.Exists(javaPath) \u0026 Environment.GetEnvironmentVariable($\u0022JAVA{javaVersion}\u0022) != null) // Use version installed separatelly\r\n {\r\n javaPath = Environment.GetEnvironmentVariable($\u0022JAVA{javaVersion}\u0022);\r\n }\r\n\r\n if(!File.Exists(javaPath)) throw new Exception($\u0022Could not find folder for Java version {javaVersion}\u0022);\r\n\r\n Process p;\r\n if (isWindows)\r\n {\r\n var startInfo = new ServiceToolStartOptions\r\n {\r\n Executable = installerFilename,\r\n WorkingDirectory = Globals.ThisService.RootDirectory,\r\n Arguments = args\r\n };\r\n startInfo.EnvironmentVariables.Add(\u0022PATH\u0022, Path.GetDirectoryName(javaPath));\r\n \r\n var startResult = await Globals.ServiceManagerService.StartService(Globals.ThisService.ServiceId, startInfo);\r\n\r\n if(!startResult.Success) throw new Exception(\u0022Could not start installer process\u0022);\r\n\r\n p = Process.GetProcessById(startResult.ProcessId);\r\n }\r\n else\r\n {\r\n var psi = new ProcessStartInfo\r\n {\r\n FileName = installerFilename,\r\n Arguments = args,\r\n WorkingDirectory = Globals.ThisService.RootDirectory,\r\n UseShellExecute = false\r\n };\r\n psi.Environment[\u0022PATH\u0022] = Path.GetDirectoryName(javaPath);\r\n p = Process.Start(psi);\r\n if (p == null) throw new Exception(\u0022Could not start installer process\u0022);\r\n }\r\n\r\n p.EnableRaisingEvents=true;\r\n UpdateProgress(-1);\r\n Globals.ThisCancellationToken.Register(() =\u003E {\r\n if(p != null \u0026\u0026 !p.HasExited) p.Kill(true);\r\n });\r\n await p.WaitForExitAsync(Globals.ThisCancellationToken);\r\n var exitCode = p.ExitCode;\r\n p = null;\r\n UpdateProgress(100);\r\n\r\n if (File.Exists(installerFilename)) File.Delete(installerFilename);\r\n \r\n WriteLog($\u0022Installer exit code: {exitCode}\u0022);\r\n if(exitCode!=0)\r\n {\r\n var installerLog = Path.Combine(Globals.ThisService.RootDirectory, \u0022.tca\u0022, \u0022console.log\u0022);\r\n if(File.Exists(installerLog))\r\n {\r\n var newLog = Path.Combine(Globals.ThisService.RootDirectory, Path.GetFileNameWithoutExtension(installerFilename) \u002B \u0022.log\u0022);\r\n await Task.Delay(3000);\r\n File.Move(installerLog, newLog, true);\r\n }\r\n throw new Exception($\u0022Installer failed with exit code {exitCode}. Check logs for details.\u0022);\r\n }\r\n \r\n WriteLog(\u0022Modpack installed\u0022);\r\n }\r\n\r\n public async Task SetCommandLine()\r\n { \r\n WriteLog(\u0022Configuring commandline\u0022);\r\n\r\n // Delete previous command line\r\n var prevCmdLineKey=$\u0022{_provider.InstallPrefix}:CustomModsCmd\u0022;\r\n if(Globals.ThisService.Variables.TryGetValue\u003Clong?\u003E(prevCmdLineKey, out var prevCmdLineId))\r\n {\r\n WriteLog(\u0022Deleting previous commandline\u0022);\r\n await Globals.ServiceManager.DeleteCustomCommandLine(Globals.ThisService.ServiceId, prevCmdLineId.Value);\r\n }\r\n\r\n // Find modloader jar in startup script\r\n var pattern = new Regex(@\u0022((-jar .*jar|@libraries/net/.*.txt|@libraries\\\\net\\\\.*.txt))\u0022);\r\n var extension = Globals.ThisServer.OperatingSystem == EOperatingSystem.Windows ? \u0022bat\u0022 : \u0022sh\u0022;\r\n var startupScript = Path.Combine(Globals.ThisService.RootDirectory, $\u0022start.{extension}\u0022);\r\n \r\n if (!File.Exists(startupScript))\r\n startupScript = Path.Combine(Globals.ThisService.RootDirectory, $\u0022startserver.{extension}\u0022);\r\n \r\n if (!File.Exists(startupScript))\r\n startupScript = Path.Combine(Globals.ThisService.RootDirectory, $\u0022run.{extension}\u0022);\r\n\r\n string modloaderJar = null;\r\n foreach (var line in File.ReadLines(startupScript))\r\n {\r\n var match = pattern.Match(line);\r\n if (match.Success)\r\n {\r\n modloaderJar = match.Groups[1].Value;\r\n break;\r\n }\r\n }\r\n\r\n var variables = new TCAdminVars() {\r\n {_provider.JarVariableName, modloaderJar}\r\n };\r\n\r\n var jarVar = $\u0022${{{_provider.JarVariableName}}}\u0022;\r\n var cmdLineId = await Globals.ServiceManager.UpdateCustomCommandLine(null, Globals.ThisService.ServiceId, 0, Globals.ThisTaskStep.DisplayName, variables);\r\n var cmdLine = await Globals.ServiceManager.GetCustomCommandLine(Globals.ThisService.ServiceId, cmdLineId);\r\n cmdLine.Name=Globals.ThisTaskStep.DisplayName;\r\n cmdLine.OverrideDefault=true;\r\n \r\n if (!modloaderJar.Contains(\u0022@\u0022))\r\n {\r\n cmdLine.CommandLine = cmdLine.CommandLine.Replace(\u0022-jar\u0022, string.Empty);\r\n }\r\n else\r\n {\r\n cmdLine.CommandLine = cmdLine.CommandLine.Replace($\u0022-jar {jarVar}\u0022, modloaderJar);\r\n }\r\n\r\n await Globals.ServiceManager.ApplyCustomCommandLine(null, Globals.ThisService.ServiceId, cmdLine.CmdLineId, false);\r\n // GameServiceManager \u2B06\uFE0F updates ThisGameService.Variables so we have to reload it for ServiceManager\r\n Globals.ThisService.Variables = Globals.ServiceManager.TCAdminDbContext.Services\r\n .Where(x =\u003E x.ServiceId == Globals.ThisService.ServiceId)\r\n .Select(x =\u003E x.Variables)\r\n .FirstOrDefault();\r\n\r\n Globals.ThisService.Variables[prevCmdLineKey] = cmdLine.CmdLineId;\r\n Globals.ThisService.Variables[$\u0022{_provider.InstallPrefix}:Type\u0022] = \u0022FTB\u0022;\r\n await Globals.GameServiceManager.UpdateAsync(Globals.ThisService, true, default);\r\n }\r\n\r\n public string GetLevelName(string propertiesFile)\r\n {\r\n if (!File.Exists(propertiesFile)) return null;\r\n \r\n foreach (var line in File.ReadLines(propertiesFile))\r\n {\r\n if (line.StartsWith(\u0022level-name=\u0022))\r\n {\r\n var levelName = line.Split(\u0027=\u0027, 2)[1].Trim();\r\n WriteLog($\u0022Level name detected as {levelName}\u0022);\r\n return levelName;\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n public void DeleteWorldFolders(string levelName)\r\n {\r\n if (string.IsNullOrEmpty(levelName))\r\n {\r\n WriteLog(\u0022Level name is blank. Cannot delete world folders\u0022);\r\n return;\r\n }\r\n \r\n WriteLog(\u0022Deleting world folders\u0022);\r\n var basePath = Path.Combine(Globals.ThisService.RootDirectory, levelName);\r\n var folders = new List\u003Cstring\u003E\r\n {\r\n basePath,\r\n basePath \u002B \u0022_the_end\u0022,\r\n basePath \u002B \u0022_nether\u0022\r\n };\r\n \r\n foreach (var folder in folders)\r\n {\r\n if (Directory.Exists(folder))\r\n {\r\n Directory.Delete(folder, true);\r\n WriteLog($\u0022Deleted folder and its contents: {folder}\u0022);\r\n }\r\n else\r\n {\r\n WriteLog($\u0022Folder does not exist: {folder}\u0022);\r\n }\r\n }\r\n }\r\n}\r\n\r\nclass ManifestRoot\r\n{\r\n public List\u003CTarget\u003E targets { get; set; }\r\n}\r\n\r\nclass Target\r\n{\r\n public string version { get; set; }\r\n public int id { get; set; }\r\n public string name { get; set; }\r\n public string type { get; set; }\r\n public long updated { get; set; }\r\n}", + "runImpersonated": false, + "stopService": false, + "ignoreErrors": false, + "order": -1, + "icon": "\u003C!-- Background square for the blocky Minecraft feel --\u003E \u003Crect x=\u00222\u0022 y=\u00222\u0022 width=\u002220\u0022 height=\u002220\u0022 fill=\u0022#4CAF50\u0022 stroke=\u0022#2E2E2E\u0022 stroke-width=\u00221\u0022/\u003E \u003C!-- Stylized beast head (simplified wolf-like shape) --\u003E \u003Cpath d=\u0022M8 6 h8 v2 h-2 v6 h-4 v-6 h-2 z\u0022 fill=\u0022#FFFFFF\u0022/\u003E \u003C!-- Ears or horns for the beast --\u003E \u003Crect x=\u00226\u0022 y=\u00224\u0022 width=\u00222\u0022 height=\u00223\u0022 fill=\u0022#FFFFFF\u0022/\u003E \u003Crect x=\u002216\u0022 y=\u00224\u0022 width=\u00222\u0022 height=\u00223\u0022 fill=\u0022#FFFFFF\u0022/\u003E \u003C!-- Eyes --\u003E \u003Crect x=\u00229\u0022 y=\u00229\u0022 width=\u00222\u0022 height=\u00222\u0022 fill=\u0022#000000\u0022/\u003E \u003Crect x=\u002213\u0022 y=\u00229\u0022 width=\u00222\u0022 height=\u00222\u0022 fill=\u0022#000000\u0022/\u003E \u003C!-- FTB text in pixelated style --\u003E \u003Cpath d=\u0022M6 16 h2 v4 h-2 z M9 16 h2 v2 h2 v2 h-4 z M14 16 h2 v4 h-2 z\u0022 fill=\u0022#FFFFFF\u0022/\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "887301d0-c078-4beb-9e21-188f6ef84cc2" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Windows", + "Linux" + ], + "scriptEvents": [ + "AfterCustomModInstalled" + ], + "name": "Install Modpack", + "code": "//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing TCAdmin.SDK.Database.Entities;\r\n\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\n// Not a mod pack\r\nvar providerId = Globals.Variables.Parse\u003Cint\u003E(\u0022ProviderId\u0022);\r\nif(providerId != 3) return;\r\n\r\nvar modType = Globals.Variables.Parse\u003Cstring\u003E(\u0022ModType\u0022);\r\n\r\nvar script = Globals.ThisGame.Scripts.SingleOrDefault(x=\u003E x.Code.IndexOf($\u0022{modType.ToUpper()}-MODPACK-INSTALL\u0022) !=-1);\r\n\r\nif(script== null) throw new Exception($\u0022Could not find install script for mod type {modType}\u0022);\r\n\r\nawait Globals.ScriptingEnvironment.ExecuteScript(Globals.ThisService, script, null, Globals.ThisCancellationToken);", + "runImpersonated": false, + "stopService": false, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cg stroke-width=\u00220\u0022\u003E\u003C/g\u003E\u003Cg stroke-linecap=\u0022round\u0022 stroke-linejoin=\u0022round\u0022\u003E\u003C/g\u003E\u003Cg\u003E \u003Cg id=\u0022Continuous-Integration-Filled\u0022\u003E \u003Cpath d=\u0022M22.91,6.66v11.88L13,23.5V11.62L22.91,6.66z M12,9.88l9.88-4.94L12,0L2.12,4.94L12,9.88z M11,11.62L1.09,6.66v11.88 L11,23.5V11.62z\u0022\u003E\u003C/path\u003E \u003C/g\u003E \u003C/g\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "d4355da9-6efe-4119-b653-6e17b4b52c57" + } + }, + { + "script": { + "allGames": false, + "global": false, + "scriptEngine": "CSharp", + "operatingSystem": [ + "Linux", + "Windows" + ], + "scriptEvents": [ + "DownloadGameFiles" + ], + "name": "Download game files", + "code": "//refAssemblies: TCAdmin.SDK.dll, TCAdmin.GameHosting.SDK.dll, TCAdmin.Scripting.dll, TCAdmin.Monitor.dll, System.IO.Compression.dll, System.Formats.Tar.dll\r\nusing System.Formats.Tar;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nusing System.IO;\r\nusing System.IO.Compression;\r\nusing System.Net.Http;\r\nusing TCAdmin.SDK.Misc.Extensions;\r\n\r\nvar Globals = new TCAdmin.Scripting.Engines.Addons.CSharpGameGlobals(); // DO NOT MODIFY THIS LINE\r\n\r\nasync Task Download(string url, string downloadFileName, CancellationToken cancellationToken)\r\n{\r\n using(HttpClient downloadClient = new HttpClient())\r\n {\r\n await downloadClient.DownloadFile(url, downloadFileName, cancellationToken);\r\n }\r\n}\r\n\r\n// download minecraft_server.jar\r\nvar saveFolder = Path.Combine(Globals.ThisServer.Configuration.GameFilesPath, Globals.Globals.ThisGame.FileAndDirectoryConfig.AutoSetupFolderName);\r\nvar filePath = Path.Combine(saveFolder, \u0022minecraft_server.jar\u0022);\r\nif(!Directory.Exists(saveFolder)) Directory.CreateDirectory(saveFolder);\r\nawait Download(\u0022http://downloads.tcadmin.net/games/minecraft.aspx\u0022, filePath, Globals.ThisCancellationToken);\r\nGlobals.ScriptConsole.WriteLine(\u0022Download of minecraft_server.jar completed successfully!\u0022);\r\n", + "runImpersonated": false, + "stopService": false, + "ignoreErrors": false, + "order": 0, + "icon": "\u003Cpath d=\u0022M0 0h24v24H0z\u0022 fill=\u0022none\u0022/\u003E\u003Cpath d=\u0022M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z\u0022/\u003E", + "scriptInputVariables": [], + "scriptRoles": [], + "editor": [], + "identifier": "f2e58de4-0910-44a5-9243-496a60db3ec9" + } + } + ], + "customModProviderConfigs": [ + { + "providerId": 1, + "gameId": 2, + "enabled": false, + "configuration": { + "SingleIcon": false + } + }, + { + "providerId": 2, + "gameId": 2, + "enabled": false, + "configuration": {} + }, + { + "providerId": 3, + "gameId": 2, + "enabled": true, + "configuration": { + "Private.JarVariable": "Jar" + } + }, + { + "providerId": 4, + "gameId": 2, + "enabled": true, + "configuration": { + "ModsPath": "mods", + "Private.CurseGameId": 432 + } + } + ], + "gamesCustomLinks": [], + "updates": [], + "createdDate": "2026-01-14T20:59:31.689707", + "modifiedDate": "2026-06-17T21:41:21.297749", + "metadata": { + "Notes": "", + "notes": "Game imported from plugin TCA.Games.MC version 1.0.4.Game imported from plugin TCA.Games.MC version 1.0.8.Game imported from plugin TCA.Games.MC version 1.0.10.Game imported from plugin TCA.Games.MC version 1.0.11.Game imported from plugin TCA.Games.MC version 1.0.13.", + "PluginVersion": "1.0.13", + "PluginIdentifier": "TCA.Games.MC" + }, + "backupConfig": { + "enabled": true, + "backupType": "Incremental", + "compressBackup": true, + "includedPaths": [ + "server.properties", + "eula.txt", + "world/**/", + "world_nether/**/", + "world_the_end/**/", + "plugins/**/*.yml", + "plugins/**/*.json", + "plugins/**/*.db", + "ops.json", + "whitelist.json", + "banned-players.json", + "banned-ips.json", + "bukkit.yml", + "spigot.yml", + "paper-global.yml", + "paper-world-defaults.yml", + "permissions.yml", + "usercache.json" + ], + "maxBackupCount": 5 + }, + "rolePermissions": [ + { + "roleId": 2, + "module": "BpFeature", + "permission": "Control", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "ConfigFiles", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "PredefCmdlines", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "CustomCmdlines", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "FileManager", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "FTP", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "ScheduledTasks", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Console", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Logs", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "ServiceActivity", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "CpuStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "MemoryStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "NetworkStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "LiveStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Reinstall", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Mods", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "PlayerStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "ServiceSettings", + "moduleData": "", + "granted": false + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-WebRequest", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-SendCommand", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-FtpUpload", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-Extract", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-DiscordWebhook", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-DeleteFolder", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-DeleteFile", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-CreateFile", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-Compress", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "CustomScripts", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Backups", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "KillService", + "moduleData": "", + "granted": true + }, + { + "roleId": 2, + "module": "BpFeature", + "permission": "Block-ServiceControl", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "ServiceActivity", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "ScheduledTasks", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Reinstall", + "moduleData": "", + "granted": false + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "PredefCmdlines", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "PlayerStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "NetworkStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Mods", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "MemoryStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Logs", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "LiveStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "FTP", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "FileManager", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "CustomCmdlines", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "CpuStats", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Control", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Console", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "ConfigFiles", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "ServiceSettings", + "moduleData": "", + "granted": false + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-WebRequest", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-SendCommand", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-FtpUpload", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-Extract", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-DiscordWebhook", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-DeleteFolder", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-DeleteFile", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-CreateFile", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-Compress", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "CustomScripts", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Backups", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "KillService", + "moduleData": "", + "granted": true + }, + { + "roleId": 3, + "module": "BpFeature", + "permission": "Block-ServiceControl", + "moduleData": "", + "granted": true + } + ], + "mods": [], + "queryMonitoringConfig": { + "enabled": false, + "checkInterval": "00:05:00", + "startupGracePeriod": "00:10:00", + "retryDelay": "00:00:30", + "failureThreshold": 3, + "failureAction": "Restart", + "maxFailureAction": "Disable", + "logActivityOnFailure": true, + "slotDetectionEnabled": false, + "slotDetectionAction": "None", + "additionalAllowedSlots": 0, + "privateDetectionEnabled": false, + "privateDetectionAction": "None", + "brandDetectionEnabled": false, + "brandDetectionAction": "None", + "brandedText": "", + "brandedTextAtEnd": true, + "brandedTextAddSpace": true, + "brandRegex": "", + "brandRegexCaseInsensitive": true, + "rules": [] + }, + "fastDLConfig": { + "enabled": false, + "relativeRoot": "", + "urlExpression": "", + "includePatterns": [], + "excludePatterns": [], + "syncOnServiceCreate": false, + "autoSyncOnFileChange": false, + "compression": "None", + "requiresHttps": false, + "stripPaths": [] + }, + "environmentVariables": [] +} \ No newline at end of file diff --git a/manifests/games/Minecraft/1.0.14/manifest.yaml b/manifests/games/Minecraft/1.0.14/manifest.yaml new file mode 100644 index 0000000..6aca4ef --- /dev/null +++ b/manifests/games/Minecraft/1.0.14/manifest.yaml @@ -0,0 +1,29 @@ +identifier: "TCA.Games.MC" +name: "Minecraft" +version: "1.0.14" +author: "TCAdmin" +shortDescription: "Minecraft game config for TCAdmin 3" +longDescription: | + # Minecraft (MC) + + **Minecraft (MC)** is a 3D sandbox adventure game developed by Mojang Studios where players can interact with a fully customizable three-dimensional world made of blocks and entities. Its diverse gameplay options allow players to choose the way they play, creating countless possibilities. + + ## Key Features + - **Minecraft modpacks** + - **Command line manager** + + ## What's new in 1.0.13 + - Added support for Java 25 + +icon: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Logo_Minecraft.png/960px-Logo_Minecraft.png" +images: + - "https://img.primaservers.dk/minecraft/index.php" +tags: + - "Game" + - "Windows" + - "Linux" +files: + - path: "Minecraft - Windows.json" + type: "GameConfig" + - path: "Minecraft - Linux.json" + type: "GameConfig"