Initial commit - PedaloLauncher with Microsoft Auth

- Modern Microsoft OAuth2 authentication
- Always-available connect button (never gets disabled)
- Dynamic port allocation (25565-25575) for Edge compatibility
- Auto-connect to PEDALO server (pedalo.vidoks.fr:25565)
- Dark theme UI with modern design
- Version management with auto-download
- Fixed browser closure handling

Features:
 Button always clickable - no more gray/disabled state
 Compatible with Microsoft Edge, Chrome, Firefox
 Automatic port conflict resolution
 Clear error messages and user guidance
 Professional launcher interface

Ready for PEDALO server deployment!
This commit is contained in:
VIDOKS 2025-09-05 02:00:17 +02:00
commit 8bafe167ac
13 changed files with 2836 additions and 0 deletions

67
.gitignore vendored Normal file
View File

@ -0,0 +1,67 @@
### Java ###
*.class
*.jar
*.war
*.ear
*.aar
hs_err_pid*
replay_pid*
### Build directories ###
build/
out/
target/
dist/
temp/
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
### Windows ###
Thumbs.db
ehthumbs.db
Desktop.ini
### Logs ###
*.log
### Temporary files ###
*.tmp
*.temp
*~
### Claude Code ###
.claude/

1
MANIFEST.MF Normal file
View File

@ -0,0 +1 @@
Main-Class: com.minecraftlauncher.Main

70
README.md Normal file
View File

@ -0,0 +1,70 @@
# Minecraft Launcher
Launcher Minecraft personnalisé avec authentification Microsoft moderne et connexion automatique au serveur PEDALO.
## Fonctionnalités
- 🔐 **Authentification Microsoft** avec OAuth2 moderne
- 🎮 **Connexion automatique** au serveur PEDALO (pedalo.vidoks.fr:25565)
- 📦 **Gestion des versions** Minecraft avec téléchargement automatique
- 🎨 **Interface moderne** avec thème sombre
- 🔄 **Bouton de connexion toujours disponible** - cliquez autant de fois que nécessaire
- 🌐 **Compatible avec tous les navigateurs** (Edge, Chrome, Firefox)
## Installation
### Prérequis
- Java 8 ou supérieur
- Connexion Internet
- Compte Microsoft avec Minecraft Java Edition
### Utilisation
```bash
java -jar MinecraftLauncher.jar
```
## Structure du Projet
```
MinecraftLauncher/
├── src/com/minecraftlauncher/
│ ├── Main.java # Point d'entrée
│ ├── auth/
│ │ └── ModernAuthManager.java # Authentification Microsoft OAuth2
│ ├── game/
│ │ └── GameLauncher.java # Lancement du jeu
│ ├── model/
│ │ └── Account.java # Modèle de compte utilisateur
│ └── ui/
│ ├── LauncherFrame.java # Interface principale
│ ├── VersionsTabDialog.java # Dialogue de versions
│ ├── InstalledVersionsDialog.java # Dialogue versions installées
│ └── DarkThemeUtils.java # Utilitaires thème sombre
├── lib/ # Bibliothèques (Gson)
├── MANIFEST.MF # Manifeste JAR
└── MinecraftLauncher.jar # Exécutable final
```
## Développement
### Compilation
```bash
javac -cp "lib/*" -d build src/com/minecraftlauncher/*.java src/com/minecraftlauncher/*/*.java
jar cfm MinecraftLauncher.jar MANIFEST.MF -C build .
```
### Authentification Microsoft
Le launcher utilise l'API OAuth2 v2.0 de Microsoft avec :
- Client ID : `54fd49e4-2103-4044-9603-2b028c814ec3`
- Port local dynamique (25565-25575) pour éviter les conflits
- Gestion automatique des timeouts et fermetures de navigateur
### Fonctionnalités Spéciales
- **Bouton toujours disponible** : Plus de blocage si le navigateur se ferme
- **Port dynamique** : Trouve automatiquement un port libre
- **Messages informatifs** : Indique clairement l'état de l'authentification
- **Compatibilité Edge** : Gestion spéciale pour Microsoft Edge
## Auteur
Développé avec l'aide de Claude Code (Anthropic)

2
src/META-INF/MANIFEST.MF Normal file
View File

@ -0,0 +1,2 @@
Main-Class: com.minecraftlauncher.Main

View File

@ -0,0 +1,52 @@
// src/com/minecraftlauncher/Main.java
package com.minecraftlauncher;
import com.minecraftlauncher.ui.LauncherFrame;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class Main {
public static void main(String[] args) {
try {
// Configuration avancée pour une meilleure apparence
System.setProperty("awt.useSystemAAFontSettings","on");
System.setProperty("swing.aatext", "true");
System.setProperty("swing.plaf.nimbus.useJavaFont", "true");
// Utiliser l'apparence système pour une meilleure intégration
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Configuration supplémentaire pour le thème sombre
UIManager.put("control", new java.awt.Color(17, 24, 39));
UIManager.put("info", new java.awt.Color(17, 24, 39));
UIManager.put("nimbusBase", new java.awt.Color(17, 24, 39));
UIManager.put("nimbusAlertYellow", new java.awt.Color(248, 187, 0));
UIManager.put("nimbusDisabledText", new java.awt.Color(128, 128, 128));
UIManager.put("nimbusFocus", new java.awt.Color(115, 164, 209));
UIManager.put("nimbusGreen", new java.awt.Color(176, 179, 50));
UIManager.put("nimbusInfoBlue", new java.awt.Color(66, 139, 221));
UIManager.put("nimbusLightBackground", new java.awt.Color(18, 30, 49));
UIManager.put("nimbusOrange", new java.awt.Color(191, 98, 4));
UIManager.put("nimbusRed", new java.awt.Color(169, 46, 34));
UIManager.put("nimbusSelectedText", new java.awt.Color(255, 255, 255));
UIManager.put("nimbusSelectionBackground", new java.awt.Color(104, 93, 156));
UIManager.put("text", new java.awt.Color(230, 230, 230));
} catch (Exception e) {
System.err.println("Impossible de configurer l'apparence système: " + e.getMessage());
// Garder l'apparence par défaut si erreur
}
SwingUtilities.invokeLater(() -> {
try {
System.out.println("[START] Demarrage du Minecraft Launcher Moderne...");
LauncherFrame frame = new LauncherFrame();
frame.setVisible(true);
System.out.println("[OK] Interface lancee avec succes !");
} catch (Exception e) {
System.err.println("[ERROR] Erreur lors du lancement: " + e.getMessage());
e.printStackTrace();
}
});
}
}

View File

@ -0,0 +1,636 @@
// src/com/minecraftlauncher/auth/ModernAuthManager.java
package com.minecraftlauncher.auth;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.minecraftlauncher.model.Account;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.awt.Desktop;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.UUID;
public class ModernAuthManager {
private static final String CLIENT_ID = "54fd49e4-2103-4044-9603-2b028c814ec3";
private static int LOCAL_PORT = 25565;
private static String REDIRECT_URI = "http://localhost:25565";
private static final String SCOPE = "XboxLive.signin offline_access";
// Endpoints OAuth2 v2.0 modernes
private static final String MICROSOFT_AUTH_URL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize";
private static final String MICROSOFT_TOKEN_URL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token";
private static final String XBOX_AUTH_URL = "https://user.auth.xboxlive.com/user/authenticate";
private static final String XSTS_AUTH_URL = "https://xsts.auth.xboxlive.com/xsts/authorize";
private static final String MINECRAFT_AUTH_URL = "https://api.minecraftservices.com/authentication/login_with_xbox";
private static final String MINECRAFT_PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile";
private static final String MINECRAFT_STORE_URL = "https://api.minecraftservices.com/entitlements/mcstore";
private Gson gson = new Gson();
private HttpServer localServer;
private CountDownLatch authLatch;
private String receivedAuthCode;
private String stateToken;
private volatile boolean authCancelled = false;
public Account authenticate() {
try {
System.out.println("=== AUTHENTIFICATION MICROSOFT MODERNE ===");
System.out.println("Capture automatique du code d'autorisation");
// Réinitialiser l'état d'annulation
authCancelled = false;
receivedAuthCode = null;
// Générer un token state pour la sécurité CSRF
stateToken = UUID.randomUUID().toString();
// Démarrer le serveur HTTP local
startLocalServer();
// Étape 1: Ouvrir le navigateur pour l'authentification
String authUrl = buildAuthUrl();
System.out.println("URL de connexion: " + authUrl);
System.out.println("Démarrage du serveur local sur le port " + LOCAL_PORT);
if (Desktop.isDesktopSupported()) {
Desktop.getDesktop().browse(URI.create(authUrl));
}
System.out.println("En attente de la réponse d'authentification...");
// Attendre la réponse avec timeout optimisé pour Edge (15 secondes par cycle)
boolean received = false;
int attempts = 0;
while (!received && attempts < 20 && !authCancelled) {
received = authLatch.await(15, TimeUnit.SECONDS);
attempts++;
if (!received && !authCancelled) {
System.out.println("Attente... (" + (attempts * 15) + "s écoulées - Le bouton reste disponible pour réessayer)");
// Après 2 tentatives (30s), encourager l'utilisateur à réessayer
if (attempts >= 2) {
System.out.println("[INFO] Si Microsoft Edge s'est fermé, cliquez à nouveau sur 'Se connecter à Microsoft' - Le système trouvera automatiquement un port libre");
}
// Après 4 tentatives (1 minute), conseil spécifique Edge
if (attempts >= 4) {
System.out.println("[CONSEIL EDGE] Edge peut parfois rester en arrière-plan. Le système utilise maintenant le port " + LOCAL_PORT + " pour éviter les conflits");
}
}
}
// Arrêter le serveur
stopLocalServer();
if (authCancelled) {
throw new RuntimeException("Authentification annulée - Le bouton reste disponible pour réessayer.");
}
if (!received) {
throw new RuntimeException("Timeout d'authentification - Le navigateur s'est peut-être fermé. Cliquez à nouveau sur 'Se connecter à Microsoft' pour réessayer.");
}
if (receivedAuthCode == null) {
throw new RuntimeException("Code d'authentification non reçu - Le bouton reste disponible pour réessayer immédiatement.");
}
System.out.println("Code d'autorisation reçu automatiquement");
// Flux d'authentification complet
return performFullAuthentication(receivedAuthCode);
} catch (Exception e) {
stopLocalServer();
System.err.println("Erreur d'authentification: " + e.getMessage());
// Messages d'erreur plus informatifs
String errorMessage = e.getMessage();
if (errorMessage == null) {
errorMessage = "Erreur d'authentification inconnue - Le bouton reste disponible pour réessayer";
}
// Conseils supplémentaires selon le type d'erreur
if (errorMessage.contains("Timeout") || errorMessage.contains("fermé")) {
System.out.println("[CONSEIL] Le navigateur s'est fermé avant la connexion. Cliquez simplement à nouveau sur 'Se connecter à Microsoft' pour relancer immédiatement l'authentification.");
} else if (errorMessage.contains("java.net.ConnectException")) {
errorMessage = "Impossible de se connecter à Microsoft. Vérifiez votre connexion Internet et réessayez.";
} else if (errorMessage.contains("Address already in use")) {
System.out.println("[INFO] Le serveur local se libère... Veuillez réessayer dans quelques secondes.");
errorMessage = "Serveur local occupé - Réessayez dans 2-3 secondes";
}
e.printStackTrace();
throw new RuntimeException(errorMessage, e);
}
}
public Account authenticateWithCode(String authCode) {
try {
System.out.println("=== TRAITEMENT DU CODE D'AUTORISATION ===");
// Flux d'authentification complet
return performFullAuthentication(authCode);
} catch (Exception e) {
System.err.println("Erreur d'authentification: " + e.getMessage());
e.printStackTrace();
throw new RuntimeException("Échec de l'authentification: " + e.getMessage(), e);
}
}
private void startLocalServer() throws IOException {
// Arrêter tout serveur existant d'abord
stopLocalServer();
// Essayer de trouver un port libre
for (int port = 25565; port <= 25575; port++) {
try {
authLatch = new CountDownLatch(1);
localServer = HttpServer.create(new InetSocketAddress(port), 0);
// Mettre à jour le port et l'URI de redirection
LOCAL_PORT = port;
REDIRECT_URI = "http://localhost:" + port;
localServer.createContext("/", new AuthHandler());
localServer.setExecutor(null);
localServer.start();
System.out.println("Serveur HTTP local démarré sur http://localhost:" + LOCAL_PORT);
return; // Succès, sortir de la boucle
} catch (IOException e) {
if (e.getMessage().contains("Address already in use")) {
System.out.println("[INFO] Port " + port + " occupé, essai du port " + (port + 1));
if (port == 25575) {
// Dernière tentative, essayer de forcer la libération du port principal
try {
Thread.sleep(3000);
LOCAL_PORT = 25565;
REDIRECT_URI = "http://localhost:25565";
localServer = HttpServer.create(new InetSocketAddress(25565), 0);
localServer.createContext("/", new AuthHandler());
localServer.setExecutor(null);
localServer.start();
System.out.println("Serveur HTTP local redémarré sur le port 25565 après délai");
return;
} catch (Exception finalException) {
throw new IOException("Impossible de démarrer le serveur local - Tous les ports occupés. Fermez complètement l'application et relancez.");
}
}
continue; // Essayer le port suivant
} else {
throw e;
}
}
}
}
private void stopLocalServer() {
if (localServer != null) {
try {
localServer.stop(0); // Arrêt immédiat
System.out.println("Serveur HTTP local arrêté (port " + LOCAL_PORT + ")");
} catch (Exception e) {
System.out.println("Avertissement: Erreur lors de l'arrêt du serveur local: " + e.getMessage());
} finally {
localServer = null; // Toujours libérer la référence
// Délai plus long pour Edge qui garde les connexions ouvertes
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
}
public void cancelAuthentication() {
authCancelled = true;
stopLocalServer();
if (authLatch != null) {
authLatch.countDown();
}
System.out.println("[INFO] Authentification annulée - Le bouton est maintenant disponible pour une nouvelle tentative");
}
private class AuthHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
String query = exchange.getRequestURI().getQuery();
System.out.println("Requête reçue: " + query);
if (query != null) {
Map<String, String> params = parseQuery(query);
if (params.containsKey("error")) {
String error = params.get("error");
String errorDesc = params.get("error_description");
System.err.println("Erreur OAuth: " + error + " - " + errorDesc);
sendErrorResponse(exchange, error, errorDesc);
authLatch.countDown();
return;
}
if (params.containsKey("code") && params.containsKey("state")) {
String receivedState = params.get("state");
if (!stateToken.equals(receivedState)) {
System.err.println("Token state invalide - possible attaque CSRF");
sendErrorResponse(exchange, "invalid_state", "Token state invalide");
authLatch.countDown();
return;
}
receivedAuthCode = params.get("code");
System.out.println("Code d'autorisation capturé: " + receivedAuthCode.substring(0, Math.min(10, receivedAuthCode.length())) + "...");
sendSuccessResponse(exchange);
authLatch.countDown();
return;
}
}
// Si pas de paramètres, afficher la page d'accueil
sendWelcomePage(exchange);
}
}
private void sendWelcomePage(HttpExchange exchange) throws IOException {
String response = String.format(
"<html>" +
"<head><title>Minecraft Launcher - Authentification</title></head>" +
"<body style=\"font-family: Arial; text-align: center; padding: 50px;\">" +
"<h2>Authentification Minecraft Launcher</h2>" +
"<p>Redirection vers Microsoft OAuth2...</p>" +
"<script>" +
"setTimeout(function() {" +
"window.location.href = '%s';" +
"}, 2000);" +
"</script>" +
"</body>" +
"</html>", buildAuthUrl());
exchange.getResponseHeaders().add("Content-Type", "text/html; charset=utf-8");
exchange.sendResponseHeaders(200, response.getBytes(StandardCharsets.UTF_8).length);
exchange.getResponseBody().write(response.getBytes(StandardCharsets.UTF_8));
exchange.close();
}
private void sendSuccessResponse(HttpExchange exchange) throws IOException {
String response =
"<html>" +
"<head><title>Authentification Réussie</title></head>" +
"<body style=\"font-family: Arial; text-align: center; padding: 50px; background: #e8f5e8;\">" +
"<div style=\"max-width: 500px; margin: 0 auto; padding: 30px; background: white; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);\">" +
"<div style=\"color: #4CAF50; font-size: 48px; margin-bottom: 20px;\">✓</div>" +
"<h2 style=\"color: #4CAF50; margin-bottom: 20px;\">Authentification Réussie !</h2>" +
"<p style=\"color: #666; font-size: 16px; margin-bottom: 30px;\">" +
"Vous êtes maintenant connecté à votre compte Microsoft.<br>" +
"Vous pouvez fermer cette fenêtre et retourner au launcher." +
"</p>" +
"<div style=\"background: #f8f9fa; padding: 15px; border-radius: 5px; border-left: 4px solid #4CAF50;\">" +
"<strong>Prochaines étapes :</strong><br>" +
"1. Retournez au launcher Minecraft<br>" +
"2. Sélectionnez une version à télécharger<br>" +
"3. Lancez votre jeu !" +
"</div>" +
"</div>" +
"<script>" +
"setTimeout(function() {" +
"window.close();" +
"}, 5000);" +
"</script>" +
"</body>" +
"</html>";
exchange.getResponseHeaders().add("Content-Type", "text/html; charset=utf-8");
exchange.sendResponseHeaders(200, response.getBytes(StandardCharsets.UTF_8).length);
exchange.getResponseBody().write(response.getBytes(StandardCharsets.UTF_8));
exchange.close();
}
private void sendErrorResponse(HttpExchange exchange, String error, String description) throws IOException {
String response = String.format(
"<html>" +
"<head><title>Erreur d'Authentification</title></head>" +
"<body style=\"font-family: Arial; text-align: center; padding: 50px; background: #ffeaea;\">" +
"<div style=\"max-width: 500px; margin: 0 auto; padding: 30px; background: white; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);\">" +
"<div style=\"color: #f44336; font-size: 48px; margin-bottom: 20px;\">✗</div>" +
"<h2 style=\"color: #f44336; margin-bottom: 20px;\">Erreur d'Authentification</h2>" +
"<p style=\"color: #666; margin-bottom: 20px;\"><strong>Erreur :</strong> %s</p>" +
"<p style=\"color: #666; margin-bottom: 30px;\"><strong>Description :</strong> %s</p>" +
"<div style=\"background: #fff3cd; padding: 15px; border-radius: 5px; border-left: 4px solid #ffc107;\">" +
"<strong>Solutions possibles :</strong><br>" +
"• Réessayez l'authentification<br>" +
"• Vérifiez votre connexion Internet<br>" +
"• Assurez-vous d'avoir un compte Microsoft valide avec Minecraft" +
"</div>" +
"</div>" +
"</body>" +
"</html>", error, description);
exchange.getResponseHeaders().add("Content-Type", "text/html; charset=utf-8");
exchange.sendResponseHeaders(500, response.getBytes(StandardCharsets.UTF_8).length);
exchange.getResponseBody().write(response.getBytes(StandardCharsets.UTF_8));
exchange.close();
}
private Map<String, String> parseQuery(String query) {
Map<String, String> params = new HashMap<>();
if (query == null) return params;
for (String param : query.split("&")) {
String[] pair = param.split("=", 2);
if (pair.length == 2) {
try {
params.put(
URLDecoder.decode(pair[0], StandardCharsets.UTF_8),
URLDecoder.decode(pair[1], StandardCharsets.UTF_8)
);
} catch (Exception e) {
System.err.println("Erreur décodage paramètre: " + param);
}
}
}
return params;
}
private String buildAuthUrl() {
return MICROSOFT_AUTH_URL +
"?client_id=" + CLIENT_ID +
"&response_type=code" +
"&redirect_uri=" + URLEncoder.encode(REDIRECT_URI, StandardCharsets.UTF_8) +
"&scope=" + URLEncoder.encode(SCOPE, StandardCharsets.UTF_8) +
"&response_mode=query" +
"&state=" + stateToken +
"&prompt=select_account";
}
private Account performFullAuthentication(String authCode) throws Exception {
// Étape 1: Échanger le code contre un token d'accès Microsoft
System.out.println("Étape 1: Obtention du token d'accès Microsoft...");
String accessToken = getAccessToken(authCode);
System.out.println("Token Microsoft obtenu");
// Étape 2: Authentification Xbox Live
System.out.println("Étape 2: Authentification Xbox Live...");
Map<String, String> xboxData = authenticateXboxLive(accessToken);
String xblToken = xboxData.get("token");
String userHash = xboxData.get("userHash");
System.out.println("Token Xbox Live obtenu");
// Étape 3: Obtenir le token XSTS
System.out.println("Étape 3: Obtention du token XSTS...");
String xstsToken = getXSTSToken(xblToken);
System.out.println("Token XSTS obtenu");
// Étape 4: Authentification finale Minecraft
System.out.println("Étape 4: Authentification finale Minecraft...");
String minecraftToken = authenticateMinecraft(xstsToken, userHash);
System.out.println("Token Minecraft obtenu");
// Étape 5: Vérifier la propriété de Minecraft
System.out.println("Étape 5: Vérification de la propriété de Minecraft...");
boolean ownsMinecraft = checkMinecraftOwnership(minecraftToken);
if (!ownsMinecraft) {
throw new RuntimeException("Ce compte ne possède pas Minecraft Java Edition");
}
System.out.println("Propriété de Minecraft confirmée");
// Étape 6: Récupérer le profil utilisateur
System.out.println("Étape 6: Récupération du profil utilisateur...");
Account account = getMinecraftProfile(minecraftToken);
System.out.println("=== AUTHENTIFICATION RÉUSSIE ===");
System.out.println("Utilisateur: " + account.getUsername());
System.out.println("UUID: " + account.getUuid());
return account;
}
private String getAccessToken(String authCode) throws Exception {
System.out.println("POST " + MICROSOFT_TOKEN_URL);
String postData = "client_id=" + CLIENT_ID +
"&code=" + URLEncoder.encode(authCode, StandardCharsets.UTF_8) +
"&grant_type=authorization_code" +
"&redirect_uri=" + URLEncoder.encode(REDIRECT_URI, StandardCharsets.UTF_8) +
"&scope=" + URLEncoder.encode(SCOPE, StandardCharsets.UTF_8);
String response = makePostRequest(MICROSOFT_TOKEN_URL, postData, "application/x-www-form-urlencoded");
JsonObject jsonResponse = gson.fromJson(response, JsonObject.class);
if (jsonResponse.has("error")) {
String error = jsonResponse.get("error").getAsString();
String errorDesc = jsonResponse.has("error_description") ?
jsonResponse.get("error_description").getAsString() : "Erreur inconnue";
throw new RuntimeException("Erreur Microsoft OAuth2: " + error + " - " + errorDesc);
}
return jsonResponse.get("access_token").getAsString();
}
private Map<String, String> authenticateXboxLive(String accessToken) throws Exception {
System.out.println("POST " + XBOX_AUTH_URL);
JsonObject requestBody = new JsonObject();
JsonObject properties = new JsonObject();
properties.addProperty("AuthMethod", "RPS");
properties.addProperty("SiteName", "user.auth.xboxlive.com");
properties.addProperty("RpsTicket", "d=" + accessToken);
requestBody.add("Properties", properties);
requestBody.addProperty("RelyingParty", "http://auth.xboxlive.com");
requestBody.addProperty("TokenType", "JWT");
String response = makePostRequest(XBOX_AUTH_URL, requestBody.toString(), "application/json");
JsonObject jsonResponse = gson.fromJson(response, JsonObject.class);
String token = jsonResponse.get("Token").getAsString();
String userHash = jsonResponse.getAsJsonObject("DisplayClaims")
.getAsJsonArray("xui")
.get(0).getAsJsonObject()
.get("uhs").getAsString();
Map<String, String> result = new HashMap<>();
result.put("token", token);
result.put("userHash", userHash);
return result;
}
private String getXSTSToken(String xblToken) throws Exception {
System.out.println("POST " + XSTS_AUTH_URL);
JsonObject requestBody = new JsonObject();
JsonObject properties = new JsonObject();
properties.addProperty("SandboxId", "RETAIL");
properties.add("UserTokens", gson.toJsonTree(new String[]{xblToken}));
requestBody.add("Properties", properties);
requestBody.addProperty("RelyingParty", "rp://api.minecraftservices.com/");
requestBody.addProperty("TokenType", "JWT");
String response = makePostRequest(XSTS_AUTH_URL, requestBody.toString(), "application/json");
JsonObject jsonResponse = gson.fromJson(response, JsonObject.class);
if (jsonResponse.has("XErr")) {
long errorCode = jsonResponse.get("XErr").getAsLong();
String errorMessage = getXSTSErrorMessage(errorCode);
throw new RuntimeException("Erreur XSTS " + errorCode + ": " + errorMessage);
}
return jsonResponse.get("Token").getAsString();
}
private String authenticateMinecraft(String xstsToken, String userHash) throws Exception {
System.out.println("POST " + MINECRAFT_AUTH_URL);
JsonObject requestBody = new JsonObject();
requestBody.addProperty("identityToken", "XBL3.0 x=" + userHash + ";" + xstsToken);
String response = makePostRequest(MINECRAFT_AUTH_URL, requestBody.toString(), "application/json");
JsonObject jsonResponse = gson.fromJson(response, JsonObject.class);
if (!jsonResponse.has("access_token")) {
throw new RuntimeException("Token d'accès Minecraft non reçu");
}
return jsonResponse.get("access_token").getAsString();
}
private boolean checkMinecraftOwnership(String minecraftToken) throws Exception {
System.out.println("GET " + MINECRAFT_STORE_URL);
URL url = new URL(MINECRAFT_STORE_URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Authorization", "Bearer " + minecraftToken);
connection.setRequestProperty("Accept", "application/json");
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);
if (responseCode == 404) {
// Compte existe mais ne possède pas Minecraft
return false;
}
if (responseCode != 200) {
System.err.println("Erreur vérification propriété: " + responseCode);
// On continue quand même, certains comptes anciens peuvent avoir ce problème
return true;
}
String response = readResponse(connection.getInputStream());
JsonObject jsonResponse = gson.fromJson(response, JsonObject.class);
// Vérifier qu'il y a au moins un produit (Minecraft Java)
return jsonResponse.has("items") && jsonResponse.getAsJsonArray("items").size() > 0;
}
private Account getMinecraftProfile(String minecraftToken) throws Exception {
System.out.println("GET " + MINECRAFT_PROFILE_URL);
URL url = new URL(MINECRAFT_PROFILE_URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Authorization", "Bearer " + minecraftToken);
connection.setRequestProperty("Accept", "application/json");
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);
if (responseCode == 404) {
throw new RuntimeException("Aucun profil Minecraft trouvé. Assurez-vous d'avoir créé un nom d'utilisateur Minecraft.");
}
if (responseCode != 200) {
throw new RuntimeException("Erreur lors de la récupération du profil: HTTP " + responseCode);
}
String response = readResponse(connection.getInputStream());
JsonObject jsonResponse = gson.fromJson(response, JsonObject.class);
String username = jsonResponse.get("name").getAsString();
String uuid = jsonResponse.get("id").getAsString();
// Formatter l'UUID avec les tirets
String formattedUuid = formatUUID(uuid);
return new Account(username, minecraftToken, formattedUuid);
}
private String formatUUID(String uuid) {
if (uuid.length() == 32) {
return String.format("%s-%s-%s-%s-%s",
uuid.substring(0, 8),
uuid.substring(8, 12),
uuid.substring(12, 16),
uuid.substring(16, 20),
uuid.substring(20, 32)
);
}
return uuid;
}
private String makePostRequest(String urlString, String postData, String contentType) throws Exception {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", contentType);
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("User-Agent", "MinecraftLauncher/1.0");
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
byte[] input = postData.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);
if (responseCode >= 400) {
String errorResponse = readResponse(connection.getErrorStream());
System.err.println("Erreur HTTP " + responseCode + ": " + errorResponse);
throw new RuntimeException("Erreur HTTP " + responseCode + ": " + errorResponse);
}
return readResponse(connection.getInputStream());
}
private String readResponse(InputStream inputStream) throws IOException {
if (inputStream == null) return "";
StringBuilder response = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
}
return response.toString();
}
private String getXSTSErrorMessage(long errorCode) {
if (errorCode == 2148916238L) {
return "Compte enfant (moins de 18 ans). Doit être ajouté à une famille Xbox avec consentement parental.";
} else if (errorCode == 2148916233L) {
return "Pas de compte Xbox Live associé. Créez un compte Xbox Live depuis xbox.com.";
} else if (errorCode == 2148916235L) {
return "Xbox Live n'est pas disponible dans votre pays/région.";
} else if (errorCode == 2148916236L || errorCode == 2148916237L) {
return "Compte adulte mais consentement parental requis dans votre région.";
} else {
return "Erreur XSTS inconnue: " + errorCode + ". Contactez le support Microsoft.";
}
}
}

View File

@ -0,0 +1,500 @@
// src/com/minecraftlauncher/game/GameLauncher.java
package com.minecraftlauncher.game;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.minecraftlauncher.model.Account;
import java.io.*;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class GameLauncher {
private static final String MINECRAFT_DIR = System.getProperty("user.home") + "/.minecraft";
private static final String VERSIONS_URL = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
private Gson gson = new Gson();
// Variables pour la connexion serveur
private String serverAddress = null;
private String serverPort = "25565";
// Méthodes pour configurer le serveur
public void setServerInfo(String address, String port) {
this.serverAddress = address;
this.serverPort = port != null ? port : "25565";
}
public Map<String, List<String>> getAvailableVersionsByType() {
Map<String, List<String>> versionsByType = new HashMap<>();
versionsByType.put("release", new ArrayList<>());
versionsByType.put("snapshot", new ArrayList<>());
versionsByType.put("old_beta", new ArrayList<>());
versionsByType.put("old_alpha", new ArrayList<>());
try {
System.out.println("[VERSIONS] Recuperation de toutes les versions disponibles...");
String response = downloadString(VERSIONS_URL);
JsonObject manifest = gson.fromJson(response, JsonObject.class);
JsonArray versions = manifest.getAsJsonArray("versions");
for (JsonElement element : versions) {
JsonObject version = element.getAsJsonObject();
String type = version.get("type").getAsString();
String id = version.get("id").getAsString();
switch (type) {
case "release":
versionsByType.get("release").add(id);
break;
case "snapshot":
versionsByType.get("snapshot").add(id);
break;
case "old_beta":
versionsByType.get("old_beta").add(id);
break;
case "old_alpha":
versionsByType.get("old_alpha").add(id);
break;
}
}
System.out.println("Versions trouvées - Release: " + versionsByType.get("release").size() +
", Snapshots: " + versionsByType.get("snapshot").size() +
", Beta: " + versionsByType.get("old_beta").size() +
", Alpha: " + versionsByType.get("old_alpha").size());
return versionsByType;
} catch (Exception e) {
System.err.println("Erreur lors de la récupération des versions: " + e.getMessage());
versionsByType.get("release").addAll(Arrays.asList("1.21.6", "1.21.5", "1.21.4", "1.21.3", "1.21.1", "1.20.1"));
return versionsByType;
}
}
public List<String> getAvailableVersions() {
Map<String, List<String>> versionsByType = getAvailableVersionsByType();
List<String> allVersions = new ArrayList<>();
allVersions.addAll(versionsByType.get("release"));
allVersions.addAll(versionsByType.get("snapshot"));
allVersions.addAll(versionsByType.get("old_beta"));
allVersions.addAll(versionsByType.get("old_alpha"));
return allVersions;
}
public String getVersionStatus(String version) {
Path versionDir = Paths.get(MINECRAFT_DIR, "versions", version);
Path jsonFile = versionDir.resolve(version + ".json");
Path jarFile = versionDir.resolve(version + ".jar");
if (!Files.exists(jsonFile) || !Files.exists(jarFile)) {
return "Non téléchargée";
}
try {
long jarSize = Files.size(jarFile) / (1024 * 1024);
return String.format("Téléchargée (%d MB)", jarSize);
} catch (IOException e) {
return "Téléchargement incomplet";
}
}
public boolean downloadVersion(String version) {
try {
System.out.println("=== TÉLÉCHARGEMENT DE " + version + " ===");
// Créer les répertoires
Path versionDir = Paths.get(MINECRAFT_DIR, "versions", version);
Files.createDirectories(versionDir);
// Récupérer les métadonnées de la version
String manifestResponse = downloadString(VERSIONS_URL);
JsonObject manifest = gson.fromJson(manifestResponse, JsonObject.class);
JsonArray versions = manifest.getAsJsonArray("versions");
String versionUrl = null;
for (JsonElement element : versions) {
JsonObject v = element.getAsJsonObject();
if (version.equals(v.get("id").getAsString())) {
versionUrl = v.get("url").getAsString();
break;
}
}
if (versionUrl == null) {
throw new RuntimeException("Version " + version + " non trouvée");
}
System.out.println("Téléchargement des métadonnées de " + version + "...");
String versionMetadata = downloadString(versionUrl);
// Sauvegarder le JSON de version
Path jsonFile = versionDir.resolve(version + ".json");
Files.write(jsonFile, versionMetadata.getBytes());
System.out.println("Métadonnées sauvegardées: " + jsonFile);
JsonObject versionData = gson.fromJson(versionMetadata, JsonObject.class);
// Télécharger le JAR client
if (versionData.has("downloads")) {
JsonObject downloads = versionData.getAsJsonObject("downloads");
if (downloads.has("client")) {
JsonObject client = downloads.getAsJsonObject("client");
String jarUrl = client.get("url").getAsString();
long jarSize = client.get("size").getAsLong();
System.out.println("Téléchargement du JAR client (" + (jarSize / 1024 / 1024) + " MB)...");
Path jarFile = versionDir.resolve(version + ".jar");
downloadFile(jarUrl, jarFile.toFile());
System.out.println("JAR client téléchargé: " + jarFile);
}
}
// Télécharger les librairies
downloadLibraries(versionData);
System.out.println("=== TÉLÉCHARGEMENT TERMINÉ ===");
return true;
} catch (Exception e) {
System.err.println("Erreur lors du téléchargement: " + e.getMessage());
e.printStackTrace();
return false;
}
}
private void downloadLibraries(JsonObject versionData) throws IOException {
if (!versionData.has("libraries")) {
return;
}
JsonArray libraries = versionData.getAsJsonArray("libraries");
System.out.println("Téléchargement de " + libraries.size() + " librairies...");
Path librariesDir = Paths.get(MINECRAFT_DIR, "libraries");
Files.createDirectories(librariesDir);
int downloaded = 0;
for (JsonElement element : libraries) {
JsonObject library = element.getAsJsonObject();
// Vérifier les règles (OS, etc.)
if (!shouldIncludeLibrary(library)) {
continue;
}
if (library.has("downloads") && library.getAsJsonObject("downloads").has("artifact")) {
JsonObject artifact = library.getAsJsonObject("downloads").getAsJsonObject("artifact");
String url = artifact.get("url").getAsString();
String path = artifact.get("path").getAsString();
Path libFile = librariesDir.resolve(path);
Files.createDirectories(libFile.getParent());
if (!Files.exists(libFile)) {
downloadFile(url, libFile.toFile());
downloaded++;
}
}
}
System.out.println(downloaded + " nouvelles librairies téléchargées");
}
private boolean shouldIncludeLibrary(JsonObject library) {
if (!library.has("rules")) {
return true;
}
JsonArray rules = library.getAsJsonArray("rules");
boolean allowed = false;
for (JsonElement element : rules) {
JsonObject rule = element.getAsJsonObject();
String action = rule.get("action").getAsString();
if (rule.has("os")) {
JsonObject os = rule.getAsJsonObject("os");
if (os.has("name")) {
String osName = os.get("name").getAsString();
String currentOS = System.getProperty("os.name").toLowerCase();
boolean matches = false;
switch (osName) {
case "windows":
matches = currentOS.contains("win");
break;
case "linux":
matches = currentOS.contains("linux");
break;
case "osx":
matches = currentOS.contains("mac");
break;
}
if (matches) {
allowed = "allow".equals(action);
}
}
} else {
allowed = "allow".equals(action);
}
}
return allowed;
}
public boolean launchGame(String version, Account account) {
try {
System.out.println("=== DÉBUT DU LANCEMENT DE MINECRAFT ===");
System.out.println("Version: " + version);
System.out.println("Utilisateur: " + account.getUsername());
Path versionDir = Paths.get(MINECRAFT_DIR, "versions", version);
Path jsonFile = versionDir.resolve(version + ".json");
Path jarFile = versionDir.resolve(version + ".jar");
// Vérifications
System.out.println("Vérification des fichiers...");
if (!Files.exists(versionDir)) {
throw new RuntimeException("Répertoire version manquant: " + versionDir);
}
if (!Files.exists(jsonFile)) {
throw new RuntimeException("Fichier JSON manquant: " + jsonFile);
}
if (!Files.exists(jarFile)) {
throw new RuntimeException("JAR client manquant: " + jarFile);
}
System.out.println("Répertoire version: " + versionDir + " (existe: " + Files.exists(versionDir) + ")");
System.out.println("Fichier JSON: " + jsonFile + " (existe: " + Files.exists(jsonFile) + ")");
System.out.println("JAR client: " + jarFile + " (existe: " + Files.exists(jarFile) + ")");
// Lire les métadonnées
String jsonContent = new String(Files.readAllBytes(jsonFile));
JsonObject versionData = gson.fromJson(jsonContent, JsonObject.class);
// Construire le classpath
String classpath = buildClasspath(versionData, jarFile);
System.out.println("Classpath construit avec " + classpath.split(File.pathSeparator).length + " éléments");
// Obtenir la classe principale
String mainClass = versionData.get("mainClass").getAsString();
System.out.println("Classe principale: " + mainClass);
// Construire les arguments
List<String> command = new ArrayList<>();
command.add("java");
command.add("-Xmx2G");
command.add("-Xms1G");
command.add("-Djava.library.path=" + Paths.get(MINECRAFT_DIR, "versions", version, "natives"));
command.add("-cp");
command.add(classpath);
command.add(mainClass);
// Arguments de jeu
Map<String, String> variables = createVariableMap(version, account);
List<String> gameArgs = getGameArguments(versionData, variables);
command.addAll(gameArgs);
// Arguments de connexion serveur - OBLIGATOIRE pour PEDALO
if (serverAddress != null && !serverAddress.isEmpty()) {
System.out.println("🌐 [SERVER] Connexion automatique au serveur PEDALO: " + serverAddress + ":" + serverPort);
command.add("--server");
command.add(serverAddress);
command.add("--port");
command.add(serverPort);
} else {
System.out.println("⚠️ [WARNING] Pas de serveur configuré - Utilisation du serveur par défaut");
// Configuration par défaut pour PEDALO
command.add("--server");
command.add("pedalo.vidoks.fr");
command.add("--port");
command.add("25565");
}
// Afficher la commande complète
System.out.println("=== COMMANDE DE LANCEMENT ===");
for (int i = 0; i < command.size(); i++) {
System.out.println(i + ": " + command.get(i));
}
// Créer le répertoire natives si nécessaire
Path nativesDir = Paths.get(MINECRAFT_DIR, "versions", version, "natives");
Files.createDirectories(nativesDir);
// Lancer le processus
System.out.println("Lancement du processus...");
ProcessBuilder pb = new ProcessBuilder(command);
pb.directory(new File(MINECRAFT_DIR));
Process process = pb.start();
System.out.println("Minecraft lancé avec succès! PID: " + process.pid());
// Capturer les logs (optionnel)
captureProcessOutput(process);
return true;
} catch (Exception e) {
System.err.println("Erreur lors du lancement: " + e.getMessage());
e.printStackTrace();
return false;
}
}
private String buildClasspath(JsonObject versionData, Path jarFile) {
List<String> classpathElements = new ArrayList<>();
// Ajouter les librairies
if (versionData.has("libraries")) {
JsonArray libraries = versionData.getAsJsonArray("libraries");
Path librariesDir = Paths.get(MINECRAFT_DIR, "libraries");
for (JsonElement element : libraries) {
JsonObject library = element.getAsJsonObject();
if (!shouldIncludeLibrary(library)) {
continue;
}
if (library.has("downloads") && library.getAsJsonObject("downloads").has("artifact")) {
JsonObject artifact = library.getAsJsonObject("downloads").getAsJsonObject("artifact");
String path = artifact.get("path").getAsString();
Path libFile = librariesDir.resolve(path);
if (Files.exists(libFile)) {
classpathElements.add(libFile.toString());
}
}
}
}
// Ajouter le JAR principal
classpathElements.add(jarFile.toString());
System.out.println("Librairies ajoutées au classpath: " + (classpathElements.size() - 1));
return String.join(File.pathSeparator, classpathElements);
}
private Map<String, String> createVariableMap(String version, Account account) {
Map<String, String> variables = new HashMap<>();
variables.put("auth_player_name", account.getUsername());
variables.put("version_name", version);
variables.put("game_directory", MINECRAFT_DIR);
variables.put("assets_root", Paths.get(MINECRAFT_DIR, "assets").toString());
variables.put("assets_index_name", version);
variables.put("auth_uuid", account.getUuid());
variables.put("auth_access_token", account.getAccessToken());
variables.put("user_type", "msa");
variables.put("version_type", "release");
variables.put("resolution_width", "1920");
variables.put("resolution_height", "1080");
variables.put("natives_directory", Paths.get(MINECRAFT_DIR, "versions", version, "natives").toString());
variables.put("launcher_name", "MinecraftLauncher");
variables.put("launcher_version", "1.0");
return variables;
}
private List<String> getGameArguments(JsonObject versionData, Map<String, String> variables) {
List<String> args = new ArrayList<>();
if (versionData.has("arguments")) {
// Arguments modernes (1.13+)
JsonObject arguments = versionData.getAsJsonObject("arguments");
if (arguments.has("game")) {
JsonArray gameArgs = arguments.getAsJsonArray("game");
for (JsonElement element : gameArgs) {
if (element.isJsonPrimitive()) {
args.add(replaceVariables(element.getAsString(), variables));
}
}
}
} else if (versionData.has("minecraftArguments")) {
// Arguments legacy (< 1.13)
String legacyArgs = versionData.get("minecraftArguments").getAsString();
String[] parts = legacyArgs.split(" ");
for (String part : parts) {
args.add(replaceVariables(part, variables));
}
} else {
// Arguments par défaut si aucun n'est spécifié
args.addAll(Arrays.asList(
"--username", variables.get("auth_player_name"),
"--version", variables.get("version_name"),
"--gameDir", variables.get("game_directory"),
"--assetsDir", variables.get("assets_root"),
"--assetIndex", variables.get("assets_index_name"),
"--uuid", variables.get("auth_uuid"),
"--accessToken", variables.get("auth_access_token"),
"--userType", variables.get("user_type"),
"--versionType", variables.get("version_type")
));
}
return args;
}
private String replaceVariables(String input, Map<String, String> variables) {
String result = input;
for (Map.Entry<String, String> entry : variables.entrySet()) {
result = result.replace("${" + entry.getKey() + "}", entry.getValue());
}
return result;
}
private void captureProcessOutput(Process process) {
// Capturer stdout de Minecraft
new Thread(() -> {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("[MINECRAFT] " + line);
}
} catch (IOException e) {
System.err.println("Erreur lecture stdout: " + e.getMessage());
}
}).start();
// Capturer stderr de Minecraft
new Thread(() -> {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.err.println("[MINECRAFT ERROR] " + line);
}
} catch (IOException e) {
System.err.println("Erreur lecture stderr: " + e.getMessage());
}
}).start();
}
private String downloadString(String urlString) throws IOException {
URL url = new URL(urlString);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
StringBuilder content = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
content.append(line);
}
return content.toString();
}
}
private void downloadFile(String urlString, File destination) throws IOException {
URL url = new URL(urlString);
try (InputStream in = url.openStream();
FileOutputStream out = new FileOutputStream(destination)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
}
}

View File

@ -0,0 +1,28 @@
// src/com/minecraftlauncher/model/Account.java
package com.minecraftlauncher.model;
public class Account {
private String username;
private String accessToken;
private String uuid;
public Account(String username, String accessToken) {
this.username = username;
this.accessToken = accessToken;
this.uuid = "00000000-0000-0000-0000-000000000000"; // UUID par défaut
}
public Account(String username, String accessToken, String uuid) {
this.username = username;
this.accessToken = accessToken;
this.uuid = uuid != null ? uuid : "00000000-0000-0000-0000-000000000000";
}
public String getUsername() { return username; }
public String getAccessToken() { return accessToken; }
public String getUuid() { return uuid; }
public void setUsername(String username) { this.username = username; }
public void setAccessToken(String accessToken) { this.accessToken = accessToken; }
public void setUuid(String uuid) { this.uuid = uuid; }
}

View File

@ -0,0 +1,15 @@
// src/com/minecraftlauncher/model/Profile.java
package com.minecraftlauncher.model;
public class Profile {
private String id;
private String name;
public Profile(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() { return id; }
public String getName() { return name; }
}

View File

@ -0,0 +1,108 @@
// src/com/minecraftlauncher/ui/DarkThemeUtils.java
package com.minecraftlauncher.ui;
import javax.swing.*;
import java.awt.*;
public class DarkThemeUtils {
// Couleurs du thème sombre
public static final Color BACKGROUND_PRIMARY = new Color(17, 24, 39);
public static final Color BACKGROUND_SECONDARY = new Color(31, 41, 55);
public static final Color BACKGROUND_TERTIARY = new Color(55, 65, 81);
public static final Color TEXT_PRIMARY = new Color(243, 244, 246);
public static final Color TEXT_SECONDARY = new Color(209, 213, 219);
public static final Color BORDER_COLOR = new Color(75, 85, 99);
public static void applyDarkThemeToOptionPane() {
// Configuration pour les JOptionPane
UIManager.put("OptionPane.background", BACKGROUND_PRIMARY);
UIManager.put("OptionPane.messageForeground", TEXT_PRIMARY);
UIManager.put("OptionPane.messageBackground", BACKGROUND_PRIMARY);
// Panneaux dans les dialog
UIManager.put("Panel.background", BACKGROUND_PRIMARY);
UIManager.put("Panel.foreground", TEXT_PRIMARY);
// Boutons dans les dialogs
UIManager.put("Button.background", BACKGROUND_TERTIARY);
UIManager.put("Button.foreground", TEXT_PRIMARY);
UIManager.put("Button.select", BACKGROUND_SECONDARY);
UIManager.put("Button.focus", BORDER_COLOR);
}
public static JDialog createDarkDialog(Frame parent, String title, boolean modal) {
JDialog dialog = new JDialog(parent, title, modal);
dialog.getContentPane().setBackground(BACKGROUND_PRIMARY);
return dialog;
}
public static void showDarkOptionPane(Component parent, String message, String title, int messageType) {
// Sauvegarder les anciennes valeurs
Color oldBg = UIManager.getColor("OptionPane.background");
Color oldFg = UIManager.getColor("OptionPane.messageForeground");
Color oldPanelBg = UIManager.getColor("Panel.background");
Color oldButtonBg = UIManager.getColor("Button.background");
Color oldButtonFg = UIManager.getColor("Button.foreground");
try {
// Appliquer le thème sombre temporairement
applyDarkThemeToOptionPane();
// Créer un panel personnalisé avec le bon style
JPanel panel = new JPanel(new BorderLayout(10, 10));
panel.setBackground(BACKGROUND_PRIMARY);
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
JLabel messageLabel = new JLabel("<html><div style='text-align: center; color: #F3F4F6;'>" +
message.replace("\n", "<br>") + "</div></html>");
messageLabel.setFont(new Font("SansSerif", Font.PLAIN, 13));
messageLabel.setForeground(TEXT_PRIMARY);
panel.add(messageLabel, BorderLayout.CENTER);
JOptionPane.showMessageDialog(parent, panel, title, messageType);
} finally {
// Restaurer les anciennes valeurs
UIManager.put("OptionPane.background", oldBg);
UIManager.put("OptionPane.messageForeground", oldFg);
UIManager.put("Panel.background", oldPanelBg);
UIManager.put("Button.background", oldButtonBg);
UIManager.put("Button.foreground", oldButtonFg);
}
}
public static int showDarkConfirmDialog(Component parent, String message, String title, int optionType) {
// Sauvegarder les anciennes valeurs
Color oldBg = UIManager.getColor("OptionPane.background");
Color oldFg = UIManager.getColor("OptionPane.messageForeground");
Color oldPanelBg = UIManager.getColor("Panel.background");
Color oldButtonBg = UIManager.getColor("Button.background");
Color oldButtonFg = UIManager.getColor("Button.foreground");
try {
// Appliquer le thème sombre temporairement
applyDarkThemeToOptionPane();
// Créer un panel personnalisé avec le bon style
JPanel panel = new JPanel(new BorderLayout(10, 10));
panel.setBackground(BACKGROUND_PRIMARY);
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
JLabel messageLabel = new JLabel("<html><div style='text-align: center; color: #F3F4F6;'>" +
message.replace("\n", "<br>") + "</div></html>");
messageLabel.setFont(new Font("SansSerif", Font.PLAIN, 13));
messageLabel.setForeground(TEXT_PRIMARY);
panel.add(messageLabel, BorderLayout.CENTER);
return JOptionPane.showConfirmDialog(parent, panel, title, optionType, JOptionPane.QUESTION_MESSAGE);
} finally {
// Restaurer les anciennes valeurs
UIManager.put("OptionPane.background", oldBg);
UIManager.put("OptionPane.messageForeground", oldFg);
UIManager.put("Panel.background", oldPanelBg);
UIManager.put("Button.background", oldButtonBg);
UIManager.put("Button.foreground", oldButtonFg);
}
}
}

View File

@ -0,0 +1,277 @@
// src/com/minecraftlauncher/ui/InstalledVersionsDialog.java
package com.minecraftlauncher.ui;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class InstalledVersionsDialog extends JDialog {
private static final String MINECRAFT_DIR = System.getProperty("user.home") + "/.minecraft";
private JList<String> versionsList;
private DefaultListModel<String> listModel;
private JButton deleteButton;
private JButton refreshButton;
private JLabel sizeLabel;
public InstalledVersionsDialog(JFrame parent) {
super(parent, "Versions Installées", true);
initializeComponents();
setupLayout();
setupEventListeners();
loadInstalledVersions();
setSize(500, 400);
setLocationRelativeTo(parent);
setResizable(true);
}
private void initializeComponents() {
// Liste des versions
listModel = new DefaultListModel<>();
versionsList = new JList<>(listModel);
versionsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
versionsList.setFont(new Font("SansSerif", Font.PLAIN, 12));
versionsList.setBackground(Color.WHITE);
versionsList.setForeground(Color.BLACK);
versionsList.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
// Boutons
deleteButton = new JButton("Supprimer");
deleteButton.setEnabled(false);
styleButton(deleteButton, new Color(220, 38, 38), new Color(185, 28, 28)); // Rouge
refreshButton = new JButton("Actualiser");
styleButton(refreshButton, new Color(37, 99, 235), new Color(29, 78, 216)); // Bleu
JButton closeButton = new JButton("Fermer");
styleButton(closeButton, new Color(107, 114, 128), new Color(75, 85, 99)); // Gris
// Label d'information
sizeLabel = new JLabel("Aucune version installée");
sizeLabel.setFont(new Font("SansSerif", Font.PLAIN, 11));
sizeLabel.setForeground(new Color(107, 114, 128));
// Événements des boutons
closeButton.addActionListener(e -> dispose());
}
private void styleButton(JButton button, Color bgColor, Color hoverColor) {
button.setBackground(bgColor);
button.setForeground(Color.WHITE);
button.setFocusPainted(false);
button.setBorderPainted(false);
button.setFont(new Font("SansSerif", Font.BOLD, 11));
button.setCursor(new Cursor(Cursor.HAND_CURSOR));
button.setBorder(BorderFactory.createEmptyBorder(8, 16, 8, 16));
// Effet hover
button.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseEntered(java.awt.event.MouseEvent evt) {
button.setBackground(hoverColor);
}
public void mouseExited(java.awt.event.MouseEvent evt) {
button.setBackground(bgColor);
}
});
}
private void setupLayout() {
setLayout(new BorderLayout(10, 10));
// Panel principal
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Titre
JLabel titleLabel = new JLabel("Versions de Minecraft Installées");
titleLabel.setFont(new Font("SansSerif", Font.BOLD, 16));
titleLabel.setHorizontalAlignment(SwingConstants.CENTER);
titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0));
// Liste avec scroll
JScrollPane scrollPane = new JScrollPane(versionsList);
scrollPane.setBorder(BorderFactory.createLineBorder(new Color(75, 85, 99), 1));
scrollPane.setPreferredSize(new Dimension(400, 250));
// Panel des boutons
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
buttonPanel.add(deleteButton);
buttonPanel.add(refreshButton);
buttonPanel.add(new JButton("Fermer") {{
styleButton(this, new Color(107, 114, 128), new Color(75, 85, 99));
addActionListener(e -> dispose());
}});
// Panel du bas avec info et boutons
JPanel bottomPanel = new JPanel(new BorderLayout());
bottomPanel.add(sizeLabel, BorderLayout.WEST);
bottomPanel.add(buttonPanel, BorderLayout.EAST);
mainPanel.add(titleLabel, BorderLayout.NORTH);
mainPanel.add(scrollPane, BorderLayout.CENTER);
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
add(mainPanel);
}
private void setupEventListeners() {
// Sélection dans la liste
versionsList.addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
deleteButton.setEnabled(versionsList.getSelectedValue() != null);
}
});
// Bouton supprimer
deleteButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
deleteSelectedVersion();
}
});
// Bouton actualiser
refreshButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
loadInstalledVersions();
}
});
}
private void loadInstalledVersions() {
listModel.clear();
List<String> installedVersions = getInstalledVersions();
if (installedVersions.isEmpty()) {
sizeLabel.setText("Aucune version installée");
} else {
for (String version : installedVersions) {
listModel.addElement(version);
}
// Calculer la taille totale
long totalSize = calculateTotalSize(installedVersions);
String sizeText = formatSize(totalSize);
sizeLabel.setText(installedVersions.size() + " version(s) - " + sizeText);
}
}
private List<String> getInstalledVersions() {
List<String> versions = new ArrayList<>();
Path versionsDir = Paths.get(MINECRAFT_DIR, "versions");
try {
if (Files.exists(versionsDir) && Files.isDirectory(versionsDir)) {
Files.list(versionsDir)
.filter(Files::isDirectory)
.forEach(versionDir -> {
String versionName = versionDir.getFileName().toString();
Path jarFile = versionDir.resolve(versionName + ".jar");
Path jsonFile = versionDir.resolve(versionName + ".json");
// Vérifier que les fichiers essentiels existent
if (Files.exists(jarFile) && Files.exists(jsonFile)) {
versions.add(versionName);
}
});
}
} catch (Exception e) {
System.err.println("Erreur lors de la lecture des versions: " + e.getMessage());
}
versions.sort(String::compareTo);
return versions;
}
private long calculateTotalSize(List<String> versions) {
long totalSize = 0;
for (String version : versions) {
Path versionDir = Paths.get(MINECRAFT_DIR, "versions", version);
try {
totalSize += Files.walk(versionDir)
.filter(Files::isRegularFile)
.mapToLong(path -> {
try {
return Files.size(path);
} catch (Exception e) {
return 0;
}
})
.sum();
} catch (Exception e) {
// Ignorer les erreurs pour cette version
}
}
return totalSize;
}
private String formatSize(long bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
if (bytes < 1024 * 1024 * 1024) return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
return String.format("%.1f GB", bytes / (1024.0 * 1024.0 * 1024.0));
}
private void deleteSelectedVersion() {
String selectedVersion = versionsList.getSelectedValue();
if (selectedVersion == null) return;
int result = JOptionPane.showConfirmDialog(
this,
"Voulez-vous vraiment supprimer la version " + selectedVersion + " ?\n\n" +
"Cette action est irréversible.",
"Confirmer la suppression",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE
);
if (result == JOptionPane.YES_OPTION) {
if (deleteVersion(selectedVersion)) {
JOptionPane.showMessageDialog(
this,
"Version " + selectedVersion + " Supprimée avec succès.",
"Suppression réussie",
JOptionPane.INFORMATION_MESSAGE
);
loadInstalledVersions(); // Actualiser la liste
} else {
JOptionPane.showMessageDialog(
this,
"Erreur lors de la suppression de la version " + selectedVersion + ".\n" +
"Vérifiez que les fichiers ne sont pas en cours d'utilisation.",
"Erreur de suppression",
JOptionPane.ERROR_MESSAGE
);
}
}
}
private boolean deleteVersion(String version) {
Path versionDir = Paths.get(MINECRAFT_DIR, "versions", version);
try {
// Supprimer récursivement le dossier de la version
Files.walk(versionDir)
.sorted((a, b) -> b.compareTo(a)) // Ordre inverse pour supprimer les fichiers avant les dossiers
.forEach(path -> {
try {
Files.delete(path);
} catch (Exception e) {
System.err.println("Impossible de supprimer: " + path);
}
});
return !Files.exists(versionDir);
} catch (Exception e) {
System.err.println("[ERROR] Erreur lors de la suppression: " + e.getMessage());
return false;
}
}
}

View File

@ -0,0 +1,704 @@
// src/com/minecraftlauncher/ui/LauncherFrame.java
package com.minecraftlauncher.ui;
import com.minecraftlauncher.auth.ModernAuthManager;
import com.minecraftlauncher.game.GameLauncher;
import com.minecraftlauncher.model.Account;
import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class LauncherFrame extends JFrame {
// Gestionnaires réels
private ModernAuthManager authManager;
private GameLauncher gameLauncher;
private Account currentAccount;
// Composants UI
private JLabel userLabel;
private JButton loginButton;
private JComboBox<String> versionComboBox;
private JButton downloadButton;
private JButton launchButton;
private JButton versionsButton;
private JLabel statusLabel;
private JLabel serverInfoLabel;
public LauncherFrame() {
initializeComponents();
setupLayout();
setupEventListeners();
// Initialisation des gestionnaires réels
authManager = new ModernAuthManager();
gameLauncher = new GameLauncher();
// Charger les versions disponibles
loadAvailableVersions();
}
private void initializeComponents() {
// Configuration de base
setTitle("Le Launcher du PEDALO");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(700, 650);
setLocationRelativeTo(null);
setResizable(true);
setMinimumSize(new Dimension(650, 600));
// Thème sombre moderne
setupModernTheme();
// Composants d'authentification avec style moderne
userLabel = new JLabel("Non connecte");
userLabel.setFont(new Font("SansSerif", Font.BOLD, 15));
userLabel.setForeground(new Color(239, 68, 68)); // Rouge moderne
userLabel.setBorder(BorderFactory.createEmptyBorder(8, 12, 8, 12));
userLabel.setOpaque(true);
userLabel.setBackground(new Color(55, 65, 81));
userLabel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(75, 85, 99), 1),
BorderFactory.createEmptyBorder(8, 12, 8, 12)));
loginButton = new JButton("Se connecter à Microsoft");
styleModernButton(loginButton, new Color(37, 99, 235), new Color(29, 78, 216)); // Bleu moderne
loginButton.setFont(new Font("SansSerif", Font.BOLD, 13));
loginButton.setPreferredSize(new Dimension(250, 40));
// Composants de version avec style moderne
versionComboBox = new JComboBox<>();
versionComboBox.setEnabled(false);
styleModernComboBox(versionComboBox);
versionComboBox.setPreferredSize(new Dimension(200, 35));
downloadButton = new JButton("Chargement...");
downloadButton.setEnabled(false);
styleModernButton(downloadButton, new Color(249, 115, 22), new Color(234, 88, 12)); // Orange moderne
downloadButton.setPreferredSize(new Dimension(150, 35));
launchButton = new JButton("🚀 REJOINDRE LE SERVEUR PEDALO");
launchButton.setEnabled(false);
styleModernButton(launchButton, new Color(34, 197, 94), new Color(22, 163, 74)); // Vert moderne
launchButton.setFont(new Font("SansSerif", Font.BOLD, 16));
launchButton.setPreferredSize(new Dimension(300, 50));
// Effet de brillance
launchButton.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(74, 222, 128), 2),
BorderFactory.createEmptyBorder(5, 20, 5, 20)));
versionsButton = new JButton("📦 Toutes les Versions");
styleModernButton(versionsButton, new Color(99, 102, 241), new Color(79, 70, 229)); // Indigo moderne
versionsButton.setPreferredSize(new Dimension(150, 35));
// Label d'information serveur fixe
serverInfoLabel = new JLabel("🌐 Connexion automatique au serveur : pedalo.vidoks.fr:25565");
serverInfoLabel.setFont(new Font("SansSerif", Font.BOLD, 14));
serverInfoLabel.setForeground(new Color(34, 197, 94)); // Vert
serverInfoLabel.setHorizontalAlignment(SwingConstants.CENTER);
serverInfoLabel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(34, 197, 94), 2),
BorderFactory.createEmptyBorder(10, 15, 10, 15)));
serverInfoLabel.setOpaque(true);
serverInfoLabel.setBackground(new Color(21, 128, 61, 20));
statusLabel = new JLabel("🚀 Prêt - Cliquez sur 'Se connecter' pour rejoindre le serveur PEDALO");
statusLabel.setBorder(BorderFactory.createEmptyBorder(10, 15, 10, 15));
statusLabel.setFont(new Font("SansSerif", Font.PLAIN, 12));
statusLabel.setForeground(new Color(156, 163, 175));
statusLabel.setOpaque(true);
statusLabel.setBackground(new Color(17, 24, 39));
statusLabel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(1, 0, 0, 0, new Color(75, 85, 99)),
BorderFactory.createEmptyBorder(10, 15, 10, 15)));
}
// Méthodes de stylisation moderne
private void setupModernTheme() {
// Couleur de fond principale - thème sombre moderne
getContentPane().setBackground(new Color(17, 24, 39)); // Gray-900
// Configuration globale des composants pour thème sombre uniforme
UIManager.put("Panel.background", new Color(17, 24, 39));
UIManager.put("Label.foreground", new Color(243, 244, 246));
// Boîtes de dialogue et menus déroulants
UIManager.put("OptionPane.background", new Color(17, 24, 39));
UIManager.put("OptionPane.messageForeground", new Color(243, 244, 246));
UIManager.put("OptionPane.messageBackground", new Color(17, 24, 39));
UIManager.put("Button.background", new Color(55, 65, 81));
UIManager.put("Button.foreground", new Color(243, 244, 246));
// ComboBox thème sombre - Fix pour éviter blanc sur blanc
UIManager.put("ComboBox.background", new Color(31, 41, 55));
UIManager.put("ComboBox.foreground", new Color(243, 244, 246));
UIManager.put("ComboBox.selectionBackground", new Color(37, 99, 235));
UIManager.put("ComboBox.selectionForeground", Color.WHITE);
UIManager.put("ComboBox.buttonBackground", new Color(55, 65, 81));
UIManager.put("ComboBox.disabledBackground", new Color(55, 65, 81));
UIManager.put("ComboBox.disabledForeground", new Color(156, 163, 175));
// Menu popup pour ComboBox
UIManager.put("PopupMenu.background", new Color(31, 41, 55));
UIManager.put("MenuItem.background", new Color(31, 41, 55));
UIManager.put("MenuItem.foreground", new Color(243, 244, 246));
UIManager.put("MenuItem.selectionBackground", new Color(55, 65, 81));
UIManager.put("MenuItem.selectionForeground", Color.WHITE);
// Scrollbars
UIManager.put("ScrollBar.background", new Color(31, 41, 55));
UIManager.put("ScrollBar.thumb", new Color(75, 85, 99));
UIManager.put("ScrollBar.thumbShadow", new Color(55, 65, 81));
UIManager.put("ScrollBar.thumbHighlight", new Color(107, 114, 128));
// Curseur personnalisé
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
// AJOUT : Fix supplémentaire pour ComboBox
fixComboBoxUIManager();
}
// NOUVELLE METHODE : Fix pour ComboBox
private void fixComboBoxUIManager() {
// Désactiver les couleurs par défaut qui peuvent interférer
UIManager.put("ComboBox.border", BorderFactory.createEmptyBorder());
UIManager.put("ComboBox.borderSelectionColor", null);
UIManager.put("ComboBox.buttonDarkShadow", new Color(75, 85, 99));
UIManager.put("ComboBox.buttonHighlight", new Color(107, 114, 128));
UIManager.put("ComboBox.buttonShadow", new Color(55, 65, 81));
UIManager.put("ComboBox.focus", new Color(37, 99, 235));
// Force les couleurs pour la liste popup
UIManager.put("List.background", new Color(31, 41, 55));
UIManager.put("List.foreground", new Color(243, 244, 246));
UIManager.put("List.selectionBackground", new Color(37, 99, 235));
UIManager.put("List.selectionForeground", Color.WHITE);
UIManager.put("List.focusCellHighlightBorder", BorderFactory.createEmptyBorder());
}
private void styleModernButton(JButton button, Color bgColor, Color hoverColor) {
button.setBackground(bgColor);
button.setForeground(Color.WHITE);
button.setFocusPainted(false);
button.setBorderPainted(false);
button.setFont(new Font("SansSerif", Font.BOLD, 12));
button.setCursor(new Cursor(Cursor.HAND_CURSOR));
// Bordures arrondies avec padding
button.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(bgColor.brighter(), 1),
BorderFactory.createEmptyBorder(8, 16, 8, 16)));
// Effet hover
button.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseEntered(java.awt.event.MouseEvent evt) {
button.setBackground(hoverColor);
}
public void mouseExited(java.awt.event.MouseEvent evt) {
button.setBackground(bgColor);
}
});
}
// METHODE MODIFIEE : Fix pour ComboBox
private void styleModernComboBox(JComboBox<?> comboBox) {
// Configuration de base
comboBox.setBackground(new Color(31, 41, 55));
comboBox.setForeground(new Color(243, 244, 246));
comboBox.setFont(new Font("SansSerif", Font.PLAIN, 12));
comboBox.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(75, 85, 99), 1),
BorderFactory.createEmptyBorder(5, 10, 5, 10)));
// SOLUTION : Renderer personnalisé avec couleurs forcées
comboBox.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
// CORRECTION : Force les couleurs explicitement
if (isSelected) {
setBackground(new Color(37, 99, 235)); // Bleu pour sélection
setForeground(Color.WHITE);
} else {
setBackground(new Color(31, 41, 55)); // Fond sombre
setForeground(new Color(243, 244, 246)); // Texte clair
}
// IMPORTANT : Force l'opacité pour que les couleurs s'appliquent
setOpaque(true);
setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
return this;
}
});
// AJOUT : Configuration supplémentaire pour éviter les conflits UIManager
comboBox.putClientProperty("JComboBox.isTableCellEditor", Boolean.FALSE);
// AJOUT : Listener pour forcer le style au focus
comboBox.addFocusListener(new java.awt.event.FocusAdapter() {
@Override
public void focusGained(java.awt.event.FocusEvent evt) {
comboBox.setBackground(new Color(31, 41, 55));
comboBox.setForeground(new Color(243, 244, 246));
}
});
}
private JLabel createSectionTitle(String title, String icon) {
JLabel label = new JLabel(icon + " " + title);
label.setFont(new Font("SansSerif", Font.BOLD, 14));
label.setForeground(new Color(147, 197, 253)); // Blue-300
label.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
return label;
}
private void setupLayout() {
setLayout(new BorderLayout());
// Panel principal avec thème sombre
JPanel mainPanel = new JPanel(new GridBagLayout());
mainPanel.setBackground(new Color(17, 24, 39));
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 25, 20, 25));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(8, 8, 8, 8);
// Section authentification avec design moderne
gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2;
JLabel authTitle = createSectionTitle("Authentification", "🔐");
mainPanel.add(authTitle, gbc);
gbc.gridy = 1; gbc.gridwidth = 1;
mainPanel.add(userLabel, gbc);
gbc.gridx = 1;
mainPanel.add(loginButton, gbc);
// Séparateur moderne
gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 2; gbc.fill = GridBagConstraints.HORIZONTAL;
JSeparator sep1 = new JSeparator();
sep1.setForeground(new Color(75, 85, 99));
sep1.setBackground(new Color(75, 85, 99));
gbc.insets = new Insets(15, 0, 15, 0);
mainPanel.add(sep1, gbc);
gbc.insets = new Insets(8, 8, 8, 8);
// Section versions avec design moderne
gbc.gridy = 3; gbc.gridwidth = 2; gbc.fill = GridBagConstraints.NONE;
JLabel versionTitle = createSectionTitle("Gestion des Versions Minecraft", "📦");
mainPanel.add(versionTitle, gbc);
gbc.gridy = 4; gbc.gridwidth = 1;
JLabel versionLabel = new JLabel("Version:");
versionLabel.setForeground(new Color(209, 213, 219));
versionLabel.setFont(new Font("SansSerif", Font.PLAIN, 12));
mainPanel.add(versionLabel, gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL;
mainPanel.add(versionComboBox, gbc);
gbc.gridx = 0; gbc.gridy = 5; gbc.fill = GridBagConstraints.NONE;
mainPanel.add(downloadButton, gbc);
gbc.gridx = 1;
mainPanel.add(versionsButton, gbc);
// Séparateur moderne
gbc.gridx = 0; gbc.gridy = 6; gbc.gridwidth = 2; gbc.fill = GridBagConstraints.HORIZONTAL;
JSeparator sep2 = new JSeparator();
sep2.setForeground(new Color(75, 85, 99));
sep2.setBackground(new Color(75, 85, 99));
gbc.insets = new Insets(15, 0, 15, 0);
mainPanel.add(sep2, gbc);
gbc.insets = new Insets(8, 8, 8, 8);
// Information serveur fixe
gbc.gridy = 7; gbc.gridwidth = 2; gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(15, 0, 15, 0);
mainPanel.add(serverInfoLabel, gbc);
gbc.insets = new Insets(8, 8, 8, 8);
// Bouton de lancement avec style spécial
gbc.gridy = 8; gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(20, 0, 10, 0);
launchButton.setPreferredSize(new Dimension(300, 50));
mainPanel.add(launchButton, gbc);
gbc.insets = new Insets(8, 8, 8, 8);
add(mainPanel, BorderLayout.CENTER);
// Barre de statut
add(statusLabel, BorderLayout.SOUTH);
}
private void setupEventListeners() {
loginButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
performAuthentication();
}
});
versionComboBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateVersionStatus();
}
});
downloadButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
downloadSelectedVersion();
}
});
launchButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
launchMinecraft();
}
});
versionsButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showAllVersionsDialog();
}
});
}
private void loadAvailableVersions() {
SwingUtilities.invokeLater(() -> statusLabel.setText("Chargement des versions installées..."));
new Thread(() -> {
try {
// Charger uniquement les versions installées
List<String> installedVersions = getInstalledVersions();
SwingUtilities.invokeLater(() -> {
versionComboBox.removeAllItems();
if (installedVersions.isEmpty()) {
versionComboBox.addItem("Aucune version installée");
versionComboBox.setEnabled(false);
downloadButton.setEnabled(false);
statusLabel.setText("Aucune version installée - Utilisez \"📦 Toutes les Versions\" pour en télécharger");
} else {
for (String version : installedVersions) {
versionComboBox.addItem(version);
}
versionComboBox.setEnabled(true);
downloadButton.setEnabled(true);
updateVersionStatus();
statusLabel.setText("" + installedVersions.size() + " version(s) installée(s) - Prêt pour l'authentification !");
}
});
} catch (Exception e) {
SwingUtilities.invokeLater(() -> {
statusLabel.setText("Erreur de chargement des versions installées");
DarkThemeUtils.showDarkOptionPane(this,
"Impossible de charger les versions installées :\n\n" + e.getMessage(),
"Erreur de Chargement", JOptionPane.ERROR_MESSAGE);
});
}
}).start();
}
private List<String> getInstalledVersions() {
List<String> versions = new ArrayList<>();
Path versionsDir = Paths.get(System.getProperty("user.home") + "/.minecraft", "versions");
try {
if (Files.exists(versionsDir) && Files.isDirectory(versionsDir)) {
Files.list(versionsDir)
.filter(Files::isDirectory)
.forEach(versionDir -> {
String versionName = versionDir.getFileName().toString();
Path jarFile = versionDir.resolve(versionName + ".jar");
Path jsonFile = versionDir.resolve(versionName + ".json");
// Vérifier que les fichiers essentiels existent
if (Files.exists(jarFile) && Files.exists(jsonFile)) {
versions.add(versionName);
}
});
}
} catch (Exception e) {
System.err.println("[ERROR] Erreur lors de la lecture des versions installées: " + e.getMessage());
}
versions.sort(String::compareTo);
return versions;
}
private void performAuthentication() {
// LE BOUTON NE SE DÉSACTIVE JAMAIS - Solution simple !
statusLabel.setText("🔐 Ouverture du navigateur pour l'authentification Microsoft... (Vous pouvez recliquer si besoin)");
new Thread(() -> {
try {
Account account = authManager.authenticate();
currentAccount = account;
SwingUtilities.invokeLater(() -> {
userLabel.setText("👤 " + account.getUsername());
userLabel.setForeground(new Color(34, 197, 94));
userLabel.setBackground(new Color(21, 128, 61, 20));
loginButton.setText("🔁 Reconnexion");
launchButton.setEnabled(true);
statusLabel.setText("✅ Authentification réussie - Prêt à rejoindre le serveur PEDALO !");
});
} catch (Exception e) {
SwingUtilities.invokeLater(() -> {
// Messages d'erreur informatifs
if (e.getMessage() != null && e.getMessage().contains("Timeout")) {
statusLabel.setText("⏱️ Navigateur fermé ou délai expiré - Cliquez à nouveau quand vous voulez");
} else if (e.getMessage() != null && e.getMessage().contains("fermé")) {
statusLabel.setText("🌐 Navigateur fermé - Recliquez pour relancer l'authentification");
} else if (e.getMessage() != null && e.getMessage().contains("occupé")) {
statusLabel.setText("⚠️ Port occupé - Attendez 2 secondes et recliquez");
} else if (e.getMessage() != null && e.getMessage().contains("annulée")) {
statusLabel.setText("🔄 Authentification annulée - Recliquez quand vous voulez");
} else {
statusLabel.setText("❌ Erreur d'authentification - Recliquez pour réessayer");
}
System.out.println("[INFO] Authentification échouée: " + e.getMessage());
System.out.println("[INFO] Vous pouvez recliquer sur le bouton à tout moment pour réessayer");
});
}
}).start();
}
private void updateVersionStatus() {
String selectedVersion = (String) versionComboBox.getSelectedItem();
if (selectedVersion != null) {
String status = gameLauncher.getVersionStatus(selectedVersion);
if (status.startsWith("Non telechargee")) {
downloadButton.setText("📥 Telecharger");
styleModernButton(downloadButton, new Color(249, 115, 22), new Color(234, 88, 12)); // Orange
} else if (status.startsWith("Telechargee")) {
downloadButton.setText("🔄 Retelecharger (" + status + ")");
styleModernButton(downloadButton, new Color(34, 197, 94), new Color(22, 163, 74)); // Vert
} else {
downloadButton.setText("🔧 Reparer (" + status + ")");
styleModernButton(downloadButton, new Color(239, 68, 68), new Color(220, 38, 38)); // Rouge
}
}
}
private void downloadSelectedVersion() {
String selectedVersion = (String) versionComboBox.getSelectedItem();
if (selectedVersion == null) return;
downloadButton.setEnabled(false);
statusLabel.setText("[DL] Telechargement en cours... Veuillez patienter");
new Thread(() -> {
try {
boolean success = gameLauncher.downloadVersion(selectedVersion);
SwingUtilities.invokeLater(() -> {
downloadButton.setEnabled(true);
if (success) {
statusLabel.setText("[OK] Telechargement termine avec succes !");
updateVersionStatus();
// Plus de popup de téléchargement réussi
} else {
statusLabel.setText("[ERROR] Echec du telechargement - Veuillez reessayer");
DarkThemeUtils.showDarkOptionPane(this,
"Erreur lors du téléchargement de " + selectedVersion + "\n\n" +
"Consultez la console pour plus de détails.\n" +
"Essayez de retélécharger la version.",
"Erreur de Téléchargement", JOptionPane.ERROR_MESSAGE);
}
});
} catch (Exception e) {
SwingUtilities.invokeLater(() -> {
downloadButton.setEnabled(true);
statusLabel.setText("[ERROR] Erreur de telechargement");
DarkThemeUtils.showDarkOptionPane(LauncherFrame.this,
"Erreur de téléchargement:\n\n" + e.getMessage(),
"Erreur de Téléchargement", JOptionPane.ERROR_MESSAGE);
});
}
}).start();
}
private void launchMinecraft() {
if (currentAccount == null) {
DarkThemeUtils.showDarkOptionPane(this,
"Vous devez vous connecter avant de lancer Minecraft.\n\n" +
"Cliquez sur 'Se connecter avec Microsoft' pour commencer.\n" +
"L'authentification est automatique et sécurisée !",
"Connexion Requise", JOptionPane.WARNING_MESSAGE);
return;
}
String selectedVersion = (String) versionComboBox.getSelectedItem();
if (selectedVersion == null) {
DarkThemeUtils.showDarkOptionPane(this,
"Veuillez sélectionner une version de Minecraft à lancer.\n\n" +
"Choisissez une version dans le menu déroulant ci-dessus.",
"Version Requise", JOptionPane.WARNING_MESSAGE);
return;
}
String status = gameLauncher.getVersionStatus(selectedVersion);
if (status.startsWith("Non téléchargée")) {
DarkThemeUtils.showDarkOptionPane(this,
"La version " + selectedVersion + " n'est pas encore téléchargée.\n\n" +
"Veuillez la télécharger avant de lancer le jeu.\n" +
"Cliquez sur le bouton 'Télécharger' ci-dessus.",
"Téléchargement Requis", JOptionPane.WARNING_MESSAGE);
return;
}
launchButton.setEnabled(false);
statusLabel.setText("🚀 Connexion au serveur PEDALO en cours...");
new Thread(() -> {
try {
// Configuration serveur fixe
gameLauncher.setServerInfo("pedalo.vidoks.fr", "25565");
boolean success = gameLauncher.launchGame(selectedVersion, currentAccount);
SwingUtilities.invokeLater(() -> {
if (success) {
statusLabel.setText("✅ Connexion au serveur PEDALO réussie ! Bon jeu !");
launchButton.setEnabled(true);
// Plus de popup de lancement réussi
} else {
statusLabel.setText("[ERROR] Echec du lancement - Verifiez les prerequis");
launchButton.setEnabled(true);
DarkThemeUtils.showDarkOptionPane(this,
"Erreur lors du lancement de Minecraft.\n\n" +
"Vérifiez les prérequis :\n" +
" - Java est installé (java -version)\n" +
" - RAM suffisante (2GB minimum)\n" +
" - Espace disque disponible\n" +
" - Antivirus n'interfère pas\n\n" +
"Consultez les logs dans la console",
"Erreur de Lancement", JOptionPane.ERROR_MESSAGE);
}
});
} catch (Exception e) {
SwingUtilities.invokeLater(() -> {
launchButton.setEnabled(true);
statusLabel.setText("[ERROR] Erreur critique de lancement");
DarkThemeUtils.showDarkOptionPane(this,
"Erreur critique de lancement :\n\n" + e.getMessage() + "\n\n" +
"Consultez la console pour plus de détails.\n" +
"Essayez de retélécharger la version.",
"Erreur Critique", JOptionPane.ERROR_MESSAGE);
});
}
}).start();
}
private void showAllVersionsDialog() {
new VersionsTabDialog(this, gameLauncher).setVisible(true);
}
// Méthodes pour les popups modernes
private void showSuccessDialog(String title, String mainMessage, String details, String uuid) {
JPanel panel = new JPanel(new BorderLayout(10, 15));
panel.setBackground(Color.WHITE);
panel.setBorder(BorderFactory.createEmptyBorder(20, 25, 20, 25));
// Titre avec icône de succès
JLabel titleLabel = new JLabel("" + mainMessage);
titleLabel.setFont(new Font("SansSerif", Font.BOLD, 18));
titleLabel.setForeground(new Color(22, 163, 74)); // Vert succès
titleLabel.setHorizontalAlignment(SwingConstants.CENTER);
// Message de détails
JTextArea detailsArea = new JTextArea(details);
detailsArea.setFont(new Font("SansSerif", Font.PLAIN, 13));
detailsArea.setForeground(new Color(55, 65, 81));
detailsArea.setBackground(Color.WHITE);
detailsArea.setEditable(false);
detailsArea.setOpaque(false);
detailsArea.setLineWrap(true);
detailsArea.setWrapStyleWord(true);
// UUID (optionnel, en petit)
JLabel uuidLabel = new JLabel("UUID: " + uuid);
uuidLabel.setFont(new Font("Monospaced", Font.PLAIN, 10));
uuidLabel.setForeground(new Color(107, 114, 128)); // Gris
uuidLabel.setHorizontalAlignment(SwingConstants.CENTER);
// Panel pour l'icône de succès
JPanel iconPanel = new JPanel();
iconPanel.setBackground(Color.WHITE);
JLabel iconLabel = new JLabel("");
iconLabel.setFont(new Font("SansSerif", Font.BOLD, 48));
iconLabel.setForeground(new Color(22, 163, 74));
iconPanel.add(iconLabel);
panel.add(iconPanel, BorderLayout.NORTH);
panel.add(titleLabel, BorderLayout.CENTER);
JPanel bottomPanel = new JPanel(new BorderLayout(5, 5));
bottomPanel.setBackground(Color.WHITE);
bottomPanel.add(detailsArea, BorderLayout.CENTER);
bottomPanel.add(uuidLabel, BorderLayout.SOUTH);
panel.add(bottomPanel, BorderLayout.SOUTH);
JOptionPane.showMessageDialog(this, panel, title, JOptionPane.PLAIN_MESSAGE);
}
private void showErrorDialog(String title, String mainMessage, String details) {
JPanel panel = new JPanel(new BorderLayout(10, 15));
panel.setBackground(Color.WHITE);
panel.setBorder(BorderFactory.createEmptyBorder(20, 25, 20, 25));
// Titre avec icône d'erreur
JLabel titleLabel = new JLabel("" + mainMessage);
titleLabel.setFont(new Font("SansSerif", Font.BOLD, 18));
titleLabel.setForeground(new Color(220, 38, 38)); // Rouge erreur
titleLabel.setHorizontalAlignment(SwingConstants.CENTER);
// Message de détails
JTextArea detailsArea = new JTextArea(details);
detailsArea.setFont(new Font("SansSerif", Font.PLAIN, 13));
detailsArea.setForeground(new Color(55, 65, 81));
detailsArea.setBackground(Color.WHITE);
detailsArea.setEditable(false);
detailsArea.setOpaque(false);
detailsArea.setLineWrap(true);
detailsArea.setWrapStyleWord(true);
// Panel pour l'icône d'erreur
JPanel iconPanel = new JPanel();
iconPanel.setBackground(Color.WHITE);
JLabel iconLabel = new JLabel("");
iconLabel.setFont(new Font("SansSerif", Font.BOLD, 48));
iconLabel.setForeground(new Color(220, 38, 38));
iconPanel.add(iconLabel);
panel.add(iconPanel, BorderLayout.NORTH);
panel.add(titleLabel, BorderLayout.CENTER);
panel.add(detailsArea, BorderLayout.SOUTH);
JOptionPane.showMessageDialog(this, panel, title, JOptionPane.PLAIN_MESSAGE);
}
}

View File

@ -0,0 +1,376 @@
// src/com/minecraftlauncher/ui/VersionsTabDialog.java
package com.minecraftlauncher.ui;
import com.minecraftlauncher.game.GameLauncher;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.Map;
public class VersionsTabDialog extends JDialog {
private GameLauncher gameLauncher;
private JTree versionsTree;
private DefaultTreeModel treeModel;
private JButton downloadButton;
private JButton installButton;
private JLabel statusLabel;
private JProgressBar progressBar;
public VersionsTabDialog(JFrame parent, GameLauncher gameLauncher) {
super(parent, "Toutes les Versions Minecraft", true);
this.gameLauncher = gameLauncher;
initializeComponents();
setupLayout();
setupEventListeners();
loadAllVersions();
setSize(850, 650);
setLocationRelativeTo(parent);
setResizable(true);
}
private void initializeComponents() {
// Configuration du thème sombre
getContentPane().setBackground(new Color(17, 24, 39));
// Arbre des versions avec icônes
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Versions Minecraft");
treeModel = new DefaultTreeModel(root);
versionsTree = new JTree(treeModel);
versionsTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
versionsTree.setShowsRootHandles(true);
versionsTree.setRootVisible(false);
// Style amélioré de l'arbre
versionsTree.setBackground(new Color(31, 41, 55));
versionsTree.setForeground(new Color(243, 244, 246));
versionsTree.setFont(new Font("SansSerif", Font.PLAIN, 13));
versionsTree.setRowHeight(25); // Plus d'espace entre les lignes
versionsTree.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
// Boutons améliorés
downloadButton = new JButton("📥 Télécharger");
downloadButton.setEnabled(false);
styleModernButton(downloadButton, new Color(34, 197, 94), new Color(22, 163, 74)); // Vert
downloadButton.setPreferredSize(new Dimension(160, 45));
downloadButton.setToolTipText("Télécharger la version sélectionnée");
installButton = new JButton("⚡ Installer");
installButton.setEnabled(false);
styleModernButton(installButton, new Color(37, 99, 235), new Color(29, 78, 216)); // Bleu
installButton.setPreferredSize(new Dimension(140, 45));
installButton.setToolTipText("Installer et définir comme version par défaut");
JButton refreshButton = new JButton("🔄 Actualiser");
styleModernButton(refreshButton, new Color(75, 85, 99), new Color(55, 65, 81)); // Gris
refreshButton.setPreferredSize(new Dimension(130, 45));
refreshButton.addActionListener(e -> loadAllVersions());
refreshButton.setToolTipText("Actualiser la liste des versions");
JButton closeButton = new JButton("✖ Fermer");
styleModernButton(closeButton, new Color(220, 38, 38), new Color(185, 28, 28)); // Rouge
closeButton.setPreferredSize(new Dimension(110, 45));
closeButton.addActionListener(e -> dispose());
// Barre de progression
progressBar = new JProgressBar();
progressBar.setStringPainted(true);
progressBar.setVisible(false);
progressBar.setBackground(new Color(31, 41, 55));
progressBar.setForeground(new Color(34, 197, 94));
// Label de statut
statusLabel = new JLabel("Chargement des versions...");
statusLabel.setForeground(new Color(156, 163, 175));
statusLabel.setFont(new Font("SansSerif", Font.PLAIN, 11));
}
private void styleModernButton(JButton button, Color bgColor, Color hoverColor) {
button.setBackground(bgColor);
button.setForeground(Color.WHITE);
button.setFocusPainted(false);
button.setBorderPainted(false);
button.setFont(new Font("SansSerif", Font.BOLD, 12));
button.setCursor(new Cursor(Cursor.HAND_CURSOR));
button.setBorder(BorderFactory.createEmptyBorder(8, 16, 8, 16));
// Effet hover
button.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseEntered(java.awt.event.MouseEvent evt) {
button.setBackground(hoverColor);
}
public void mouseExited(java.awt.event.MouseEvent evt) {
button.setBackground(bgColor);
}
});
}
private void setupLayout() {
setLayout(new BorderLayout(10, 10));
// Panel principal avec thème sombre
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBackground(new Color(17, 24, 39));
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Titre avec icône amélioré
JLabel titleLabel = new JLabel("📦 Télécharger toutes les versions Minecraft", SwingConstants.CENTER);
titleLabel.setFont(new Font("SansSerif", Font.BOLD, 20));
titleLabel.setForeground(new Color(147, 197, 253));
titleLabel.setBorder(BorderFactory.createEmptyBorder(5, 0, 10, 0));
// Description améliorée
JLabel descLabel = new JLabel("<html><center><b>Explorez toutes les versions disponibles</b><br/>" +
"<span style='color: #10B981;'>🟢 Versions Stables</span> • " +
"<span style='color: #F59E0B;'>🟡 Snapshots</span> • " +
"<span style='color: #3B82F6;'>🔵 Betas</span> • " +
"<span style='color: #8B5CF6;'>🟣 Alphas</span></center></html>");
descLabel.setFont(new Font("SansSerif", Font.PLAIN, 13));
descLabel.setForeground(new Color(209, 213, 219));
descLabel.setHorizontalAlignment(SwingConstants.CENTER);
// Panel du haut
JPanel topPanel = new JPanel(new BorderLayout(5, 5));
topPanel.setBackground(new Color(17, 24, 39));
topPanel.add(titleLabel, BorderLayout.CENTER);
topPanel.add(descLabel, BorderLayout.SOUTH);
// Panel central avec l'arbre amélioré
JScrollPane scrollPane = new JScrollPane(versionsTree);
scrollPane.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(75, 85, 99), 2),
BorderFactory.createEmptyBorder(5, 5, 5, 5)
));
scrollPane.setPreferredSize(new Dimension(750, 420));
scrollPane.getViewport().setBackground(new Color(31, 41, 55));
// Amélioration du scrollbar
scrollPane.getVerticalScrollBar().setBackground(new Color(31, 41, 55));
scrollPane.getHorizontalScrollBar().setBackground(new Color(31, 41, 55));
// Panel des boutons
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
buttonPanel.setBackground(new Color(17, 24, 39));
buttonPanel.add(downloadButton);
buttonPanel.add(installButton);
buttonPanel.add(new JButton("Actualiser") {{
styleModernButton(this, new Color(75, 85, 99), new Color(55, 65, 81));
setPreferredSize(new Dimension(100, 40));
addActionListener(e -> loadAllVersions());
}});
buttonPanel.add(new JButton("Fermer") {{
styleModernButton(this, new Color(220, 38, 38), new Color(185, 28, 28));
setPreferredSize(new Dimension(80, 40));
addActionListener(e -> dispose());
}});
// Panel du bas avec status et progress
JPanel bottomPanel = new JPanel(new BorderLayout(5, 5));
bottomPanel.setBackground(new Color(17, 24, 39));
bottomPanel.add(statusLabel, BorderLayout.WEST);
bottomPanel.add(progressBar, BorderLayout.CENTER);
mainPanel.add(topPanel, BorderLayout.NORTH);
mainPanel.add(scrollPane, BorderLayout.CENTER);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
// Panel final
JPanel finalPanel = new JPanel(new BorderLayout());
finalPanel.add(mainPanel, BorderLayout.CENTER);
finalPanel.add(bottomPanel, BorderLayout.SOUTH);
add(finalPanel);
}
private void setupEventListeners() {
// Sélection dans l'arbre
versionsTree.addTreeSelectionListener(e -> {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) versionsTree.getLastSelectedPathComponent();
if (node != null && node.isLeaf() && !node.isRoot()) {
String version = node.getUserObject().toString();
if (!version.contains(" (") && !version.equals("Versions Minecraft")) { // Pas un dossier de catégorie
downloadButton.setEnabled(true);
installButton.setEnabled(true);
updateVersionInfo(version);
} else {
downloadButton.setEnabled(false);
installButton.setEnabled(false);
}
} else {
downloadButton.setEnabled(false);
installButton.setEnabled(false);
}
});
// Bouton de téléchargement
downloadButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
downloadSelectedVersion();
}
});
// Bouton d'installation
installButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
installSelectedVersion();
}
});
}
private void loadAllVersions() {
statusLabel.setText("Chargement des versions depuis Mojang...");
downloadButton.setEnabled(false);
installButton.setEnabled(false);
new Thread(() -> {
try {
Map<String, List<String>> versionsByType = gameLauncher.getAvailableVersionsByType();
SwingUtilities.invokeLater(() -> {
populateVersionTree(versionsByType);
statusLabel.setText("Versions chargées avec succès - Sélectionnez une version pour la télécharger");
});
} catch (Exception e) {
SwingUtilities.invokeLater(() -> {
statusLabel.setText("Erreur lors du chargement des versions");
DarkThemeUtils.showDarkOptionPane(this,
"Erreur lors du chargement des versions :\n" + e.getMessage(),
"Erreur", JOptionPane.ERROR_MESSAGE);
});
}
}).start();
}
private void populateVersionTree(Map<String, List<String>> versionsByType) {
DefaultMutableTreeNode root = (DefaultMutableTreeNode) treeModel.getRoot();
root.removeAllChildren();
// Ajouter les versions stables
if (!versionsByType.get("release").isEmpty()) {
DefaultMutableTreeNode releaseNode = new DefaultMutableTreeNode("🟢 Versions Stables (" + versionsByType.get("release").size() + ")");
for (String version : versionsByType.get("release")) {
releaseNode.add(new DefaultMutableTreeNode(version));
}
root.add(releaseNode);
}
// Ajouter les snapshots
if (!versionsByType.get("snapshot").isEmpty()) {
DefaultMutableTreeNode snapshotNode = new DefaultMutableTreeNode("🟡 Snapshots (" + versionsByType.get("snapshot").size() + ")");
for (String version : versionsByType.get("snapshot")) {
snapshotNode.add(new DefaultMutableTreeNode(version));
}
root.add(snapshotNode);
}
// Ajouter les versions beta
if (!versionsByType.get("old_beta").isEmpty()) {
DefaultMutableTreeNode betaNode = new DefaultMutableTreeNode("🔵 Versions Beta (" + versionsByType.get("old_beta").size() + ")");
for (String version : versionsByType.get("old_beta")) {
betaNode.add(new DefaultMutableTreeNode(version));
}
root.add(betaNode);
}
// Ajouter les versions alpha
if (!versionsByType.get("old_alpha").isEmpty()) {
DefaultMutableTreeNode alphaNode = new DefaultMutableTreeNode("🟣 Versions Alpha (" + versionsByType.get("old_alpha").size() + ")");
for (String version : versionsByType.get("old_alpha")) {
alphaNode.add(new DefaultMutableTreeNode(version));
}
root.add(alphaNode);
}
treeModel.reload();
// Expand les nœuds par défaut
for (int i = 0; i < versionsTree.getRowCount(); i++) {
versionsTree.expandRow(i);
}
}
private void updateVersionInfo(String version) {
String status = gameLauncher.getVersionStatus(version);
if (status.startsWith("Non téléchargée")) {
statusLabel.setText("Version " + version + " - Non installée, prête à télécharger");
} else {
statusLabel.setText("Version " + version + " - " + status);
}
}
private void downloadSelectedVersion() {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) versionsTree.getLastSelectedPathComponent();
if (node == null || !node.isLeaf()) return;
String version = node.getUserObject().toString();
downloadButton.setEnabled(false);
installButton.setEnabled(false);
progressBar.setVisible(true);
progressBar.setIndeterminate(true);
statusLabel.setText("Téléchargement de " + version + " en cours...");
new Thread(() -> {
try {
boolean success = gameLauncher.downloadVersion(version);
SwingUtilities.invokeLater(() -> {
progressBar.setVisible(false);
downloadButton.setEnabled(true);
installButton.setEnabled(true);
if (success) {
statusLabel.setText("Téléchargement de " + version + " terminé avec succès!");
DarkThemeUtils.showDarkOptionPane(this,
"Version " + version + " téléchargée avec succès!\n" +
"Vous pouvez maintenant la lancer depuis l'interface principale.",
"Téléchargement Réussi", JOptionPane.INFORMATION_MESSAGE);
} else {
statusLabel.setText("Erreur lors du téléchargement de " + version);
DarkThemeUtils.showDarkOptionPane(this,
"Erreur lors du téléchargement de " + version + "\n" +
"Vérifiez votre connexion Internet et réessayez.",
"Erreur de Téléchargement", JOptionPane.ERROR_MESSAGE);
}
});
} catch (Exception e) {
SwingUtilities.invokeLater(() -> {
progressBar.setVisible(false);
downloadButton.setEnabled(true);
installButton.setEnabled(true);
statusLabel.setText("Erreur lors du téléchargement");
DarkThemeUtils.showDarkOptionPane(this,
"Erreur lors du téléchargement :\n" + e.getMessage(),
"Erreur", JOptionPane.ERROR_MESSAGE);
});
}
}).start();
}
private void installSelectedVersion() {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) versionsTree.getLastSelectedPathComponent();
if (node == null || !node.isLeaf()) return;
String version = node.getUserObject().toString();
int choice = DarkThemeUtils.showDarkConfirmDialog(this,
"Voulez-vous télécharger et définir " + version + " comme version par défaut ?",
"Installation et Configuration",
JOptionPane.YES_NO_OPTION);
if (choice == JOptionPane.YES_OPTION) {
downloadSelectedVersion();
// TODO: Définir comme version par défaut dans LauncherFrame
}
}
}