убрана информация о паролях пользователя
This commit is contained in:
@@ -244,16 +244,18 @@ Pause is implemented as cooperative cancellation:
|
||||
|
||||
When the user presses `Play`:
|
||||
|
||||
1. launcher resolves `<install-root>/Wow.exe`
|
||||
2. launcher clears `<install-root>/Cache`
|
||||
3. launcher requests a single-use game ticket
|
||||
4. launcher starts `Wow.exe` with working directory set to installation root,
|
||||
1. launcher removes every `SET accountName ...` line from
|
||||
`<install-root>/WTF/Config.wtf` when that file exists
|
||||
2. launcher resolves `<install-root>/Wow.exe`
|
||||
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:
|
||||
- `MOONWELL_LAUNCH_ACCOUNT=<account>`
|
||||
- `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
|
||||
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.
|
||||
|
||||
|
||||
@@ -351,6 +351,7 @@ class HomeScreenBloc extends Bloc<HomeScreenEvent, HomeScreenState> {
|
||||
_gameLaunchPending = true;
|
||||
try {
|
||||
final installationDir = outputPath.toFilePath();
|
||||
await _gameInstallationService.removeSavedAccountName(installationDir);
|
||||
await _gameInstallationService.clearCache(installationDir);
|
||||
final authorization = await _launcherApiClient.issueGameTicket(
|
||||
_session.accessToken,
|
||||
|
||||
@@ -220,6 +220,27 @@ class GameInstallationService {
|
||||
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(
|
||||
String installationDir, {
|
||||
required LauncherGameTicket authorization,
|
||||
|
||||
@@ -148,11 +148,15 @@ void main() {
|
||||
final manifest = ClientManifest.fromJson(<String, Object?>{
|
||||
'files': <Map<String, Object?>>[],
|
||||
});
|
||||
final gameService = _TrackingGameInstallationService();
|
||||
final launchOperations = <String>[];
|
||||
final gameService = _TrackingGameInstallationService(launchOperations);
|
||||
final bloc = HomeScreenBloc(
|
||||
clientSyncUseCase: _CapturingClientSyncUseCase(),
|
||||
gameInstallationService: gameService,
|
||||
launcherApiClient: _FakeLauncherApiClient(newsItems: const []),
|
||||
launcherApiClient: _FakeLauncherApiClient(
|
||||
newsItems: const [],
|
||||
launchOperations: launchOperations,
|
||||
),
|
||||
preferencesRepository: _FakePreferencesRepository(
|
||||
outputDir: Uri.directory('C:/World of Warcraft'),
|
||||
),
|
||||
@@ -169,6 +173,13 @@ void main() {
|
||||
await pumpEventQueue(times: 20);
|
||||
|
||||
expect(gameService.launchCount, 1);
|
||||
expect(gameService.savedAccountNameRemovalCount, 1);
|
||||
expect(launchOperations, <String>[
|
||||
'removeSavedAccountName',
|
||||
'clearCache',
|
||||
'issueGameTicket',
|
||||
'launchGame',
|
||||
]);
|
||||
expect(gameService.launchedWith?.account, 'PLAYERONE');
|
||||
expect(gameService.launchedWith?.ticket, 'A1B2C3D4E5F6G7H8');
|
||||
expect(bloc.state.model.isGameRunning, isTrue);
|
||||
@@ -230,12 +241,14 @@ class _FakeLauncherApiClient extends LauncherApiClient {
|
||||
this.manifest,
|
||||
this.realms = const [],
|
||||
this.account = const LauncherAccount(username: ''),
|
||||
this.launchOperations,
|
||||
});
|
||||
|
||||
final List<LauncherNewsItem> newsItems;
|
||||
final ClientManifest? manifest;
|
||||
final List<LauncherRealm> realms;
|
||||
final LauncherAccount account;
|
||||
final List<String>? launchOperations;
|
||||
int manifestRequestCount = 0;
|
||||
int gameTicketRequestCount = 0;
|
||||
|
||||
@@ -251,6 +264,7 @@ class _FakeLauncherApiClient extends LauncherApiClient {
|
||||
|
||||
@override
|
||||
Future<LauncherGameTicket> issueGameTicket(String accessToken) async {
|
||||
launchOperations?.add('issueGameTicket');
|
||||
gameTicketRequestCount += 1;
|
||||
return LauncherGameTicket(
|
||||
account: 'PLAYERONE',
|
||||
@@ -284,21 +298,34 @@ class _FakeGameInstallationService extends GameInstallationService {
|
||||
}
|
||||
|
||||
class _TrackingGameInstallationService extends _FakeGameInstallationService {
|
||||
_TrackingGameInstallationService(this.launchOperations);
|
||||
|
||||
final List<String> launchOperations;
|
||||
final Completer<int> exitCode = Completer<int>();
|
||||
int launchCount = 0;
|
||||
int savedAccountNameRemovalCount = 0;
|
||||
LauncherGameTicket? launchedWith;
|
||||
|
||||
@override
|
||||
Future<bool> hasClientExecutable(String installationDir) async => true;
|
||||
|
||||
@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
|
||||
Future<GameProcessHandle> launchGame(
|
||||
String installationDir, {
|
||||
required LauncherGameTicket authorization,
|
||||
}) async {
|
||||
launchOperations.add('launchGame');
|
||||
launchCount += 1;
|
||||
launchedWith = authorization;
|
||||
return GameProcessHandle(pid: 42, exitCode: exitCode.future);
|
||||
|
||||
@@ -92,6 +92,30 @@ void main() {
|
||||
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 {
|
||||
final tempPath =
|
||||
'${rootDirectory.path}${Platform.pathSeparator}Data${Platform.pathSeparator}patches${Platform.pathSeparator}common-2.MPQ.moonwell.part';
|
||||
|
||||
Reference in New Issue
Block a user