убрана информация о паролях пользователя
This commit is contained in:
@@ -244,16 +244,18 @@ Pause is implemented as cooperative cancellation:
|
|||||||
|
|
||||||
When the user presses `Play`:
|
When the user presses `Play`:
|
||||||
|
|
||||||
1. launcher resolves `<install-root>/Wow.exe`
|
1. launcher removes every `SET accountName ...` line from
|
||||||
2. launcher clears `<install-root>/Cache`
|
`<install-root>/WTF/Config.wtf` when that file exists
|
||||||
3. launcher requests a single-use game ticket
|
2. launcher resolves `<install-root>/Wow.exe`
|
||||||
4. launcher starts `Wow.exe` with working directory set to installation root,
|
3. launcher clears `<install-root>/Cache`
|
||||||
|
4. launcher requests a single-use game ticket
|
||||||
|
5. launcher starts `Wow.exe` with working directory set to installation root,
|
||||||
no authorization command-line arguments, and these child environment values:
|
no authorization command-line arguments, and these child environment values:
|
||||||
- `MOONWELL_LAUNCH_ACCOUNT=<account>`
|
- `MOONWELL_LAUNCH_ACCOUNT=<account>`
|
||||||
- `MOONWELL_LAUNCH_TICKET=<ticket>`
|
- `MOONWELL_LAUNCH_TICKET=<ticket>`
|
||||||
5. launcher retains a process handle; while the process is alive, it disables repeated game launches
|
6. launcher retains a process handle; while the process is alive, it disables repeated game launches
|
||||||
and client synchronization
|
and client synchronization
|
||||||
6. when the process exits, the launcher returns to the ready-to-play state
|
7. when the process exits, the launcher returns to the ready-to-play state
|
||||||
|
|
||||||
If `Wow.exe` is missing, launch fails with an error.
|
If `Wow.exe` is missing, launch fails with an error.
|
||||||
|
|
||||||
|
|||||||
@@ -351,6 +351,7 @@ class HomeScreenBloc extends Bloc<HomeScreenEvent, HomeScreenState> {
|
|||||||
_gameLaunchPending = true;
|
_gameLaunchPending = true;
|
||||||
try {
|
try {
|
||||||
final installationDir = outputPath.toFilePath();
|
final installationDir = outputPath.toFilePath();
|
||||||
|
await _gameInstallationService.removeSavedAccountName(installationDir);
|
||||||
await _gameInstallationService.clearCache(installationDir);
|
await _gameInstallationService.clearCache(installationDir);
|
||||||
final authorization = await _launcherApiClient.issueGameTicket(
|
final authorization = await _launcherApiClient.issueGameTicket(
|
||||||
_session.accessToken,
|
_session.accessToken,
|
||||||
|
|||||||
@@ -220,6 +220,27 @@ class GameInstallationService {
|
|||||||
await cacheDirectory.create(recursive: true);
|
await cacheDirectory.create(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> removeSavedAccountName(String installationDir) async {
|
||||||
|
final configFile = File(p.join(installationDir, 'WTF', 'Config.wtf'));
|
||||||
|
if (!await configFile.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final contents = await configFile.readAsString();
|
||||||
|
final sanitizedContents = contents.replaceAll(
|
||||||
|
RegExp(
|
||||||
|
r'^[ \t]*SET[ \t]+accountName(?:[ \t]+.*)?(?:\r\n|\n|\r|$)',
|
||||||
|
caseSensitive: false,
|
||||||
|
multiLine: true,
|
||||||
|
),
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sanitizedContents != contents) {
|
||||||
|
await configFile.writeAsString(sanitizedContents, flush: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<GameProcessHandle> launchGame(
|
Future<GameProcessHandle> launchGame(
|
||||||
String installationDir, {
|
String installationDir, {
|
||||||
required LauncherGameTicket authorization,
|
required LauncherGameTicket authorization,
|
||||||
|
|||||||
@@ -148,11 +148,15 @@ void main() {
|
|||||||
final manifest = ClientManifest.fromJson(<String, Object?>{
|
final manifest = ClientManifest.fromJson(<String, Object?>{
|
||||||
'files': <Map<String, Object?>>[],
|
'files': <Map<String, Object?>>[],
|
||||||
});
|
});
|
||||||
final gameService = _TrackingGameInstallationService();
|
final launchOperations = <String>[];
|
||||||
|
final gameService = _TrackingGameInstallationService(launchOperations);
|
||||||
final bloc = HomeScreenBloc(
|
final bloc = HomeScreenBloc(
|
||||||
clientSyncUseCase: _CapturingClientSyncUseCase(),
|
clientSyncUseCase: _CapturingClientSyncUseCase(),
|
||||||
gameInstallationService: gameService,
|
gameInstallationService: gameService,
|
||||||
launcherApiClient: _FakeLauncherApiClient(newsItems: const []),
|
launcherApiClient: _FakeLauncherApiClient(
|
||||||
|
newsItems: const [],
|
||||||
|
launchOperations: launchOperations,
|
||||||
|
),
|
||||||
preferencesRepository: _FakePreferencesRepository(
|
preferencesRepository: _FakePreferencesRepository(
|
||||||
outputDir: Uri.directory('C:/World of Warcraft'),
|
outputDir: Uri.directory('C:/World of Warcraft'),
|
||||||
),
|
),
|
||||||
@@ -169,6 +173,13 @@ void main() {
|
|||||||
await pumpEventQueue(times: 20);
|
await pumpEventQueue(times: 20);
|
||||||
|
|
||||||
expect(gameService.launchCount, 1);
|
expect(gameService.launchCount, 1);
|
||||||
|
expect(gameService.savedAccountNameRemovalCount, 1);
|
||||||
|
expect(launchOperations, <String>[
|
||||||
|
'removeSavedAccountName',
|
||||||
|
'clearCache',
|
||||||
|
'issueGameTicket',
|
||||||
|
'launchGame',
|
||||||
|
]);
|
||||||
expect(gameService.launchedWith?.account, 'PLAYERONE');
|
expect(gameService.launchedWith?.account, 'PLAYERONE');
|
||||||
expect(gameService.launchedWith?.ticket, 'A1B2C3D4E5F6G7H8');
|
expect(gameService.launchedWith?.ticket, 'A1B2C3D4E5F6G7H8');
|
||||||
expect(bloc.state.model.isGameRunning, isTrue);
|
expect(bloc.state.model.isGameRunning, isTrue);
|
||||||
@@ -230,12 +241,14 @@ class _FakeLauncherApiClient extends LauncherApiClient {
|
|||||||
this.manifest,
|
this.manifest,
|
||||||
this.realms = const [],
|
this.realms = const [],
|
||||||
this.account = const LauncherAccount(username: ''),
|
this.account = const LauncherAccount(username: ''),
|
||||||
|
this.launchOperations,
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<LauncherNewsItem> newsItems;
|
final List<LauncherNewsItem> newsItems;
|
||||||
final ClientManifest? manifest;
|
final ClientManifest? manifest;
|
||||||
final List<LauncherRealm> realms;
|
final List<LauncherRealm> realms;
|
||||||
final LauncherAccount account;
|
final LauncherAccount account;
|
||||||
|
final List<String>? launchOperations;
|
||||||
int manifestRequestCount = 0;
|
int manifestRequestCount = 0;
|
||||||
int gameTicketRequestCount = 0;
|
int gameTicketRequestCount = 0;
|
||||||
|
|
||||||
@@ -251,6 +264,7 @@ class _FakeLauncherApiClient extends LauncherApiClient {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<LauncherGameTicket> issueGameTicket(String accessToken) async {
|
Future<LauncherGameTicket> issueGameTicket(String accessToken) async {
|
||||||
|
launchOperations?.add('issueGameTicket');
|
||||||
gameTicketRequestCount += 1;
|
gameTicketRequestCount += 1;
|
||||||
return LauncherGameTicket(
|
return LauncherGameTicket(
|
||||||
account: 'PLAYERONE',
|
account: 'PLAYERONE',
|
||||||
@@ -284,21 +298,34 @@ class _FakeGameInstallationService extends GameInstallationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _TrackingGameInstallationService extends _FakeGameInstallationService {
|
class _TrackingGameInstallationService extends _FakeGameInstallationService {
|
||||||
|
_TrackingGameInstallationService(this.launchOperations);
|
||||||
|
|
||||||
|
final List<String> launchOperations;
|
||||||
final Completer<int> exitCode = Completer<int>();
|
final Completer<int> exitCode = Completer<int>();
|
||||||
int launchCount = 0;
|
int launchCount = 0;
|
||||||
|
int savedAccountNameRemovalCount = 0;
|
||||||
LauncherGameTicket? launchedWith;
|
LauncherGameTicket? launchedWith;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> hasClientExecutable(String installationDir) async => true;
|
Future<bool> hasClientExecutable(String installationDir) async => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> clearCache(String installationDir) async {}
|
Future<void> removeSavedAccountName(String installationDir) async {
|
||||||
|
launchOperations.add('removeSavedAccountName');
|
||||||
|
savedAccountNameRemovalCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> clearCache(String installationDir) async {
|
||||||
|
launchOperations.add('clearCache');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<GameProcessHandle> launchGame(
|
Future<GameProcessHandle> launchGame(
|
||||||
String installationDir, {
|
String installationDir, {
|
||||||
required LauncherGameTicket authorization,
|
required LauncherGameTicket authorization,
|
||||||
}) async {
|
}) async {
|
||||||
|
launchOperations.add('launchGame');
|
||||||
launchCount += 1;
|
launchCount += 1;
|
||||||
launchedWith = authorization;
|
launchedWith = authorization;
|
||||||
return GameProcessHandle(pid: 42, exitCode: exitCode.future);
|
return GameProcessHandle(pid: 42, exitCode: exitCode.future);
|
||||||
|
|||||||
@@ -92,6 +92,30 @@ void main() {
|
|||||||
expect(await cacheDirectory.list().isEmpty, isTrue);
|
expect(await cacheDirectory.list().isEmpty, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('removeSavedAccountName removes accountName lines only', () async {
|
||||||
|
final configFile = File(
|
||||||
|
'${rootDirectory.path}${Platform.pathSeparator}WTF${Platform.pathSeparator}Config.wtf',
|
||||||
|
);
|
||||||
|
await configFile.parent.create(recursive: true);
|
||||||
|
await configFile.writeAsString(
|
||||||
|
'SET gxWindow "1"\r\n'
|
||||||
|
'SET accountName "admin#&|𞉀#&|�"\r\n'
|
||||||
|
'set ACCOUNTNAME "another account"\r\n'
|
||||||
|
'SET locale "ruRU"\r\n',
|
||||||
|
);
|
||||||
|
|
||||||
|
await service.removeSavedAccountName(rootDirectory.path);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await configFile.readAsString(),
|
||||||
|
'SET gxWindow "1"\r\nSET locale "ruRU"\r\n',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removeSavedAccountName ignores a missing Config.wtf', () async {
|
||||||
|
await service.removeSavedAccountName(rootDirectory.path);
|
||||||
|
});
|
||||||
|
|
||||||
test('ensureParentDirectoryExists creates nested directories', () async {
|
test('ensureParentDirectoryExists creates nested directories', () async {
|
||||||
final tempPath =
|
final tempPath =
|
||||||
'${rootDirectory.path}${Platform.pathSeparator}Data${Platform.pathSeparator}patches${Platform.pathSeparator}common-2.MPQ.moonwell.part';
|
'${rootDirectory.path}${Platform.pathSeparator}Data${Platform.pathSeparator}patches${Platform.pathSeparator}common-2.MPQ.moonwell.part';
|
||||||
|
|||||||
Reference in New Issue
Block a user