Skip to content

Commit

Permalink
Fixes #1355
Browse files Browse the repository at this point in the history
  • Loading branch information
1-alex98 committed Mar 5, 2020
1 parent aa9d6be commit e1008be
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Data
Expand Down Expand Up @@ -32,6 +34,7 @@ public class ClientProperties {
private boolean showIceAdapterDebugWindow;
private String statusPageUrl;
private Map<String, String> links = new HashMap<>();
private List<String> vanillaGameHashes = new ArrayList<>();

@Data
public static class News {
Expand Down
34 changes: 27 additions & 7 deletions src/main/java/com/faforever/client/game/GamePathHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;

@Component
public class GamePathHandler implements InitializingBean {
Expand Down Expand Up @@ -55,30 +58,47 @@ public void afterPropertiesSet() {
@Subscribe
public void onGameDirectoryChosenEvent(GameDirectoryChosenEvent event) {
Path path = event.getPath();
Optional<CompletableFuture<Path>> future = event.getFuture();

if (path == null || !Files.isDirectory(path)) {
notificationService.addNotification(new ImmediateNotification(i18n.get("gameChosen.invalidPath"), i18n.get("gameChosen.couldNotLocatedGame"), Severity.WARN));
if (path == null) {
notificationService.addNotification(new ImmediateNotification(i18n.get("gameSelect.select.invalidPath"), i18n.get("gamePath.select.noneChosen"), Severity.WARN));
future.ifPresent(pathCompletableFuture -> pathCompletableFuture.completeExceptionally(new CancellationException("User cancelled")));
return;
}

if (!Files.isRegularFile(path.resolve(PreferencesService.FORGED_ALLIANCE_EXE)) && !Files.isRegularFile(path.resolve(PreferencesService.SUPREME_COMMANDER_EXE))) {
onGameDirectoryChosenEvent(new GameDirectoryChosenEvent(path.resolve("bin")));
return;
Path pathWithBin = path.resolve("bin");
if (Files.isDirectory(pathWithBin)) {
path = pathWithBin;
}

// At this point, path points to the "bin" directory
Path gamePath = path.getParent();

String gamePathValidWithError;
try {
gamePathValidWithError = preferencesService.isGamePathValidWithError(gamePath);
} catch (Exception e) {
notificationService.addImmediateErrorNotification(e, "gamePath.select.error");
future.ifPresent(pathCompletableFuture -> pathCompletableFuture.completeExceptionally(e));
return;
}
if (gamePathValidWithError != null) {
notificationService.addNotification(new ImmediateNotification(i18n.get("gameSelect.select.invalidPath"), i18n.get(gamePathValidWithError), Severity.WARN));
future.ifPresent(pathCompletableFuture -> pathCompletableFuture.completeExceptionally(new IllegalArgumentException("Invalid path")));
return;
}


logger.info("Found game path at {}", gamePath);
preferencesService.getPreferences().getForgedAlliance().setInstallationPath(gamePath);
preferencesService.storeInBackground();
future.ifPresent(pathCompletableFuture -> pathCompletableFuture.complete(gamePath));
}


private void detectGamePath() {
for (Path path : USUAL_GAME_PATHS) {
if (preferencesService.isGamePathValid(path.resolve("bin"))) {
onGameDirectoryChosenEvent(new GameDirectoryChosenEvent(path));
onGameDirectoryChosenEvent(new GameDirectoryChosenEvent(path, Optional.empty()));
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.paint.Color;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
Expand All @@ -40,6 +44,8 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -334,8 +340,48 @@ public Path getThemesDirectory() {
return getFafDataDirectory().resolve("themes");
}

@SneakyThrows
public boolean isGamePathValid() {
return preferences.getForgedAlliance().getInstallationPath() != null && isGamePathValid(preferences.getForgedAlliance().getInstallationPath().resolve("bin"));
return isGamePathValidWithError(preferences.getForgedAlliance().getInstallationPath()) == null;
}

public String isGamePathValidWithError(Path installationPath) throws IOException, NoSuchAlgorithmException {
boolean valid = installationPath != null && isGamePathValid(installationPath.resolve("bin"));
if (!valid) {
return "gamePath.select.noValidExe";
}
Path binPath = installationPath.resolve("bin");
String exeHash;
if (Files.exists(binPath.resolve(FORGED_ALLIANCE_EXE))) {
exeHash = sha256OfFile(binPath.resolve(FORGED_ALLIANCE_EXE));
} else {
exeHash = sha256OfFile(binPath.resolve(SUPREME_COMMANDER_EXE));
}
for (String hash : clientProperties.getVanillaGameHashes()) {
if (hash.equals(exeHash)) {
return "gamePath.select.vanillaGameSelected";
}
}

if (binPath.equals(getFafBinDirectory())) {
return "gamePath.select.fafDataSelected";
}

return null;
}

private String sha256OfFile(Path path) throws IOException, NoSuchAlgorithmException {
byte[] buffer = new byte[8192];
int count;
MessageDigest digest = MessageDigest.getInstance("SHA-256");
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path.toFile()));
while ((count = bis.read(buffer)) > 0) {
digest.update(buffer, 0, count);
}
bis.close();

byte[] hash = digest.digest();
return new String(Base64.encodeBase64(hash));
}

public boolean isGamePathValid(Path binPath) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

import java.io.File;
import java.lang.invoke.MethodHandles;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import static javafx.application.Platform.runLater;

Expand All @@ -28,6 +30,7 @@ public class GameDirectoryRequiredHandler implements InitializingBean {

private final EventBus eventBus;
private final I18n i18n;
private CompletableFuture<Path> future;

@Override
public void afterPropertiesSet() {
Expand All @@ -43,7 +46,10 @@ public void onChooseGameDirectory(GameDirectoryChooseEvent event) {

logger.info("User selected game directory: {}", result);

eventBus.post(new GameDirectoryChosenEvent(Optional.ofNullable(result).map(File::toPath).orElse(null)));
Path path = Optional.ofNullable(result).map(File::toPath).orElse(null);
eventBus.post(new GameDirectoryChosenEvent(path, event.getFuture()));

});
}

}
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
package com.faforever.client.ui.preferences.event;

import lombok.Value;
import org.jetbrains.annotations.Nullable;

import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

@Value
public class GameDirectoryChosenEvent {
@Nullable
private Path path;

public GameDirectoryChosenEvent(@Nullable Path path) {
this.path = path;
}

@Nullable
public Path getPath() {
return path;
}
private Optional<CompletableFuture<Path>> future;
}
3 changes: 3 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ faf-client:
spookiesUrl: https://spooky.github.io/unitdb/#/
rackOversUrl: https://unitdb.faforever.com?settings64=eyJwcmV2aWV3Q29ybmVyIjoiTm9uZSJ9

vanillaGameHashes:
- 1AFBCD0CA85546470660FA8AC09B230E870EC65C8D1B33FEB6731BB5D4C366C5

map-generator:
repoAndOwnerName: FAForever/Neroxis-Map-Generator
queryLatestVersionUrl: https://api.github.com/repos/${faf-client.map-generator.repoAndOwnerName}/releases/latest
Expand Down
9 changes: 6 additions & 3 deletions src/main/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -667,15 +667,18 @@ map.created=Created:
modType.ui=Ui mod
modType.sim=Sim mod
menu.connect=Connecting
gameChosen.invalidPath=Invalid path
gameChosen.couldNotLocatedGame=The specified location does not contain a valid Supreme Commander: Forged Alliance installation.
gameSelect.select.invalidPath=Invalid path
gamePath.select.noneChosen=You did not select a game location!
gamePath.select.noValidExe=The specified location does not contain a valid Supreme Commander.exe and is therefore is no valid Forged Alliance installation.
gamePath.select.vanillaGameSelected=The specified location is a vanilla installation, you need to select the extension Supreme Commander Forged Alliance.
gamePath.select.fafDataSelected=The specified location is the Forged Alliance Forever game data directory, please select the Forged Alliance installation instead.
gamePath.select.error=Something went wrong selecting game path
userInfo.statistics.errorLoading=Could not load statistics
settings.general.unitDatabase=Unit database
unitDatabase.rackover=Rackover
unitDatabase.spooky=Spooky
game.player.globalRating=One of the player's global ratings
game.player.ladderRating=One of the player's ladder ratings

mapVault.ladder=Ladder maps
news.showLadderMaps=Show ladder maps
ranked1v1.showMaps=Show ladder maps
Expand Down
14 changes: 11 additions & 3 deletions src/test/java/com/faforever/client/game/GamePathHandlerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.nio.file.Paths;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;

Expand All @@ -35,13 +39,17 @@ public void setUp() throws Exception {

@Test
public void testNotificationOnEmptyString() throws Exception {
instance.onGameDirectoryChosenEvent(new GameDirectoryChosenEvent(Paths.get("")));
CompletableFuture<Path> completableFuture = new CompletableFuture<>();
instance.onGameDirectoryChosenEvent(new GameDirectoryChosenEvent(null, Optional.of(completableFuture)));
verify(notificationService).addNotification(any(ImmediateNotification.class));
assertThat(completableFuture.isCompletedExceptionally(), is(true));
}

@Test
public void testNotificationOnNull() throws Exception {
instance.onGameDirectoryChosenEvent(new GameDirectoryChosenEvent(null));
CompletableFuture<Path> completableFuture = new CompletableFuture<>();
instance.onGameDirectoryChosenEvent(new GameDirectoryChosenEvent(null, Optional.of(completableFuture)));
verify(notificationService).addNotification(any(ImmediateNotification.class));
assertThat(completableFuture.isCompletedExceptionally(), is(true));
}
}

0 comments on commit e1008be

Please sign in to comment.