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/packets/packets.dart';
|
||||||
|
import 'package:libac_flutter/utils/IOTools.dart';
|
||||||
import 'package:servermanager/packets/ClientPackets.dart';
|
import 'package:servermanager/packets/ClientPackets.dart';
|
||||||
|
import 'package:servermanager/structs/settings.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
ClientPackets.register();
|
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();
|
await PacketServer.start();
|
||||||
|
|
||||||
|
|
442
lib/game.dart
442
lib/game.dart
|
@ -1,14 +1,9 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:crypto/crypto.dart';
|
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:libac_flutter/utils/IOTools.dart';
|
||||||
import 'package:servermanager/autorestart.dart';
|
import 'package:servermanager/structs/mod.dart';
|
||||||
import 'package:servermanager/mod.dart';
|
import 'package:servermanager/structs/settings.dart';
|
||||||
import 'package:servermanager/serversettings.dart';
|
|
||||||
import 'package:servermanager/settings.dart';
|
|
||||||
import 'package:servermanager/statemachine.dart';
|
|
||||||
|
|
||||||
Future<void> doDownloadMods(String modsFolder) async {
|
Future<void> doDownloadMods(String modsFolder) async {
|
||||||
Settings settings = Settings();
|
Settings settings = Settings();
|
||||||
|
@ -133,436 +128,3 @@ Future<List<Mod>> doScanMods(String modsFolder) async {
|
||||||
}
|
}
|
||||||
return ret;
|
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:flutter/material.dart';
|
||||||
|
import 'package:libac_flutter/nbt/NbtUtils.dart';
|
||||||
import 'package:libac_flutter/packets/packets.dart';
|
import 'package:libac_flutter/packets/packets.dart';
|
||||||
import 'package:servermanager/Constants.dart';
|
import 'package:libac_flutter/utils/uuid/UUID.dart';
|
||||||
import 'package:servermanager/autorestart.dart';
|
import 'package:servermanager/packets/ClientPackets.dart';
|
||||||
import 'package:servermanager/game.dart';
|
import 'package:servermanager/pages/Constants.dart';
|
||||||
import 'package:servermanager/home.dart';
|
import 'package:servermanager/pages/GameServerPage.dart';
|
||||||
import 'package:servermanager/proton.dart';
|
import 'package:servermanager/pages/ModManager.dart';
|
||||||
import 'package:servermanager/serversettings.dart';
|
import 'package:servermanager/pages/autorestart.dart';
|
||||||
import 'package:servermanager/settings.dart';
|
import 'package:servermanager/pages/home.dart';
|
||||||
import 'package:servermanager/steamcmd.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 {
|
Future<void> main() async {
|
||||||
runApp(MyApp());
|
runApp(MyApp());
|
||||||
|
@ -57,6 +62,21 @@ class ServerPage extends StatelessWidget {
|
||||||
Settings settings = Settings();
|
Settings settings = Settings();
|
||||||
settings.client = PacketClient();
|
settings.client = PacketClient();
|
||||||
await settings.client!.startConnect(serverIP.text);
|
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"),
|
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/packets/packets.dart';
|
||||||
import 'package:libac_flutter/utils/uuid/NbtUUID.dart';
|
import 'package:libac_flutter/utils/uuid/NbtUUID.dart';
|
||||||
import 'package:libac_flutter/utils/uuid/UUID.dart';
|
import 'package:libac_flutter/utils/uuid/UUID.dart';
|
||||||
import 'package:servermanager/settings.dart';
|
import 'package:servermanager/structs/settings.dart';
|
||||||
|
|
||||||
class ClientPackets {
|
class ClientPackets {
|
||||||
static void register() {
|
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: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 {
|
import '../structs/serversettings.dart';
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
class ServerSettingsPage extends StatefulWidget {
|
class ServerSettingsPage extends StatefulWidget {
|
||||||
ServerSettingsPage({super.key});
|
ServerSettingsPage({super.key});
|
||||||
|
@ -72,8 +34,8 @@ class ServerSettingsState extends State<ServerSettingsPage> {
|
||||||
title: Text("Server Settings"),
|
title: Text("Server Settings"),
|
||||||
backgroundColor: Color.fromARGB(255, 100, 0, 0),
|
backgroundColor: Color.fromARGB(255, 100, 0, 0),
|
||||||
),
|
),
|
||||||
body: WillPopScope(
|
body: PopScope(
|
||||||
onWillPop: () async {
|
onPopInvoked: (v) async {
|
||||||
Navigator.pop(
|
Navigator.pop(
|
||||||
context,
|
context,
|
||||||
ServerSettings(
|
ServerSettings(
|
||||||
|
@ -81,7 +43,6 @@ class ServerSettingsState extends State<ServerSettingsPage> {
|
||||||
RconPort: rconPort,
|
RconPort: rconPort,
|
||||||
GamePort: gPort,
|
GamePort: gPort,
|
||||||
QueryPort: qPort));
|
QueryPort: qPort));
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
|
@ -1,45 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:libac_flutter/nbt/NbtUtils.dart';
|
import 'package:servermanager/structs/autorestarts.dart';
|
||||||
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
|
import 'package:servermanager/structs/settings.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";
|
|
||||||
}
|
|
||||||
|
|
||||||
class AutoRestartPage extends StatefulWidget {
|
class AutoRestartPage extends StatefulWidget {
|
||||||
AutoRestartPage({super.key});
|
AutoRestartPage({super.key});
|
|
@ -3,8 +3,8 @@ import 'dart:io';
|
||||||
import 'package:file_selector/file_selector.dart';
|
import 'package:file_selector/file_selector.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:servermanager/Constants.dart';
|
import 'package:servermanager/pages/Constants.dart';
|
||||||
import 'package:servermanager/settings.dart';
|
import 'package:servermanager/structs/settings.dart';
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
Settings settings;
|
Settings settings;
|
|
@ -1,12 +1,11 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:archive/archive_io.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:servermanager/credentials.dart';
|
import 'package:servermanager/pages/dialogbox.dart';
|
||||||
import 'package:servermanager/dialogbox.dart';
|
import 'package:servermanager/structs/credentials.dart';
|
||||||
import 'package:servermanager/settings.dart';
|
import 'package:servermanager/structs/settings.dart';
|
||||||
|
|
||||||
class SteamCMD extends StatefulWidget {
|
class SteamCMD extends StatefulWidget {
|
||||||
Settings settings;
|
Settings settings;
|
||||||
|
@ -17,14 +16,6 @@ class SteamCMD extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SteamCMDState extends State<SteamCMD> {
|
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();
|
Settings settings = Settings();
|
||||||
SteamCMDState({required this.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?",
|
"This action will delete any existing copy of SteamCMD and download a fresh copy. \nAre you sure?",
|
||||||
changed: (X) {},
|
changed: (X) {},
|
||||||
onSubmit: () async {
|
onSubmit: () async {
|
||||||
// Yes, Proceed
|
settings.initializeSteamCmd();
|
||||||
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);
|
|
||||||
},
|
},
|
||||||
onCancel: () {},
|
onCancel: () {},
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
|
@ -136,7 +59,7 @@ class SteamCMDState extends State<SteamCMD> {
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final dio = Dio();
|
final dio = Dio();
|
||||||
await dio.download(
|
await dio.download(
|
||||||
Base2FAPath + (Platform.isWindows ? ".exe" : ""),
|
settings.Base2FAPath + (Platform.isWindows ? ".exe" : ""),
|
||||||
settings.steamcmd_path +
|
settings.steamcmd_path +
|
||||||
Platform.pathSeparator +
|
Platform.pathSeparator +
|
||||||
(Platform.isWindows
|
(Platform.isWindows
|
|
@ -1,15 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:servermanager/settings.dart';
|
|
||||||
|
|
||||||
class Proton extends StatefulWidget {
|
import 'package:servermanager/structs/settings.dart';
|
||||||
Settings settings;
|
|
||||||
Proton({super.key, required this.settings});
|
|
||||||
|
|
||||||
@override
|
|
||||||
ProtonState createState() => ProtonState(settings: settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> runProton(String command, List<String> argx) async {
|
Future<void> runProton(String command, List<String> argx) async {
|
||||||
Settings settings = Settings();
|
Settings settings = Settings();
|
||||||
|
@ -68,36 +59,3 @@ Future<void> runDetachedProton(
|
||||||
print('Error executing command: $e');
|
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 'dart:io';
|
||||||
|
|
||||||
import 'package:libac_flutter/utils/IOTools.dart';
|
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/game.dart';
|
||||||
import 'package:servermanager/proton.dart';
|
import 'package:servermanager/proton.dart';
|
||||||
import 'package:servermanager/settings.dart';
|
import 'package:servermanager/structs/settings.dart';
|
||||||
|
|
||||||
enum States {
|
enum States {
|
||||||
Idle, // For when the state machine is waiting for a state change
|
Idle, // For when the state machine is waiting for a state change
|
||||||
|
@ -35,11 +35,11 @@ class StateMachine {
|
||||||
return; // Nothing to do here
|
return; // Nothing to do here
|
||||||
} else if (currentState == States.FullStop) {
|
} else if (currentState == States.FullStop) {
|
||||||
Settings settings = Settings();
|
Settings settings = Settings();
|
||||||
await createSocket("127.0.0.1",
|
Client cli = await Client.create(
|
||||||
port: settings.inst!.serverSettings.RconPort);
|
"127.0.0.1", settings.inst!.serverSettings.RconPort);
|
||||||
login(settings.inst!.serverSettings.RconPassword);
|
cli.login(settings.inst!.serverSettings.RconPassword);
|
||||||
|
|
||||||
sendCommand("shutdown");
|
cli.send(Message.create(cli, PacketType.command, "shutdown"));
|
||||||
|
|
||||||
changeState(States.Inactive);
|
changeState(States.Inactive);
|
||||||
} else if (currentState == States.Starting) {
|
} 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 'dart:io';
|
||||||
|
|
||||||
|
import 'package:archive/archive.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:libac_flutter/nbt/NbtIo.dart';
|
import 'package:libac_flutter/nbt/NbtIo.dart';
|
||||||
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
|
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
|
||||||
import 'package:libac_flutter/packets/packets.dart';
|
import 'package:libac_flutter/packets/packets.dart';
|
||||||
import 'package:libac_flutter/utils/IOTools.dart';
|
import 'package:libac_flutter/utils/IOTools.dart';
|
||||||
import 'package:libac_flutter/utils/uuid/UUID.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/statemachine.dart';
|
||||||
|
import 'package:servermanager/structs/credentials.dart';
|
||||||
|
import 'package:servermanager/structs/mod.dart';
|
||||||
|
import 'package:servermanager/structs/settingsEntry.dart';
|
||||||
|
|
||||||
class Settings {
|
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._();
|
Settings._();
|
||||||
static final Settings Instance = Settings._();
|
static final Settings Instance = Settings._();
|
||||||
|
|
||||||
|
@ -32,8 +42,7 @@ class Settings {
|
||||||
|
|
||||||
Future<void> Read() async {
|
Future<void> Read() async {
|
||||||
try {
|
try {
|
||||||
var tag = await NbtIo.read(
|
var tag = await NbtIo.read("settings.dat");
|
||||||
PathHelper.builder(game_path).resolve("settings.dat").build());
|
|
||||||
|
|
||||||
inst = SettingsEntry.deserialize(tag.get("entry") as CompoundTag);
|
inst = SettingsEntry.deserialize(tag.get("entry") as CompoundTag);
|
||||||
serverLoginCreds = Credentials.deserialize(
|
serverLoginCreds = Credentials.deserialize(
|
||||||
|
@ -49,8 +58,7 @@ class Settings {
|
||||||
tag.put("entry", inst!.serialize());
|
tag.put("entry", inst!.serialize());
|
||||||
tag.put(Credentials.TAG_NAME, serverLoginCreds.save());
|
tag.put(Credentials.TAG_NAME, serverLoginCreds.save());
|
||||||
|
|
||||||
NbtIo.write(
|
NbtIo.write("settings.dat", tag);
|
||||||
PathHelper.builder(game_path).resolve("settings.dat").build(), tag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isValid() {
|
bool isValid() {
|
||||||
|
@ -186,4 +194,71 @@ class Settings {
|
||||||
await file.writeAsString(paths.join("\n"),
|
await file.writeAsString(paths.join("\n"),
|
||||||
flush: true, mode: FileMode.writeOnly);
|
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/NbtUtils.dart';
|
||||||
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
|
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
|
||||||
import 'package:libac_flutter/nbt/impl/StringTag.dart';
|
import 'package:libac_flutter/nbt/impl/StringTag.dart';
|
||||||
import 'package:servermanager/autorestart.dart';
|
import 'package:servermanager/structs/autorestarts.dart';
|
||||||
import 'package:servermanager/credentials.dart';
|
import 'package:servermanager/structs/credentials.dart';
|
||||||
import 'package:servermanager/mod.dart';
|
import 'package:servermanager/structs/mod.dart';
|
||||||
import 'package:servermanager/serversettings.dart';
|
import 'package:servermanager/structs/serversettings.dart';
|
||||||
|
|
||||||
class SettingsEntry {
|
class SettingsEntry {
|
||||||
List<Mod> mods = [];
|
List<Mod> mods = [];
|
|
@ -38,11 +38,11 @@ dependencies:
|
||||||
file_selector:
|
file_selector:
|
||||||
archive:
|
archive:
|
||||||
dio:
|
dio:
|
||||||
mc_rcon_dart:
|
|
||||||
crypto:
|
crypto:
|
||||||
libac_flutter:
|
libac_flutter:
|
||||||
hosted: https://git.zontreck.com/api/packages/AriasCreations/pub/
|
hosted: https://git.zontreck.com/api/packages/AriasCreations/pub/
|
||||||
version: 1.0.12
|
version: 1.0.12
|
||||||
|
rcon: ^1.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue