384 lines
10 KiB
Dart
384 lines
10 KiB
Dart
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:mc_rcon_dart/mc_rcon_dart.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<void> 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<void> 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<void> createModFolderIfNotExists() async {
|
|
if (Directory(getModPath()).existsSync()) {
|
|
return;
|
|
} else {
|
|
await Directory(getModPath()).create(recursive: true);
|
|
}
|
|
}
|
|
|
|
Future<void> 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<ProcessResult> 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<void> 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<void> writeOutModListFile() async {
|
|
await createServerModFolderIfNotExists();
|
|
var file = getModListFile();
|
|
|
|
List<String> 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<void> initializeProtonPrefix() async {
|
|
runProton("echo", ["hello"]);
|
|
}
|
|
|
|
Future<bool> sendRconCommand(String command) async {
|
|
try {
|
|
createSocket("127.0.0.1", port: inst!.serverSettings.RconPort);
|
|
|
|
if (inst!.serverSettings.RconPassword.isNotEmpty)
|
|
login(inst!.serverSettings.RconPassword);
|
|
|
|
return sendCommand(command);
|
|
} finally {
|
|
close();
|
|
}
|
|
}
|
|
|
|
Future<void> 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<void> 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<int>;
|
|
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<int>;
|
|
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);
|
|
}
|
|
}
|