diff --git a/client/README.md b/client/README.md index cb87cc9..986150b 100644 --- a/client/README.md +++ b/client/README.md @@ -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. \ No newline at end of file diff --git a/client/lib/Constants.dart b/client/lib/Constants.dart new file mode 100644 index 0000000..582f53a --- /dev/null +++ b/client/lib/Constants.dart @@ -0,0 +1,5 @@ +import 'dart:ui'; + +class Constants { + static const TITLEBAR_COLOR = Color.fromARGB(255, 97, 0, 0); +} diff --git a/client/lib/autorestart.dart b/client/lib/autorestart.dart index 8de4487..51ad406 100644 --- a/client/lib/autorestart.dart +++ b/client/lib/autorestart.dart @@ -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 { 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 { hours: hours, minutes: minutes, seconds: seconds)); - - return true; }, child: SingleChildScrollView( child: Padding( diff --git a/client/lib/credentials.dart b/client/lib/credentials.dart index c59cbb6..cb5f919 100644 --- a/client/lib/credentials.dart +++ b/client/lib/credentials.dart @@ -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"; } diff --git a/client/lib/dialogbox.dart b/client/lib/dialogbox.dart index b7ad5ed..7c54353 100644 --- a/client/lib/dialogbox.dart +++ b/client/lib/dialogbox.dart @@ -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 { diff --git a/client/lib/game.dart b/client/lib/game.dart index 6cc6e96..fb8ee46 100644 --- a/client/lib/game.dart +++ b/client/lib/game.dart @@ -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'; diff --git a/client/lib/home.dart b/client/lib/home.dart index fb86676..b1c3866 100644 --- a/client/lib/home.dart +++ b/client/lib/home.dart @@ -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 { @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: [ diff --git a/client/lib/main.dart b/client/lib/main.dart index 3a661a1..e28767f 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -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 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, + ), + ) + ], + ), + )); + } +} diff --git a/client/lib/mod.dart b/client/lib/mod.dart index 1e14f63..696841f 100644 --- a/client/lib/mod.dart +++ b/client/lib/mod.dart @@ -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( diff --git a/client/lib/packets/ClientPackets.dart b/client/lib/packets/ClientPackets.dart new file mode 100644 index 0000000..e69de29 diff --git a/client/lib/pathtools.dart b/client/lib/pathtools.dart deleted file mode 100644 index 53d9fc6..0000000 --- a/client/lib/pathtools.dart +++ /dev/null @@ -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; - } -} diff --git a/client/lib/serversettings.dart b/client/lib/serversettings.dart index 7d92f18..3934dfb 100644 --- a/client/lib/serversettings.dart +++ b/client/lib/serversettings.dart @@ -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 { diff --git a/client/lib/settings.dart b/client/lib/settings.dart index f034898..f8b6c98 100644 --- a/client/lib/settings.dart +++ b/client/lib/settings.dart @@ -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 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> Open() { + Future 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; } } diff --git a/client/lib/settingsEntry.dart b/client/lib/settingsEntry.dart index 2d110f9..d015dc9 100644 --- a/client/lib/settingsEntry.dart +++ b/client/lib/settingsEntry.dart @@ -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 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; + } } diff --git a/client/lib/statemachine.dart b/client/lib/statemachine.dart index ed2e4dd..b1bd195 100644 --- a/client/lib/statemachine.dart +++ b/client/lib/statemachine.dart @@ -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 diff --git a/client/macos/Flutter/GeneratedPluginRegistrant.swift b/client/macos/Flutter/GeneratedPluginRegistrant.swift index b878e03..14b5f7c 100644 --- a/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -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")) } diff --git a/client/pubspec.yaml b/client/pubspec.yaml index f2e9102..5a70f0d 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -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 diff --git a/client/test/widget_test.dart b/client/test/widget_test.dart deleted file mode 100644 index 554f241..0000000 --- a/client/test/widget_test.dart +++ /dev/null @@ -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); - }); -}