Start to update stuff

This commit is contained in:
zontreck 2024-05-22 16:09:36 -07:00
parent 110b2d150c
commit 399d884681
18 changed files with 217 additions and 240 deletions

View file

@ -1,16 +1,8 @@
# servermanager
A new Flutter project.
A Conan Exiles server manager
## Getting Started
## Client
__________
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
Client is the software used to configure the server. It connects to, and provides an interface for managing mods, and settings.

View file

@ -0,0 +1,5 @@
import 'dart:ui';
class Constants {
static const TITLEBAR_COLOR = Color.fromARGB(255, 97, 0, 0);
}

View file

@ -1,25 +1,13 @@
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:hive/hive.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';
part 'autorestart.g.dart';
@HiveType(typeId: 3)
class AutomaticRestartInfo {
@HiveField(0, defaultValue: 0)
final int hours;
@HiveField(1, defaultValue: 0)
final int minutes;
@HiveField(2, defaultValue: 0)
final int seconds;
@HiveField(3, defaultValue: false)
final bool enabled;
const AutomaticRestartInfo(
@ -27,6 +15,30 @@ class AutomaticRestartInfo {
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 {
@ -62,8 +74,8 @@ class AutoRestartState extends State<AutoRestartPage> {
title: Text("Automatic Restart"),
backgroundColor: Color.fromARGB(255, 100, 0, 0),
),
body: WillPopScope(
onWillPop: () async {
body: PopScope(
onPopInvoked: (v) async {
Navigator.pop(
context,
AutomaticRestartInfo(
@ -71,8 +83,6 @@ class AutoRestartState extends State<AutoRestartPage> {
hours: hours,
minutes: minutes,
seconds: seconds));
return true;
},
child: SingleChildScrollView(
child: Padding(

View file

@ -1,19 +1,11 @@
import 'package:hive/hive.dart';
import 'package:libac_flutter/nbt/NbtUtils.dart';
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'package:libac_flutter/nbt/impl/StringTag.dart';
part 'credentials.g.dart';
@HiveType(typeId: 1)
class Credentials {
@HiveField(0, defaultValue: "")
String username;
@HiveField(1, defaultValue: "")
String password;
@HiveField(2, defaultValue: "")
String secret;
@HiveField(3, defaultValue: false)
bool has_2fa = false;
Credentials(
@ -21,4 +13,28 @@ class Credentials {
required this.password,
required this.secret,
required this.has_2fa});
CompoundTag save() {
CompoundTag tag = CompoundTag();
tag.put(TAG_USERNAME, StringTag.valueOf(username));
tag.put(TAG_PASSWORD, StringTag.valueOf(password));
tag.put(TAG_SECRET, StringTag.valueOf(secret));
NbtUtils.writeBoolean(tag, TAG_2FA, has_2fa);
return tag;
}
static Credentials deserialize(CompoundTag tag) {
return Credentials(
username: tag.get(TAG_USERNAME)?.asString() ?? "",
password: tag.get(TAG_PASSWORD)?.asString() ?? "",
secret: tag.get(TAG_SECRET)?.asString() ?? "",
has_2fa: NbtUtils.readBoolean(tag, TAG_2FA));
}
static const TAG_NAME = "credentials";
static const TAG_USERNAME = "username";
static const TAG_PASSWORD = "password";
static const TAG_SECRET = "secret";
static const TAG_2FA = "2fa";
}

View file

@ -1,5 +1,3 @@
// ignore_for_file: non_constant_identifier_names, prefer_typing_uninitialized_variables, prefer_const_constructors
import 'package:flutter/material.dart';
class InputBox extends StatelessWidget {

View file

@ -3,9 +3,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/pathtools.dart';
import 'package:servermanager/serversettings.dart';
import 'package:servermanager/settings.dart';
import 'package:servermanager/statemachine.dart';

View file

@ -3,6 +3,7 @@ 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';
class HomePage extends StatefulWidget {
@ -20,13 +21,10 @@ class HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
settings.Read();
return Scaffold(
appBar: AppBar(
title: Text("Conan Exiles Server Manager"),
backgroundColor: Color.fromARGB(255, 100, 0, 0),
),
title: Text("Conan Exiles Server Manager"),
backgroundColor: Constants.TITLEBAR_COLOR),
drawer: Drawer(
child: SingleChildScrollView(
child: Column(children: [

View file

@ -1,23 +1,15 @@
import 'package:flutter/material.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:libac_flutter/packets/packets.dart';
import 'package:servermanager/Constants.dart';
import 'package:servermanager/autorestart.dart';
import 'package:servermanager/credentials.dart';
import 'package:servermanager/game.dart';
import 'package:servermanager/home.dart';
import 'package:servermanager/mod.dart';
import 'package:servermanager/proton.dart';
import 'package:servermanager/serversettings.dart';
import 'package:servermanager/settings.dart';
import 'package:servermanager/settingsEntry.dart';
import 'package:servermanager/steamcmd.dart';
Future<void> main() async {
await Hive.initFlutter();
Hive.registerAdapter(CredentialsAdapter());
Hive.registerAdapter(ModAdapter());
Hive.registerAdapter(SettingsEntryAdapter());
Hive.registerAdapter(AutomaticRestartInfoAdapter());
Hive.registerAdapter(ServerSettingsAdapter());
runApp(MyApp());
}
@ -30,10 +22,8 @@ class MyApp extends StatelessWidget {
return MaterialApp(
title: 'Server Manager',
theme: ThemeData.dark(useMaterial3: true),
home: HomePage(
settings: appSettings,
),
routes: {
"/": (context) => ServerPage(),
"/home": (context) => HomePage(settings: appSettings),
"/proton": (context) => Proton(settings: appSettings),
"/steamcmd": (context) => SteamCMD(
@ -48,3 +38,54 @@ class MyApp extends StatelessWidget {
});
}
}
class ServerPage extends StatelessWidget {
TextEditingController serverIP = TextEditingController();
TextEditingController username = TextEditingController();
TextEditingController password = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Conan Exiles Server Manager - Login"),
backgroundColor: Constants.TITLEBAR_COLOR,
),
floatingActionButton: ElevatedButton(
onPressed: () {
// Send login packet to server
Settings settings = Settings();
settings.client = PacketClient(serverIP.text);
},
child: Text("Login"),
),
body: SingleChildScrollView(
child: Column(
children: [
ListTile(
title: Text("Server IP/FQDN"),
subtitle: TextField(
controller: serverIP,
decoration: InputDecoration(hintText: "ex. 192.168.1.100"),
),
),
ListTile(
title: Text("Username"),
subtitle: TextField(
controller: username,
decoration: InputDecoration(hintText: "the_user"),
),
),
ListTile(
title: Text("Password"),
subtitle: TextField(
controller: password,
decoration: InputDecoration(hintText: "pass"),
obscureText: true,
),
)
],
),
));
}
}

View file

@ -1,30 +1,20 @@
import 'package:hive/hive.dart';
import 'package:uuid/v4.dart';
import 'package:libac_flutter/utils/uuid/NbtUUID.dart';
import 'package:libac_flutter/utils/uuid/UUID.dart';
part 'mod.g.dart';
@HiveType(typeId: 2)
class Mod {
@HiveField(0, defaultValue: "")
String mod_name = "";
@HiveField(1, defaultValue: 0)
int mod_id = 0;
@HiveField(2, defaultValue: "")
String mod_pak = "";
@HiveField(3, defaultValue: "")
String mod_hash = "";
bool newMod = false;
String _id = "";
NbtUUID _id = NbtUUID.ZERO;
String mod_instance_id() {
if (_id.isEmpty) {
_id = UuidV4().generate();
if (_id.toString() == NbtUUID.ZERO) {
_id = NbtUUID.fromUUID(UUID.generate(4));
}
return _id;
return _id.toString();
}
Mod(

View file

View file

@ -1,67 +0,0 @@
import 'dart:io';
class PathHelper {
String pth = "";
PathHelper({required this.pth});
static String combine(String path1, String path2) {
return path1 + Platform.pathSeparator + path2;
}
static PathHelper builder(String startPath) {
return PathHelper(pth: startPath);
}
PathHelper resolve(String path2) {
pth += Platform.pathSeparator + path2;
return this;
}
PathHelper conditionalResolve(bool flag, String path) {
if (flag) pth += Platform.pathSeparator + path;
return this;
}
bool deleteDirectory({bool recursive = false}) {
Directory dir = new Directory(build());
try {
dir.deleteSync(recursive: recursive);
return true;
} catch (E) {
return false;
}
}
bool deleteFile() {
File file = new File(build());
try {
file.deleteSync(recursive: true);
return true;
} catch (E) {
return false;
}
}
PathHelper removeDir() {
deleteDirectory(recursive: true);
return this;
}
PathHelper removeFile() {
deleteFile();
return this;
}
PathHelper mkdir() {
Directory dir = new Directory(build());
dir.createSync(recursive: true);
return this;
}
String build() {
return pth;
}
}

View file

@ -1,23 +1,12 @@
import 'dart:ffi';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.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';
part 'serversettings.g.dart';
@HiveType(typeId: 5)
class ServerSettings {
@HiveField(0, defaultValue: "Password01234")
final String RconPassword;
@HiveField(1, defaultValue: 7779)
final int RconPort;
@HiveField(2, defaultValue: 7780)
final int GamePort;
@HiveField(3, defaultValue: 7782)
final int QueryPort;
const ServerSettings(
@ -25,6 +14,30 @@ class ServerSettings {
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 {

View file

@ -1,8 +1,10 @@
import 'dart:io';
import 'package:hive/hive.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:servermanager/mod.dart';
import 'package:servermanager/pathtools.dart';
import 'package:servermanager/settingsEntry.dart';
import 'package:servermanager/statemachine.dart';
@ -12,6 +14,7 @@ class Settings {
String steamcmd_path = "";
String game_path = "";
PacketClient? client;
StateMachine subsys = StateMachine();
@ -21,45 +24,43 @@ class Settings {
SettingsEntry? inst;
void Read() {
if (!isValid()) return;
var box = Hive.box("settings");
Future<void> Read() async {
try {
var tag = await NbtIo.read(
PathHelper.builder(game_path).resolve("settings.dat").build());
inst = box.get("entry", defaultValue: SettingsEntry()) as SettingsEntry;
inst = SettingsEntry.deserialize(tag.get("entry") as CompoundTag);
} catch (E) {
inst = SettingsEntry();
}
}
void Write() {
if (!isValid()) return;
var box = Hive.box("settings");
box.put("entry", inst);
CompoundTag tag = CompoundTag();
tag.put("entry", inst!.serialize());
box.compact();
NbtIo.write(
PathHelper.builder(game_path).resolve("settings.dat").build(), tag);
}
bool isValid() {
if (!Hive.isBoxOpen("settings")) {
if (inst == null) {
return false;
} else {
return true;
}
}
Future<Box<E>> Open<E>() {
Future<void> Open() async {
Close();
return Hive.openBox(
"settings",
path: game_path,
compactionStrategy: (entries, deletedEntries) {
return deletedEntries > 1;
},
);
Instance.Read();
}
static void Close() {
if (Hive.isBoxOpen("settings")) {
var box = Hive.box("settings");
box.compact();
box.close();
static void Close() async {
if (Instance.isValid()) {
Instance.Write();
Instance.inst = null;
}
}

View file

@ -1,37 +1,50 @@
import 'package:hive/hive.dart';
import 'package:libac_flutter/nbt/NbtUtils.dart';
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'package:libac_flutter/nbt/impl/StringTag.dart';
import 'package:servermanager/autorestart.dart';
import 'package:servermanager/credentials.dart';
import 'package:servermanager/mod.dart';
import 'package:servermanager/serversettings.dart';
part 'settingsEntry.g.dart';
@HiveType(typeId: 0)
class SettingsEntry {
@HiveField(0, defaultValue: [])
List<Mod> mods = [];
@HiveField(3)
Credentials? steam_creds;
@HiveField(4, defaultValue: AutomaticRestartInfo())
AutomaticRestartInfo timer = AutomaticRestartInfo();
@HiveField(5,
defaultValue: ServerSettings(
RconPassword: "Password01234",
RconPort: 7779,
GamePort: 7780,
QueryPort: 7782))
ServerSettings serverSettings = ServerSettings(
RconPassword: "Password01234",
RconPort: 7779,
GamePort: 7780,
QueryPort: 7782);
@HiveField(6, defaultValue: true)
bool downloadMods = true;
@HiveField(7, defaultValue: "")
String conanExilesLibraryPath = "";
static SettingsEntry deserialize(CompoundTag tag) {
SettingsEntry st = SettingsEntry();
if (tag.containsKey(Credentials.TAG_NAME))
st.steam_creds =
Credentials.deserialize(tag.get(Credentials.TAG_NAME) as CompoundTag);
st.timer = AutomaticRestartInfo.deserialize(
tag.get(AutomaticRestartInfo.TAG_NAME) as CompoundTag);
st.serverSettings = ServerSettings.deserialize(
tag.get(ServerSettings.TAG_NAME) as CompoundTag);
st.downloadMods = NbtUtils.readBoolean(tag, "download");
st.conanExilesLibraryPath = tag.get("libpath")?.asString() ?? "";
return st;
}
CompoundTag serialize() {
CompoundTag tag = CompoundTag();
if (steam_creds != null) tag.put(Credentials.TAG_NAME, steam_creds!.save());
tag.put(AutomaticRestartInfo.TAG_NAME, timer.serialize());
tag.put(ServerSettings.TAG_NAME, serverSettings.serialize());
NbtUtils.writeBoolean(tag, "download", downloadMods);
tag.put("libpath", StringTag.valueOf(conanExilesLibraryPath));
return tag;
}
}

View file

@ -1,11 +1,11 @@
import 'dart:async';
import 'dart:io';
import 'package:libac_flutter/utils/IOTools.dart';
import 'package:mc_rcon_dart/mc_rcon_dart.dart';
import 'package:servermanager/game.dart';
import 'package:servermanager/pathtools.dart';
import 'package:servermanager/proton.dart';
import 'package:servermanager/settings.dart';
import 'package:mc_rcon_dart/mc_rcon_dart.dart';
enum States {
Idle, // For when the state machine is waiting for a state change

View file

@ -6,9 +6,7 @@ import FlutterMacOS
import Foundation
import file_selector_macos
import path_provider_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
}

View file

@ -35,14 +35,14 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
hive:
hive_flutter:
file_selector:
archive:
dio:
mc_rcon_dart:
uuid: ^4.1.0
crypto:
libac_flutter:
hosted: https://git.zontreck.com/api/packages/AriasCreations/pub/
version: 1.0.8
dev_dependencies:
flutter_test:
@ -54,7 +54,6 @@ dev_dependencies:
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^3.0.0
hive_generator:
build_runner:
msix: ^3.16.6

View file

@ -1,30 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:servermanager/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}