Compare commits
No commits in common. "833b022bf8f5cc54abaf3a16c51031abf5737a18" and "93a77b9ab5fe3ec937718d97b5a3a31e2ece645c" have entirely different histories.
833b022bf8
...
93a77b9ab5
82
.gitignore
vendored
82
.gitignore
vendored
@ -1,76 +1,26 @@
|
||||
### Java ###
|
||||
# ---> Java
|
||||
# Compiled class file
|
||||
*.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 +0,0 @@
|
||||
Main-Class: com.minecraftlauncher.Main
|
69
README.md
69
README.md
@ -1,70 +1,3 @@
|
||||
# PedaloLauncher
|
||||
|
||||
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)
|
||||
Le dépot du launcher du PEDALO
|
@ -1,2 +0,0 @@
|
||||
Main-Class: com.minecraftlauncher.Main
|
||||
|
@ -1,52 +0,0 @@
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,636 +0,0 @@
|
||||
// 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.";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,500 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
// 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; }
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
// 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; }
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,277 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,704 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
@ -1,376 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user