import 'dart:io'; import 'package:archive/archive.dart'; import 'package:dio/dio.dart'; import 'package:libac_flutter/nbt/NbtIo.dart'; import 'package:libac_flutter/nbt/NbtUtils.dart'; import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; import 'package:libac_flutter/nbt/impl/StringTag.dart'; import 'package:libac_flutter/packets/packets.dart'; import 'package:libac_flutter/utils/IOTools.dart'; import 'package:libac_flutter/utils/uuid/NbtUUID.dart'; import 'package:libac_flutter/utils/uuid/UUID.dart'; import 'package:rcon/rcon.dart'; import 'package:servermanager/statemachine.dart'; import 'package:servermanager/structs/credentials.dart'; import 'package:servermanager/structs/mod.dart'; import 'package:servermanager/structs/settingsEntry.dart'; import '../proton.dart'; class Settings { final String windows = "https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip"; final String linux = "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz"; final String Base2FAPath = "https://github.com/zontreck/steamcmd-2fa/releases/download/0.2.0/steamcmd-2fa"; final String PROTON_URL = "https://github.com/GloriousEggroll/proton-ge-custom/releases/download/GE-Proton9-5/GE-Proton9-5.tar.gz"; Settings._(); static final Settings Instance = Settings._(); bool server = true; String steamcmd_path = ""; String game_path = ""; String proton_path = ""; String base_path = ""; bool FTS = true; Credentials serverLoginCreds = Credentials(username: "admin", password: "changeMe123"); UUID remoteLoginToken = UUID.ZERO; PacketClient? client; StateMachine subsys = StateMachine(); factory Settings() { return Instance; } CompoundTag serialize() { CompoundTag tag = CompoundTag(); tag.put("steamcmd", StringTag.valueOf(steamcmd_path)); tag.put("game", StringTag.valueOf(game_path)); tag.put("proton", StringTag.valueOf(proton_path)); tag.put("base", StringTag.valueOf(base_path)); NbtUtils.writeBoolean(tag, "fts", FTS); tag.put("server_creds", serverLoginCreds.save()); NbtUtils.writeUUID(tag, "token", NbtUUID.fromUUID(remoteLoginToken)); if (inst != null) tag.put("main", inst!.serialize()); return tag; } void deserialize(CompoundTag tag) { // Verify Remote Login Token UUID ID = NbtUtils.readUUID(tag, "token").toUUID(); if (ID.toString() != remoteLoginToken.toString()) { // Invalid session print( "Invalid login session detected, or two admins are connected at once"); } steamcmd_path = tag.get("steamcmd")!.asString(); game_path = tag.get("game")!.asString(); proton_path = tag.get("proton")!.asString(); base_path = tag.get("proton")!.asString(); FTS = NbtUtils.readBoolean(tag, "fts"); // First Time Setup. // FTS should be disabled by the client when sending it back to the server in a C2SApplySettingsPacket serverLoginCreds = Credentials.deserialize(tag.get("server_creds")!.asCompoundTag()); if (tag.containsKey("main")) { inst = SettingsEntry.deserialize(tag.get("main")!.asCompoundTag()); } } SettingsEntry? inst; Future Read() async { if (!server) return; try { var tag = await NbtIo.read("settings.dat"); inst = SettingsEntry.deserialize(tag.get("entry") as CompoundTag); serverLoginCreds = Credentials.deserialize( tag.get(Credentials.TAG_NAME)!.asCompoundTag()); FTS = NbtUtils.readBoolean(tag, "fts"); } catch (E) { print("No existing settings file found, initializing default settings"); inst = SettingsEntry(); inst!.steam_creds = Credentials( username: "", password: "", ); serverLoginCreds = Credentials( username: "admin", password: "changeMe123", ); FTS = true; } } void Write() { if (!server) { return; // safeguard against writing to a settings file on the client } if (inst == null) return; CompoundTag tag = CompoundTag(); tag.put("entry", inst!.serialize()); tag.put(Credentials.TAG_NAME, serverLoginCreds.save()); NbtUtils.writeBoolean(tag, "fts", FTS); NbtIo.write("settings.dat", tag); } Future Open() async { Close(); Instance.Read(); } static void Close() async { Instance.Write(); Instance.inst = null; } String getServerPath() { return game_path; } bool checkInitDone() { if (File("$steamcmd_path${Platform.pathSeparator}cxinit").existsSync()) { return true; } else { return false; } } String getSteamCmd() { return "$steamcmd_path${Platform.pathSeparator}steamcmd${Platform.isWindows ? ".exe" : ".sh"}"; } String getSteamCmdPath() { return steamcmd_path; } String getModPath() { return PathHelper(pth: base_path).resolve("mods").build(); } String getModJailPath() { return PathHelper(pth: base_path).resolve("mods.jail").build(); } String getProtonPath() { return PathHelper(pth: base_path).resolve("proton").build(); } String getProtonExecutablePath() { return PathHelper(pth: base_path) .resolve("proton") .resolve("GE-Proton9-5") .resolve("proton") .build(); } Future createModFolderIfNotExists() async { if (Directory(getModPath()).existsSync()) { return; } else { await Directory(getModPath()).create(recursive: true); } } Future createModJailFolderIfNotExists() async { if (Directory(getModJailPath()).existsSync()) { return; } else { await Directory(getModJailPath()).create(recursive: true); } } bool serverInstalled() { return File( "${getServerPath()}${Platform.pathSeparator}ConanSandboxServer.exe") .existsSync(); } Future RunUpdate({bool valid = true}) { return Process.run(getSteamCmd(), [ "+@sSteamCmdForcePlatformType", "windows", "+force_install_dir", getServerPath(), "+login", "anonymous", "+app_update", "443030", "public", if (valid) "validate", "+quit" ]); } Future createServerModFolderIfNotExists() async { Directory dir = Directory(PathHelper(pth: getServerPath()) .resolve("ConanSandbox") .resolve("Mods") .build()); if (await dir.exists()) { return; } else { await dir.create(recursive: true); } } File getModListFile() { return File(PathHelper(pth: getServerPath()) .resolve("ConanSandbox") .resolve("Mods") .resolve("modlist.txt") .build()); } Future writeOutModListFile() async { await createServerModFolderIfNotExists(); var file = getModListFile(); List paths = []; for (Mod mod in inst!.mods) { var pth = PathHelper(pth: getModPath()) .resolve("steamapps") .resolve("workshop") .resolve("content") .resolve("440900") .resolve("${mod.mod_id}") .resolve(mod.mod_pak) .build(); if (Platform.isWindows) { paths.add(pth); } else { var rpl = []; // proton's rpl.addAll(pth.split('/')); rpl[0] = "Z:"; paths.add(rpl.join("\\")); } } await file.writeAsString(paths.join("\n"), flush: true, mode: FileMode.writeOnly); } Future initializeProtonPrefix() async { runProton("echo", ["hello"]); } Future sendRconCommand(String command) async { Client cli = await Client.create("127.0.0.1", inst!.serverSettings.RconPort); Message msg = Message.create(cli, PacketType.command, command); return cli.send(msg).payload; } Future initializeProton() async { Dio dio = Dio(); print("Downloading proton..."); final path = PathHelper(pth: getProtonPath()).resolve("proton.tar.gz"); await dio.download(PROTON_URL, path.build()); String oldWD = Directory.current.path; Directory.current = getProtonPath(); Process.runSync("tar", ["-xvf", "proton.tar.gz"]); Process.runSync("rm", ["-f", "proton.tar.gz"]); Directory.current = oldWD; // Restore the old working directory print("Finished!"); } Future initializeSteamCmd() async { if (File(PathHelper(pth: getSteamCmdPath()).resolve("cxinit").build()) .existsSync()) { print( "Skipping SteamCmd and Proton initialization, already marked as ready"); return; } // Yes, Proceed var x = Directory(steamcmd_path); try { await x.delete(recursive: true); } catch (e) {} await x.create(recursive: true); Directory.current = Directory(steamcmd_path); final dio = Dio(); Process proc; if (Platform.isWindows) { // Download zip file final path = "$steamcmd_path${Platform.pathSeparator}windows.zip"; final reply = await dio.download(windows, path); final bytes = File(path).readAsBytesSync(); final arc = ZipDecoder().decodeBytes(bytes); print("SteamCmd downloaded. Performing initial update"); for (final file in arc) { final name = file.name; if (file.isFile) { final data = file.content as List; File(name) ..createSync(recursive: true) ..writeAsBytesSync(data); } else { Directory(name).create(recursive: true); } } Process.runSync("echo", ["X", ">", "cxinit"]); proc = await Process.start("steamcmd.exe", ["+quit"]); print("Completed."); } else { // Download tgz file final path = "$steamcmd_path${Platform.pathSeparator}linux.tgz"; final reply = await dio.download(linux, path); final bytes = File(path).readAsBytesSync(); final arc = GZipDecoder().decodeBytes(bytes); final arc2 = TarDecoder().decodeBytes(arc); print("SteamCmd downloaded. Performing initial update"); for (final file in arc2) { final name = file.name; if (file.isFile) { final data = file.content as List; File(name) ..createSync(recursive: true) ..writeAsBytesSync(data); } else { Directory(name).create(recursive: true); } } Process.runSync("chmod", ["+x", "steamcmd.sh"]); Process.runSync("chmod", ["+x", "linux32/steamcmd"]); Process.runSync("touch", ["cxinit"]); Process.runSync("./steamcmd.sh", ["+quit"]); print("Completed. Initializing Proton"); await initializeProton(); } Directory.current = Directory(game_path); } }