Compare commits

...

2 Commits

Author SHA1 Message Date
833b022bf8 Resolve merge conflicts and integrate PedaloLauncher code
- Merged existing repository structure with complete launcher implementation
- Updated README.md with full documentation
- Enhanced .gitignore with comprehensive exclusions
- Ready for production deployment
2025-09-05 02:02:58 +02:00
8bafe167ac 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!
2025-09-05 02:00:17 +02:00
13 changed files with 2833 additions and 17 deletions

82
.gitignore vendored
View File

@ -1,26 +1,76 @@
# ---> Java
# Compiled class file
### Java ###
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.aar
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
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/
### BlueJ files ###
*.ctxt
### Mobile Tools for Java (J2ME) ###
.mtj.tmp/
### 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

View File

@ -1,3 +1,70 @@
# PedaloLauncher
Le dépot du launcher du PEDALO
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
```
PedaloLauncher/
├── 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é par VIDOKS 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
}
}
}