This commit is contained in:
2026-08-17 20:25:08 +04:00
parent b64dffca9a
commit 25e7ad156b
12 changed files with 368 additions and 13 deletions
@@ -12,6 +12,7 @@ import 'package:moonwell_launcher/features/launcher/domain/entities/client_hash_
import 'package:moonwell_launcher/features/launcher/domain/entities/client_installation_snapshot.dart';
import 'package:moonwell_launcher/features/launcher/domain/entities/client_manifest.dart';
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_exception.dart';
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_game_ticket.dart';
import 'package:moonwell_launcher/features/launcher/domain/entities/local_client_file.dart';
import 'package:path/path.dart' as p;
@@ -219,7 +220,10 @@ class GameInstallationService {
await cacheDirectory.create(recursive: true);
}
Future<GameProcessHandle> launchGame(String installationDir) async {
Future<GameProcessHandle> launchGame(
String installationDir, {
required LauncherGameTicket authorization,
}) async {
final executablePath = getExecutablePath(installationDir);
final executable = File(executablePath);
@@ -229,10 +233,29 @@ class GameInstallationService {
);
}
return startGameProcess(
executablePath: executablePath,
arguments: const <String>[],
workingDirectory: installationDir,
environment: <String, String>{
'MOONWELL_LAUNCH_ACCOUNT': authorization.account,
'MOONWELL_LAUNCH_TICKET': authorization.ticket,
},
);
}
Future<GameProcessHandle> startGameProcess({
required String executablePath,
required List<String> arguments,
required String workingDirectory,
required Map<String, String> environment,
}) async {
final process = await Process.start(
executablePath,
const <String>[],
workingDirectory: installationDir,
arguments,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: true,
mode: ProcessStartMode.normal,
);
await process.stdin.close();
@@ -11,6 +11,7 @@ import 'package:moonwell_launcher/features/launcher/domain/entities/client_manif
import 'package:moonwell_launcher/features/launcher/domain/entities/client_manifest_file.dart';
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_account.dart';
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_exception.dart';
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_game_ticket.dart';
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_news_item.dart';
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_realm.dart';
import 'package:moonwell_launcher/features/launcher/domain/entities/launcher_session.dart';
@@ -162,6 +163,47 @@ class LauncherApiClient {
}
}
Future<LauncherGameTicket> issueGameTicket(String accessToken) async {
final ticketUri = _resolveUri('api/launcher/game-ticket');
try {
final response = await _dio.postUri(
ticketUri,
data: const <String, Object?>{'client_build': 12340},
options: _authorizedOptions(accessToken),
);
final ticket = LauncherGameTicket.fromJson(_coerceMap(response.data));
if (!ticket.isUsable()) {
throw const LauncherApiException(
'Launcher API returned an expired game ticket.',
);
}
return ticket;
} on DioException catch (error, stackTrace) {
await _launcherLogService.error(
'Game ticket request failed.',
details: <String, Object?>{
'endpoint': ticketUri.toString(),
'statusCode': error.response?.statusCode,
'dioType': error.type.name,
},
error: error,
stackTrace: stackTrace,
);
throw LauncherApiException(_extractErrorMessage(error));
} on FormatException catch (error, stackTrace) {
await _launcherLogService.error(
'Launcher API returned an invalid game ticket payload.',
details: <String, Object?>{'endpoint': ticketUri.toString()},
error: error,
stackTrace: stackTrace,
);
throw const LauncherApiException(
'Launcher API returned an invalid game ticket.',
);
}
}
Future<ClientManifest> fetchManifest(String accessToken) async {
final manifestUri = _resolveUri('api/launcher/manifest');