Refactor project, get server-client coms working

This commit is contained in:
zontreck 2024-05-23 00:11:14 -07:00
parent 6fadb92a26
commit 7cddfd2de6
22 changed files with 730 additions and 677 deletions

View file

@ -1,8 +1,28 @@
import 'dart:io';
import 'package:libac_flutter/packets/packets.dart';
import 'package:libac_flutter/utils/IOTools.dart';
import 'package:servermanager/packets/ClientPackets.dart';
import 'package:servermanager/structs/settings.dart';
void main() async {
ClientPackets.register();
// Set up paths
Settings settings = Settings();
settings.Read();
PathHelper helper = PathHelper(pth: Directory.current.path);
helper = helper.resolve("data").mkdir();
PathHelper steamCmd =
PathHelper.builder(helper.build()).resolve("steamcmd").mkdir();
PathHelper game = PathHelper.builder(helper.build()).resolve("game").mkdir();
settings.game_path = game.build();
settings.steamcmd_path = steamCmd.build();
settings.Write();
settings.initializeSteamCmd();
await PacketServer.start();

View file

@ -1,14 +1,9 @@
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/material.dart';
import 'package:libac_flutter/utils/IOTools.dart';
import 'package:servermanager/autorestart.dart';
import 'package:servermanager/mod.dart';
import 'package:servermanager/serversettings.dart';
import 'package:servermanager/settings.dart';
import 'package:servermanager/statemachine.dart';
import 'package:servermanager/structs/mod.dart';
import 'package:servermanager/structs/settings.dart';
Future<void> doDownloadMods(String modsFolder) async {
Settings settings = Settings();
@ -133,436 +128,3 @@ Future<List<Mod>> doScanMods(String modsFolder) async {
}
return ret;
}
class GameServerPage extends StatefulWidget {
Settings settings;
GameServerPage({super.key, required this.settings});
@override
GameServerPageState createState() => GameServerPageState(settings: settings);
}
class GameServerPageState extends State<GameServerPage> {
Settings settings;
GameServerPageState({required this.settings});
var downloading = false;
late Stream<List<int>> download_stream;
TextEditingController ValueControl = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Conan Exiles Server Manager - Game Server"),
backgroundColor: Color.fromARGB(255, 100, 0, 0),
),
body: WillPopScope(
onWillPop: () async {
if (downloading) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Wait until the download completes")));
return false;
} else {
return true;
}
},
child: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
children: [
ListTile(
title: settings.serverInstalled()
? Text("Update / Validate Server Install")
: Text("Initial Server Download"),
subtitle: settings.serverInstalled()
? Text(
"Validates game files or performs an update. This is done when starting the server as well.")
: Text(
"Download the game server. This is step 1, after having downloaded steamcmd."),
leading: Icon(Icons.numbers),
onTap: () async {
if (downloading) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Wait until the download completes")));
return;
}
if (!settings.isValid()) return;
Directory(settings.getServerPath()).createSync();
setState(() {
downloading = true;
});
// Start server download into folder
await settings.RunUpdate();
setState(() {
downloading = false;
});
},
),
if (downloading)
ListTile(
title: Text("Downloading..."),
leading: Icon(Icons.downloading),
),
ListTile(
title: Text("Mods"),
leading: Icon(Icons.build),
subtitle: Text("Server Mod Management"),
onTap: () {
if (downloading) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Wait until the download completes")));
return;
}
Navigator.pushNamed(context, "/server/mods");
},
),
if (settings.inst!.downloadMods)
ListTile(
title: Text("Download Mods"),
subtitle: Text("Downloads the mods"),
leading: Icon(Icons.download_sharp),
onTap: () async {
setState(() {
downloading = true;
});
await doDownloadMods(settings.getModPath());
setState(() {
downloading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Scanning mods...")));
var mods = await doScanMods(settings.getModPath());
setState(() {
settings.inst!.mods = mods;
settings.Write();
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Mod scanning complete")));
},
),
if (!settings.inst!.downloadMods)
ListTile(
title: Text("Conan Exiles Install Path"),
subtitle:
Text("Set the Steam Library location of Conan Exiles"),
leading: Icon(Icons.folder),
onTap: () async {
// Open the folder select prompt
var path = await getDirectoryPath();
setState(() {
settings.inst!.conanExilesLibraryPath =
path ?? settings.inst!.conanExilesLibraryPath;
if (path == null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
"You must select a valid SteamLibrary folder")));
}
});
},
),
ListTile(
title: Text("Configure AutoRestart"),
leading: Icon(Icons.timer),
onTap: () async {
if (downloading) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Wait until the download completes")));
return;
}
var reply = await Navigator.pushNamed(
context, "/server/autorestart",
arguments: settings.inst!.timer);
setState(() {
settings.inst!.timer = reply as AutomaticRestartInfo;
});
},
),
SwitchListTile(
value: settings.inst!.downloadMods,
onChanged: (value) {
setState(() {
settings.inst!.downloadMods = value;
});
},
title: Text("Automatic Download of Mods"),
subtitle: Text(
"If enabled, downloads mods using steamcmd, if disabled, you must configure the SteamLibrary folder location"),
),
ListTile(
title: Text("Configure Server Ports"),
leading: Icon(Icons.numbers),
onTap: () async {
var reply = await Navigator.pushNamed(
context, "/server/ports",
arguments: settings.inst!.serverSettings);
setState(() {
settings.inst!.serverSettings = reply as ServerSettings;
settings.Write();
});
},
),
ListTile(
title: Text("Status:"),
subtitle: Text(settings.subsys.currentState != States.Inactive
? "Active"
: "Not Running"),
leading: Icon(settings.subsys.currentState != States.Inactive
? Icons.play_arrow
: Icons.error),
onTap: () async {
// Toggle state
setState(() {
if (settings.subsys.currentState == States.Inactive) {
settings.subsys.changeState(States.Starting);
} else
settings.subsys.changeState(States.FullStop);
});
},
),
],
)),
),
);
}
}
class ModManager extends StatefulWidget {
Settings settings;
ModManager({super.key, required this.settings});
@override
ModManagerState createState() => ModManagerState(settings: settings);
}
class ModManagerState extends State<ModManager> {
Settings settings;
ModManagerState({required this.settings});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Conan Exiles Server Manager - Mod Manager"),
backgroundColor: Color.fromARGB(255, 100, 0, 0),
),
body: WillPopScope(
child: ReorderableListView.builder(
onReorder: (oldIndex, newIndex) {
if (oldIndex < newIndex) {
// From top to Bottom
int end = newIndex - 1;
Mod item = settings.inst!.mods[oldIndex];
int i = 0;
int local = oldIndex;
do {
settings.inst!.mods[local] = settings.inst!.mods[++local];
i++;
} while (i < end - oldIndex);
settings.inst!.mods[end] = item;
} else if (oldIndex > newIndex) {
//From bottom to top
Mod item = settings.inst!.mods[oldIndex];
for (int i = oldIndex; i > newIndex; i--) {
settings.inst!.mods[i] = settings.inst!.mods[i - 1];
}
settings.inst!.mods[newIndex] = item;
}
setState(() {
settings.Write();
});
},
itemBuilder: (ctx, idx) {
Mod mod = settings.inst!.mods[idx];
return Padding(
key: Key(mod.mod_instance_id()),
padding: EdgeInsets.all(12),
child: ListTile(
title: Text(mod.mod_name),
subtitle: Text("ID: ${mod.mod_id}"),
onTap: () async {
final reply = await Navigator.pushNamed(
context, "/server/mods/edit",
arguments: Mod(
mod_id: mod.mod_id,
mod_name: mod.mod_name,
mod_pak: mod.mod_pak,
mod_hash: mod.mod_hash,
newMod: false));
if (reply != null) {
setState(() {
settings.inst!.mods[idx] = reply as Mod;
});
} else {
setState(() {
settings.inst!.mods.removeAt(idx);
});
}
},
),
);
},
itemCount: settings.inst!.mods.length,
),
onWillPop: () async {
Navigator.pop(context);
return true;
},
),
floatingActionButton: ElevatedButton(
child: Icon(Icons.add),
onPressed: () async {
// Open new mod info screen
final reply = await Navigator.pushNamed(context, "/server/mods/edit",
arguments: Mod(newMod: true));
if (reply != null) {
Mod mod = reply as Mod;
setState(() {
settings.inst!.mods.add(mod);
settings.Write();
});
}
},
),
);
}
}
class ModPage extends StatelessWidget {
bool initDone = false;
TextEditingController id = TextEditingController();
TextEditingController name = TextEditingController();
String instance = "";
bool isNewMod = false;
bool willDelete = false;
String pak = "Not initialized";
String hash = "";
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as Mod?;
if (!initDone) {
initDone = true;
if (args != null) {
id.text = args.mod_id.toString();
name.text = args.mod_name;
isNewMod = args.newMod;
instance = args.mod_instance_id();
pak = args.mod_pak;
hash = args.mod_hash;
}
}
return Scaffold(
appBar: AppBar(
title: Text("Mod Editor"),
backgroundColor: Color.fromARGB(255, 100, 0, 0),
),
body: WillPopScope(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(children: [
Row(
children: [
SizedBox(
width: 150,
child: ListTile(
leading: Icon(Icons.abc_rounded),
title: Text("Mod Name"),
)),
Expanded(
child: TextField(
controller: name,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4)))),
)
],
),
SizedBox(
height: 16,
),
Row(
children: [
SizedBox(
width: 150,
child: ListTile(
leading: Icon(Icons.perm_identity),
title: Text("Mod ID")),
),
Expanded(
child: TextField(
controller: id,
keyboardType: TextInputType.number,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4))),
))
],
),
SizedBox(
height: 16,
),
ListTile(
title: Text("Mod Instance ID"),
subtitle: Text(instance),
),
ListTile(
title: Text("Mod Pak File: $pak"),
subtitle:
Text("Mod pak file name as detected during downloading"),
),
ListTile(
title: Text("Mod Hash"),
subtitle: Text("$hash"),
),
if (!isNewMod)
ElevatedButton(
onPressed: () {
willDelete = true;
Navigator.pop(context);
},
child: Row(
children: [
Icon(Icons.delete),
SizedBox(
width: 4,
),
Text("Remove Mod")
],
))
]),
),
onWillPop: () async {
int idVal = 0;
try {
idVal = int.parse(id.text);
} catch (E) {}
if (willDelete) {
Navigator.pop(context, null);
} else {
Navigator.pop(context,
Mod(mod_id: idVal, mod_name: name.text, newMod: false));
}
return true;
},
),
);
}
}

View file

@ -1,13 +1,18 @@
import 'package:flutter/material.dart';
import 'package:libac_flutter/nbt/NbtUtils.dart';
import 'package:libac_flutter/packets/packets.dart';
import 'package:servermanager/Constants.dart';
import 'package:servermanager/autorestart.dart';
import 'package:servermanager/game.dart';
import 'package:servermanager/home.dart';
import 'package:servermanager/proton.dart';
import 'package:servermanager/serversettings.dart';
import 'package:servermanager/settings.dart';
import 'package:servermanager/steamcmd.dart';
import 'package:libac_flutter/utils/uuid/UUID.dart';
import 'package:servermanager/packets/ClientPackets.dart';
import 'package:servermanager/pages/Constants.dart';
import 'package:servermanager/pages/GameServerPage.dart';
import 'package:servermanager/pages/ModManager.dart';
import 'package:servermanager/pages/autorestart.dart';
import 'package:servermanager/pages/home.dart';
import 'package:servermanager/pages/steamcmd.dart';
import 'package:servermanager/structs/settings.dart';
import 'pages/Proton.dart';
import 'pages/ServerSettings.dart';
Future<void> main() async {
runApp(MyApp());
@ -57,6 +62,21 @@ class ServerPage extends StatelessWidget {
Settings settings = Settings();
settings.client = PacketClient();
await settings.client!.startConnect(serverIP.text);
C2SLoginPacket login = C2SLoginPacket();
login.username = username.text;
login.password = password.text;
S2CResponse response = await settings.client!.send(login);
bool valid = NbtUtils.readBoolean(response.contents, "valid");
if (valid) {
settings.remoteLoginToken =
UUID.parse(response.contents.get("token")!.asString());
Navigator.pushNamed(context, "/home");
} else {
print("Login not valid");
}
},
child: Text("Login"),
),

View file

@ -7,7 +7,7 @@ import 'package:libac_flutter/nbt/impl/StringTag.dart';
import 'package:libac_flutter/packets/packets.dart';
import 'package:libac_flutter/utils/uuid/NbtUUID.dart';
import 'package:libac_flutter/utils/uuid/UUID.dart';
import 'package:servermanager/settings.dart';
import 'package:servermanager/structs/settings.dart';
class ClientPackets {
static void register() {

View file

@ -0,0 +1,215 @@
import 'dart:io';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/material.dart';
import '../game.dart';
import '../statemachine.dart';
import '../structs/autorestarts.dart';
import '../structs/serversettings.dart';
import '../structs/settings.dart';
class GameServerPage extends StatefulWidget {
Settings settings;
GameServerPage({super.key, required this.settings});
@override
GameServerPageState createState() => GameServerPageState(settings: settings);
}
class GameServerPageState extends State<GameServerPage> {
Settings settings;
GameServerPageState({required this.settings});
var downloading = false;
late Stream<List<int>> download_stream;
TextEditingController ValueControl = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Conan Exiles Server Manager - Game Server"),
backgroundColor: Color.fromARGB(255, 100, 0, 0),
),
body: WillPopScope(
onWillPop: () async {
if (downloading) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Wait until the download completes")));
return false;
} else {
return true;
}
},
child: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
children: [
ListTile(
title: settings.serverInstalled()
? Text("Update / Validate Server Install")
: Text("Initial Server Download"),
subtitle: settings.serverInstalled()
? Text(
"Validates game files or performs an update. This is done when starting the server as well.")
: Text(
"Download the game server. This is step 1, after having downloaded steamcmd."),
leading: Icon(Icons.numbers),
onTap: () async {
if (downloading) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Wait until the download completes")));
return;
}
if (!settings.isValid()) return;
Directory(settings.getServerPath()).createSync();
setState(() {
downloading = true;
});
// Start server download into folder
await settings.RunUpdate();
setState(() {
downloading = false;
});
},
),
if (downloading)
ListTile(
title: Text("Downloading..."),
leading: Icon(Icons.downloading),
),
ListTile(
title: Text("Mods"),
leading: Icon(Icons.build),
subtitle: Text("Server Mod Management"),
onTap: () {
if (downloading) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Wait until the download completes")));
return;
}
Navigator.pushNamed(context, "/server/mods");
},
),
if (settings.inst!.downloadMods)
ListTile(
title: Text("Download Mods"),
subtitle: Text("Downloads the mods"),
leading: Icon(Icons.download_sharp),
onTap: () async {
setState(() {
downloading = true;
});
await doDownloadMods(settings.getModPath());
setState(() {
downloading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Scanning mods...")));
var mods = await doScanMods(settings.getModPath());
setState(() {
settings.inst!.mods = mods;
settings.Write();
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Mod scanning complete")));
},
),
if (!settings.inst!.downloadMods)
ListTile(
title: Text("Conan Exiles Install Path"),
subtitle:
Text("Set the Steam Library location of Conan Exiles"),
leading: Icon(Icons.folder),
onTap: () async {
// Open the folder select prompt
var path = await getDirectoryPath();
setState(() {
settings.inst!.conanExilesLibraryPath =
path ?? settings.inst!.conanExilesLibraryPath;
if (path == null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
"You must select a valid SteamLibrary folder")));
}
});
},
),
ListTile(
title: Text("Configure AutoRestart"),
leading: Icon(Icons.timer),
onTap: () async {
if (downloading) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Wait until the download completes")));
return;
}
var reply = await Navigator.pushNamed(
context, "/server/autorestart",
arguments: settings.inst!.timer);
setState(() {
settings.inst!.timer = reply as AutomaticRestartInfo;
});
},
),
SwitchListTile(
value: settings.inst!.downloadMods,
onChanged: (value) {
setState(() {
settings.inst!.downloadMods = value;
});
},
title: Text("Automatic Download of Mods"),
subtitle: Text(
"If enabled, downloads mods using steamcmd, if disabled, you must configure the SteamLibrary folder location"),
),
ListTile(
title: Text("Configure Server Ports"),
leading: Icon(Icons.numbers),
onTap: () async {
var reply = await Navigator.pushNamed(
context, "/server/ports",
arguments: settings.inst!.serverSettings);
setState(() {
settings.inst!.serverSettings = reply as ServerSettings;
settings.Write();
});
},
),
ListTile(
title: Text("Status:"),
subtitle: Text(settings.subsys.currentState != States.Inactive
? "Active"
: "Not Running"),
leading: Icon(settings.subsys.currentState != States.Inactive
? Icons.play_arrow
: Icons.error),
onTap: () async {
// Toggle state
setState(() {
if (settings.subsys.currentState == States.Inactive) {
settings.subsys.changeState(States.Starting);
} else
settings.subsys.changeState(States.FullStop);
});
},
),
],
)),
),
);
}
}

232
lib/pages/ModManager.dart Normal file
View file

@ -0,0 +1,232 @@
import 'package:flutter/material.dart';
import '../structs/mod.dart';
import '../structs/settings.dart';
class ModManager extends StatefulWidget {
Settings settings;
ModManager({super.key, required this.settings});
@override
ModManagerState createState() => ModManagerState(settings: settings);
}
class ModManagerState extends State<ModManager> {
Settings settings;
ModManagerState({required this.settings});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Conan Exiles Server Manager - Mod Manager"),
backgroundColor: Color.fromARGB(255, 100, 0, 0),
),
body: WillPopScope(
child: ReorderableListView.builder(
onReorder: (oldIndex, newIndex) {
if (oldIndex < newIndex) {
// From top to Bottom
int end = newIndex - 1;
Mod item = settings.inst!.mods[oldIndex];
int i = 0;
int local = oldIndex;
do {
settings.inst!.mods[local] = settings.inst!.mods[++local];
i++;
} while (i < end - oldIndex);
settings.inst!.mods[end] = item;
} else if (oldIndex > newIndex) {
//From bottom to top
Mod item = settings.inst!.mods[oldIndex];
for (int i = oldIndex; i > newIndex; i--) {
settings.inst!.mods[i] = settings.inst!.mods[i - 1];
}
settings.inst!.mods[newIndex] = item;
}
setState(() {
settings.Write();
});
},
itemBuilder: (ctx, idx) {
Mod mod = settings.inst!.mods[idx];
return Padding(
key: Key(mod.mod_instance_id()),
padding: EdgeInsets.all(12),
child: ListTile(
title: Text(mod.mod_name),
subtitle: Text("ID: ${mod.mod_id}"),
onTap: () async {
final reply = await Navigator.pushNamed(
context, "/server/mods/edit",
arguments: Mod(
mod_id: mod.mod_id,
mod_name: mod.mod_name,
mod_pak: mod.mod_pak,
mod_hash: mod.mod_hash,
newMod: false));
if (reply != null) {
setState(() {
settings.inst!.mods[idx] = reply as Mod;
});
} else {
setState(() {
settings.inst!.mods.removeAt(idx);
});
}
},
),
);
},
itemCount: settings.inst!.mods.length,
),
onWillPop: () async {
Navigator.pop(context);
return true;
},
),
floatingActionButton: ElevatedButton(
child: Icon(Icons.add),
onPressed: () async {
// Open new mod info screen
final reply = await Navigator.pushNamed(context, "/server/mods/edit",
arguments: Mod(newMod: true));
if (reply != null) {
Mod mod = reply as Mod;
setState(() {
settings.inst!.mods.add(mod);
settings.Write();
});
}
},
),
);
}
}
class ModPage extends StatelessWidget {
bool initDone = false;
TextEditingController id = TextEditingController();
TextEditingController name = TextEditingController();
String instance = "";
bool isNewMod = false;
bool willDelete = false;
String pak = "Not initialized";
String hash = "";
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as Mod?;
if (!initDone) {
initDone = true;
if (args != null) {
id.text = args.mod_id.toString();
name.text = args.mod_name;
isNewMod = args.newMod;
instance = args.mod_instance_id();
pak = args.mod_pak;
hash = args.mod_hash;
}
}
return Scaffold(
appBar: AppBar(
title: Text("Mod Editor"),
backgroundColor: Color.fromARGB(255, 100, 0, 0),
),
body: WillPopScope(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(children: [
Row(
children: [
SizedBox(
width: 150,
child: ListTile(
leading: Icon(Icons.abc_rounded),
title: Text("Mod Name"),
)),
Expanded(
child: TextField(
controller: name,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4)))),
)
],
),
SizedBox(
height: 16,
),
Row(
children: [
SizedBox(
width: 150,
child: ListTile(
leading: Icon(Icons.perm_identity),
title: Text("Mod ID")),
),
Expanded(
child: TextField(
controller: id,
keyboardType: TextInputType.number,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4))),
))
],
),
SizedBox(
height: 16,
),
ListTile(
title: Text("Mod Instance ID"),
subtitle: Text(instance),
),
ListTile(
title: Text("Mod Pak File: $pak"),
subtitle:
Text("Mod pak file name as detected during downloading"),
),
ListTile(
title: Text("Mod Hash"),
subtitle: Text("$hash"),
),
if (!isNewMod)
ElevatedButton(
onPressed: () {
willDelete = true;
Navigator.pop(context);
},
child: Row(
children: [
Icon(Icons.delete),
SizedBox(
width: 4,
),
Text("Remove Mod")
],
))
]),
),
onWillPop: () async {
int idVal = 0;
try {
idVal = int.parse(id.text);
} catch (E) {}
if (willDelete) {
Navigator.pop(context, null);
} else {
Navigator.pop(context,
Mod(mod_id: idVal, mod_name: name.text, newMod: false));
}
return true;
},
),
);
}
}

46
lib/pages/Proton.dart Normal file
View file

@ -0,0 +1,46 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../proton.dart';
import '../structs/settings.dart';
class Proton extends StatefulWidget {
Settings settings;
Proton({super.key, required this.settings});
@override
ProtonState createState() => ProtonState(settings: settings);
}
class ProtonState extends State<Proton> {
Settings settings;
ProtonState({required this.settings});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Proton Manager"),
backgroundColor: Color.fromARGB(255, 100, 0, 0),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
ListTile(
title: Text("Init Prefix"),
subtitle: Text("Resets proton prefix"),
leading: Icon(CupertinoIcons.folder_fill),
onTap: () {
runProton(
"echo", ["hello"]); // Change to "cmd" to execute 'cmd'
},
)
],
),
),
),
);
}
}

View file

@ -1,44 +1,6 @@
import 'package:flutter/material.dart';
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'package:libac_flutter/nbt/impl/IntTag.dart';
import 'package:libac_flutter/nbt/impl/StringTag.dart';
class ServerSettings {
final String RconPassword;
final int RconPort;
final int GamePort;
final int QueryPort;
const ServerSettings(
{required this.RconPassword,
required this.RconPort,
required this.GamePort,
required this.QueryPort});
static ServerSettings deserialize(CompoundTag tag) {
return ServerSettings(
RconPassword: tag.get(TAG_PASSWORD)?.asString() ?? "",
RconPort: tag.get(TAG_RCON_PORT)?.asInt() ?? 25565,
GamePort: tag.get(TAG_GAME_PORT)?.asInt() ?? 0,
QueryPort: tag.get(TAG_QUERY_PORT)?.asInt() ?? 0);
}
CompoundTag serialize() {
CompoundTag tag = CompoundTag();
tag.put(TAG_PASSWORD, StringTag.valueOf(RconPassword));
tag.put(TAG_RCON_PORT, IntTag.valueOf(RconPort));
tag.put(TAG_GAME_PORT, IntTag.valueOf(GamePort));
tag.put(TAG_QUERY_PORT, IntTag.valueOf(QueryPort));
return tag;
}
static const TAG_NAME = "serverSettings";
static const TAG_PASSWORD = "password";
static const TAG_RCON_PORT = "rcon";
static const TAG_GAME_PORT = "game";
static const TAG_QUERY_PORT = "query";
}
import '../structs/serversettings.dart';
class ServerSettingsPage extends StatefulWidget {
ServerSettingsPage({super.key});
@ -72,8 +34,8 @@ class ServerSettingsState extends State<ServerSettingsPage> {
title: Text("Server Settings"),
backgroundColor: Color.fromARGB(255, 100, 0, 0),
),
body: WillPopScope(
onWillPop: () async {
body: PopScope(
onPopInvoked: (v) async {
Navigator.pop(
context,
ServerSettings(
@ -81,7 +43,6 @@ class ServerSettingsState extends State<ServerSettingsPage> {
RconPort: rconPort,
GamePort: gPort,
QueryPort: qPort));
return true;
},
child: Padding(
padding: const EdgeInsets.all(16.0),

View file

@ -1,45 +1,6 @@
import 'package:flutter/material.dart';
import 'package:libac_flutter/nbt/NbtUtils.dart';
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'package:libac_flutter/nbt/impl/IntTag.dart';
import 'package:servermanager/settings.dart';
class AutomaticRestartInfo {
final int hours;
final int minutes;
final int seconds;
final bool enabled;
const AutomaticRestartInfo(
{this.hours = 0,
this.minutes = 0,
this.seconds = 0,
this.enabled = false});
static AutomaticRestartInfo deserialize(CompoundTag tag) {
return AutomaticRestartInfo(
hours: tag.get(TAG_HOURS)?.asInt() ?? 12,
minutes: tag.get(TAG_MINUTES)?.asInt() ?? 0,
seconds: tag.get(TAG_SECONDS)?.asInt() ?? 0,
enabled: NbtUtils.readBoolean(tag, TAG_ENABLED));
}
CompoundTag serialize() {
CompoundTag tag = CompoundTag();
tag.put(TAG_HOURS, IntTag.valueOf(hours));
tag.put(TAG_MINUTES, IntTag.valueOf(minutes));
tag.put(TAG_SECONDS, IntTag.valueOf(seconds));
NbtUtils.writeBoolean(tag, TAG_ENABLED, enabled);
return tag;
}
static const TAG_NAME = "ari";
static const TAG_HOURS = "hours";
static const TAG_MINUTES = "minutes";
static const TAG_SECONDS = "seconds";
static const TAG_ENABLED = "enabled";
}
import 'package:servermanager/structs/autorestarts.dart';
import 'package:servermanager/structs/settings.dart';
class AutoRestartPage extends StatefulWidget {
AutoRestartPage({super.key});

View file

@ -3,8 +3,8 @@ import 'dart:io';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:servermanager/Constants.dart';
import 'package:servermanager/settings.dart';
import 'package:servermanager/pages/Constants.dart';
import 'package:servermanager/structs/settings.dart';
class HomePage extends StatefulWidget {
Settings settings;

View file

@ -1,12 +1,11 @@
import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:servermanager/credentials.dart';
import 'package:servermanager/dialogbox.dart';
import 'package:servermanager/settings.dart';
import 'package:servermanager/pages/dialogbox.dart';
import 'package:servermanager/structs/credentials.dart';
import 'package:servermanager/structs/settings.dart';
class SteamCMD extends StatefulWidget {
Settings settings;
@ -17,14 +16,6 @@ class SteamCMD extends StatefulWidget {
}
class SteamCMDState extends State<SteamCMD> {
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";
Settings settings = Settings();
SteamCMDState({required this.settings});
@ -52,75 +43,7 @@ class SteamCMDState extends State<SteamCMD> {
"This action will delete any existing copy of SteamCMD and download a fresh copy. \nAre you sure?",
changed: (X) {},
onSubmit: () async {
// Yes, Proceed
var x = Directory(settings.steamcmd_path);
try {
await x.delete(recursive: true);
} catch (e) {}
await x.create(recursive: true);
Directory.current = Directory(settings.steamcmd_path);
final dio = Dio();
Process proc;
if (Platform.isWindows) {
// Download zip file
final path =
"${settings.steamcmd_path}${Platform.pathSeparator}windows.zip";
final reply = await dio.download(windows, path);
final bytes = File(path).readAsBytesSync();
final arc = ZipDecoder().decodeBytes(bytes);
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"]);
} else {
// Download tgz file
final path =
"${settings.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);
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"]);
proc =
await Process.start("./steamcmd.sh", ["+quit"]);
}
Directory.current = Directory(settings.game_path);
settings.initializeSteamCmd();
},
onCancel: () {},
isDefault: false,
@ -136,7 +59,7 @@ class SteamCMDState extends State<SteamCMD> {
onTap: () async {
final dio = Dio();
await dio.download(
Base2FAPath + (Platform.isWindows ? ".exe" : ""),
settings.Base2FAPath + (Platform.isWindows ? ".exe" : ""),
settings.steamcmd_path +
Platform.pathSeparator +
(Platform.isWindows

View file

@ -1,15 +1,6 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:servermanager/settings.dart';
class Proton extends StatefulWidget {
Settings settings;
Proton({super.key, required this.settings});
@override
ProtonState createState() => ProtonState(settings: settings);
}
import 'package:servermanager/structs/settings.dart';
Future<void> runProton(String command, List<String> argx) async {
Settings settings = Settings();
@ -68,36 +59,3 @@ Future<void> runDetachedProton(
print('Error executing command: $e');
}
}
class ProtonState extends State<Proton> {
Settings settings;
ProtonState({required this.settings});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Proton Manager"),
backgroundColor: Color.fromARGB(255, 100, 0, 0),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
ListTile(
title: Text("Init Prefix"),
subtitle: Text("Resets proton prefix"),
leading: Icon(CupertinoIcons.folder_fill),
onTap: () {
runProton(
"echo", ["hello"]); // Change to "cmd" to execute 'cmd'
},
)
],
),
),
),
);
}
}

View file

@ -2,10 +2,10 @@ import 'dart:async';
import 'dart:io';
import 'package:libac_flutter/utils/IOTools.dart';
import 'package:mc_rcon_dart/mc_rcon_dart.dart';
import 'package:rcon/rcon.dart';
import 'package:servermanager/game.dart';
import 'package:servermanager/proton.dart';
import 'package:servermanager/settings.dart';
import 'package:servermanager/structs/settings.dart';
enum States {
Idle, // For when the state machine is waiting for a state change
@ -35,11 +35,11 @@ class StateMachine {
return; // Nothing to do here
} else if (currentState == States.FullStop) {
Settings settings = Settings();
await createSocket("127.0.0.1",
port: settings.inst!.serverSettings.RconPort);
login(settings.inst!.serverSettings.RconPassword);
Client cli = await Client.create(
"127.0.0.1", settings.inst!.serverSettings.RconPort);
cli.login(settings.inst!.serverSettings.RconPassword);
sendCommand("shutdown");
cli.send(Message.create(cli, PacketType.command, "shutdown"));
changeState(States.Inactive);
} else if (currentState == States.Starting) {

View file

@ -0,0 +1,40 @@
import 'package:libac_flutter/nbt/NbtUtils.dart';
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'package:libac_flutter/nbt/impl/IntTag.dart';
class AutomaticRestartInfo {
final int hours;
final int minutes;
final int seconds;
final bool enabled;
const AutomaticRestartInfo(
{this.hours = 0,
this.minutes = 0,
this.seconds = 0,
this.enabled = false});
static AutomaticRestartInfo deserialize(CompoundTag tag) {
return AutomaticRestartInfo(
hours: tag.get(TAG_HOURS)?.asInt() ?? 12,
minutes: tag.get(TAG_MINUTES)?.asInt() ?? 0,
seconds: tag.get(TAG_SECONDS)?.asInt() ?? 0,
enabled: NbtUtils.readBoolean(tag, TAG_ENABLED));
}
CompoundTag serialize() {
CompoundTag tag = CompoundTag();
tag.put(TAG_HOURS, IntTag.valueOf(hours));
tag.put(TAG_MINUTES, IntTag.valueOf(minutes));
tag.put(TAG_SECONDS, IntTag.valueOf(seconds));
NbtUtils.writeBoolean(tag, TAG_ENABLED, enabled);
return tag;
}
static const TAG_NAME = "ari";
static const TAG_HOURS = "hours";
static const TAG_MINUTES = "minutes";
static const TAG_SECONDS = "seconds";
static const TAG_ENABLED = "enabled";
}

View file

@ -0,0 +1,40 @@
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'package:libac_flutter/nbt/impl/IntTag.dart';
import 'package:libac_flutter/nbt/impl/StringTag.dart';
class ServerSettings {
final String RconPassword;
final int RconPort;
final int GamePort;
final int QueryPort;
const ServerSettings(
{required this.RconPassword,
required this.RconPort,
required this.GamePort,
required this.QueryPort});
static ServerSettings deserialize(CompoundTag tag) {
return ServerSettings(
RconPassword: tag.get(TAG_PASSWORD)?.asString() ?? "",
RconPort: tag.get(TAG_RCON_PORT)?.asInt() ?? 25565,
GamePort: tag.get(TAG_GAME_PORT)?.asInt() ?? 0,
QueryPort: tag.get(TAG_QUERY_PORT)?.asInt() ?? 0);
}
CompoundTag serialize() {
CompoundTag tag = CompoundTag();
tag.put(TAG_PASSWORD, StringTag.valueOf(RconPassword));
tag.put(TAG_RCON_PORT, IntTag.valueOf(RconPort));
tag.put(TAG_GAME_PORT, IntTag.valueOf(GamePort));
tag.put(TAG_QUERY_PORT, IntTag.valueOf(QueryPort));
return tag;
}
static const TAG_NAME = "serverSettings";
static const TAG_PASSWORD = "password";
static const TAG_RCON_PORT = "rcon";
static const TAG_GAME_PORT = "game";
static const TAG_QUERY_PORT = "query";
}

View file

@ -1,16 +1,26 @@
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/impl/CompoundTag.dart';
import 'package:libac_flutter/packets/packets.dart';
import 'package:libac_flutter/utils/IOTools.dart';
import 'package:libac_flutter/utils/uuid/UUID.dart';
import 'package:servermanager/credentials.dart';
import 'package:servermanager/mod.dart';
import 'package:servermanager/settingsEntry.dart';
import 'package:servermanager/statemachine.dart';
import 'package:servermanager/structs/credentials.dart';
import 'package:servermanager/structs/mod.dart';
import 'package:servermanager/structs/settingsEntry.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";
Settings._();
static final Settings Instance = Settings._();
@ -32,8 +42,7 @@ class Settings {
Future<void> Read() async {
try {
var tag = await NbtIo.read(
PathHelper.builder(game_path).resolve("settings.dat").build());
var tag = await NbtIo.read("settings.dat");
inst = SettingsEntry.deserialize(tag.get("entry") as CompoundTag);
serverLoginCreds = Credentials.deserialize(
@ -49,8 +58,7 @@ class Settings {
tag.put("entry", inst!.serialize());
tag.put(Credentials.TAG_NAME, serverLoginCreds.save());
NbtIo.write(
PathHelper.builder(game_path).resolve("settings.dat").build(), tag);
NbtIo.write("settings.dat", tag);
}
bool isValid() {
@ -186,4 +194,71 @@ class Settings {
await file.writeAsString(paths.join("\n"),
flush: true, mode: FileMode.writeOnly);
}
Future<void> initializeSteamCmd() async {
// 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);
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"]);
} 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);
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"]);
proc = await Process.start("./steamcmd.sh", ["+quit"]);
}
Directory.current = Directory(game_path);
}
}

View file

@ -1,10 +1,10 @@
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:servermanager/autorestart.dart';
import 'package:servermanager/credentials.dart';
import 'package:servermanager/mod.dart';
import 'package:servermanager/serversettings.dart';
import 'package:servermanager/structs/autorestarts.dart';
import 'package:servermanager/structs/credentials.dart';
import 'package:servermanager/structs/mod.dart';
import 'package:servermanager/structs/serversettings.dart';
class SettingsEntry {
List<Mod> mods = [];

View file

@ -38,11 +38,11 @@ dependencies:
file_selector:
archive:
dio:
mc_rcon_dart:
crypto:
libac_flutter:
hosted: https://git.zontreck.com/api/packages/AriasCreations/pub/
version: 1.0.12
rcon: ^1.0.0
dev_dependencies:
flutter_test: