Synchronize the client and select installed language packs #3

Merged
sindoring merged 4 commits from feat/ruru-release-20261010 into master 2026-10-10 16:50:43 +03:00
6 changed files with 58 additions and 1 deletions
Showing only changes of commit 2fb6774ec6 - Show all commits
+2
View File
@@ -98,6 +98,8 @@ or local-only API URLs.
plus legacy `WoW.exe` and DLLs directly in the installation root or `WoW`.
Remove unused Data subdirectories only after their children, without recursive
deletion. Preserve unrelated files and user state outside these areas.
- Language archives under `Data/ruRU` or `WoW/Data/ruRU` use the regular manifest sync.
Pass `WOW_LOCALE=auto` to the game so its selected install determines the locale.
- Downloads must write to `*.moonwell.part`, verify size and SHA-256, then
replace the destination file only after successful verification.
- Local scans should ignore volatile client directories and launcher metadata:
+6
View File
@@ -73,6 +73,12 @@ folder. For builds that pin an endpoint, set
`--dart-define=MOONWELL_AUTH_ADDRESS=login.example:3724` instead. This endpoint is
the vMaNGOS auth server, separate from the realm's world port and the Web API.
Russian 1.12.1 language archives are regular manifest files under `Data/ruRU`
(or `WoW/Data/ruRU` for a nested install). They are downloaded and verified with
the same size and SHA-256 checks as the base client. The launcher passes
`WOW_LOCALE=auto` to the game: an installed pack selects Russian, and a base-only
install selects English, regardless of the launcher's inherited environment.
Example run command:
```powershell
+6
View File
@@ -286,6 +286,8 @@ When the user presses `Play`:
- `WOW_HOST=<auth-address>`
- `WOW_DATA=<absolute-data-directory>`
- `BENILLA_HOME=<install-root>/benilla-config`
- `WOW_LOCALE=auto` (select the installed `Data/ruRU` or `WoW/Data/ruRU` language pack,
otherwise English; override any locale inherited from the launcher's parent)
6. launcher retains a process handle; while the process is alive, it disables repeated game launches
and client synchronization
7. when the process exits, the launcher returns to the ready-to-play state
@@ -297,6 +299,10 @@ If the executable, data directory, or auth endpoint is missing or invalid,
launch fails with an error. A consumed ticket cannot authenticate again; close
the client and press Play for a fresh ticket after a session ends.
Language archives are ordinary client-manifest entries, including their nested paths,
sizes and SHA-256 hashes. Synchronization verifies and retains required `ruRU` files
and removes obsolete archives under the same manifest-authoritative Data policy.
## Error Behavior
The launcher surfaces errors for:
@@ -304,6 +304,8 @@ class GameInstallationService {
'WOW_PASS': authorization.ticket,
'WOW_HOST': authAddress,
'WOW_DATA': dataPath,
// Let the client select Data/ruRU independently of the launcher's parent environment.
'WOW_LOCALE': 'auto',
'BENILLA_HOME': p.join(rootPath, 'benilla-config'),
},
);
+1 -1
View File
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.5+8
version: 1.0.7+10
environment:
sdk: ">=3.10.0"
@@ -421,6 +421,7 @@ void main() {
'WOW_PASS': 'A1B2C3D4E5F6G7H8',
'WOW_HOST': '127.0.0.1:3724',
'WOW_DATA': '${rootDirectory.path}${Platform.pathSeparator}Data',
'WOW_LOCALE': 'auto',
'BENILLA_HOME':
'${rootDirectory.path}${Platform.pathSeparator}benilla-config',
});
@@ -479,6 +480,46 @@ void main() {
expect(recordingService.environment?['WOW_DATA'], nestedData.path);
});
test('verifies and retains Russian archives before launching', () async {
final paths = [
'MoonWell.exe',
'Data/common.MPQ',
'Data/ruRU/patch-1.MPQ',
'Data/ruRU/patch-2.MPQ',
'Data/ruRU/patch-3.MPQ',
];
for (final path in paths.skip(2)) {
final file = File(service.resolveClientPath(rootDirectory.path, path));
await file.parent.create(recursive: true);
await file.writeAsString('localized archive: $path');
}
final manifest = await _manifestForPaths(service, rootDirectory, paths);
final snapshot = await service.scanInstallation(
rootDirectory.path,
manifest: manifest,
);
expect(snapshot.buildHash, manifest.buildHash);
expect(snapshot.stalePaths, isEmpty);
final recordingService = _RecordingGameInstallationService();
await recordingService.launchGame(
rootDirectory.path,
authorization: _validTicket(),
);
expect(recordingService.environment?['WOW_LOCALE'], 'auto');
expect(
recordingService.environment?['WOW_DATA'],
'${rootDirectory.path}${Platform.pathSeparator}Data',
);
for (final path in paths.skip(2)) {
expect(
await File(
service.resolveClientPath(rootDirectory.path, path),
).readAsString(),
'localized archive: $path',
);
}
});
test('configured auth address overrides the install realmlist', () async {
final configured = _RecordingGameInstallationService(
authAddress: 'login.example:3725',