Refactor project, get server-client coms working
This commit is contained in:
parent
6fadb92a26
commit
7cddfd2de6
22 changed files with 730 additions and 677 deletions
|
@ -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();
|
||||
|
||||
|
|
442
lib/game.dart
442
lib/game.dart
|
@ -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;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
),
|
||||
|
|
|
@ -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() {
|
||||
|
|
215
lib/pages/GameServerPage.dart
Normal file
215
lib/pages/GameServerPage.dart
Normal 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
232
lib/pages/ModManager.dart
Normal 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
46
lib/pages/Proton.dart
Normal 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'
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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),
|
|
@ -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});
|
|
@ -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;
|
|
@ -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
|
|
@ -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'
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
40
lib/structs/autorestarts.dart
Normal file
40
lib/structs/autorestarts.dart
Normal 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";
|
||||
}
|
40
lib/structs/serversettings.dart
Normal file
40
lib/structs/serversettings.dart
Normal 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";
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 = [];
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue