Created
December 16, 2025 06:48
-
-
Save tirbofish/230ce140bd6820bb8459ce2f23ba8834 to your computer and use it in GitHub Desktop.
Minecraft downloader (deadass it actually works LMAO)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Minecraft Portable Downloader | |
| Downloads Minecraft client, libraries, assets, and Java runtime to run from USB drive | |
| """ | |
| import os | |
| import sys | |
| import json | |
| import urllib.request | |
| import hashlib | |
| import platform | |
| import zipfile | |
| import shutil | |
| from pathlib import Path | |
| from urllib.parse import urlparse | |
| class MinecraftDownloader: | |
| def __init__(self, install_dir): | |
| self.install_dir = Path(install_dir) | |
| self.manifest_url = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json" | |
| self.libraries_url = "https://libraries.minecraft.net/" | |
| self.resources_url = "https://resources.download.minecraft.net/" | |
| # Detect OS | |
| system = platform.system() | |
| if system == "Windows": | |
| self.os_name = "windows" | |
| elif system == "Darwin": | |
| self.os_name = "osx" | |
| else: | |
| self.os_name = "linux" | |
| print(f"Detected OS: {self.os_name}") | |
| def download_file(self, url, dest_path, expected_sha1=None): | |
| """Download a file with SHA1 verification""" | |
| dest_path = Path(dest_path) | |
| dest_path.parent.mkdir(parents=True, exist_ok=True) | |
| # Skip if exists and hash matches | |
| if dest_path.exists() and expected_sha1: | |
| with open(dest_path, 'rb') as f: | |
| file_hash = hashlib.sha1(f.read()).hexdigest() | |
| if file_hash == expected_sha1: | |
| print(f" ✓ Already exists: {dest_path.name}") | |
| return True | |
| try: | |
| print(f" Downloading: {dest_path.name}") | |
| urllib.request.urlretrieve(url, dest_path) | |
| # Verify hash if provided | |
| if expected_sha1: | |
| with open(dest_path, 'rb') as f: | |
| file_hash = hashlib.sha1(f.read()).hexdigest() | |
| if file_hash != expected_sha1: | |
| print(f" ✗ Hash mismatch for {dest_path.name}") | |
| return False | |
| return True | |
| except Exception as e: | |
| print(f" ✗ Error downloading {url}: {e}") | |
| return False | |
| def get_version_manifest(self): | |
| """Fetch the version manifest""" | |
| print("\nFetching version manifest...") | |
| with urllib.request.urlopen(self.manifest_url) as response: | |
| return json.loads(response.read()) | |
| def get_version_data(self, version_url): | |
| """Fetch specific version data""" | |
| with urllib.request.urlopen(version_url) as response: | |
| return json.loads(response.read()) | |
| def list_versions(self, manifest): | |
| """List available versions""" | |
| print("\n=== Available Versions ===") | |
| print(f"Latest release: {manifest['latest']['release']}") | |
| print(f"Latest snapshot: {manifest['latest']['snapshot']}") | |
| print("\nRecent releases:") | |
| releases = [v for v in manifest['versions'] if v['type'] == 'release'][:10] | |
| for v in releases: | |
| print(f" - {v['id']}") | |
| def should_download_library(self, lib): | |
| """Check if library should be downloaded for this OS""" | |
| if 'rules' not in lib: | |
| return True | |
| for rule in lib['rules']: | |
| action = rule['action'] | |
| if 'os' in rule: | |
| os_match = rule['os'].get('name') == self.os_name | |
| if action == 'allow' and os_match: | |
| return True | |
| elif action == 'disallow' and os_match: | |
| return False | |
| elif action == 'allow': | |
| return True | |
| return False | |
| def download_libraries(self, version_data): | |
| """Download all required libraries""" | |
| print("\n=== Downloading Libraries ===") | |
| libs_dir = self.install_dir / "libraries" | |
| for lib in version_data['libraries']: | |
| if not self.should_download_library(lib): | |
| continue | |
| # Parse library name | |
| lib_name = lib['downloads'].get('artifact') or lib['downloads'].get('classifiers', {}) | |
| if 'artifact' in lib['downloads']: | |
| artifact = lib['downloads']['artifact'] | |
| lib_path = libs_dir / artifact['path'] | |
| lib_url = artifact['url'] | |
| lib_sha1 = artifact['sha1'] | |
| self.download_file(lib_url, lib_path, lib_sha1) | |
| # Download natives | |
| if 'classifiers' in lib['downloads']: | |
| classifiers = lib['downloads']['classifiers'] | |
| natives_key = lib.get('natives', {}).get(self.os_name) | |
| if natives_key and natives_key in classifiers: | |
| native = classifiers[natives_key] | |
| native_path = libs_dir / native['path'] | |
| self.download_file(native['url'], native_path, native['sha1']) | |
| # Extract natives | |
| natives_dir = self.install_dir / "natives" | |
| natives_dir.mkdir(exist_ok=True) | |
| try: | |
| with zipfile.ZipFile(native_path, 'r') as zip_ref: | |
| for file in zip_ref.namelist(): | |
| if not file.startswith('META-INF/'): | |
| zip_ref.extract(file, natives_dir) | |
| except Exception as e: | |
| print(f" Warning: Could not extract {native_path.name}: {e}") | |
| def download_assets(self, version_data): | |
| """Download game assets""" | |
| print("\n=== Downloading Assets ===") | |
| assets_dir = self.install_dir / "assets" | |
| # Download asset index | |
| asset_index = version_data['assetIndex'] | |
| index_path = assets_dir / "indexes" / f"{asset_index['id']}.json" | |
| self.download_file(asset_index['url'], index_path, asset_index['sha1']) | |
| # Load asset index | |
| with open(index_path, 'r') as f: | |
| asset_data = json.load(f) | |
| # Download assets | |
| objects_dir = assets_dir / "objects" | |
| total = len(asset_data['objects']) | |
| print(f"Downloading {total} assets...") | |
| for i, (name, obj) in enumerate(asset_data['objects'].items(), 1): | |
| hash_prefix = obj['hash'][:2] | |
| asset_path = objects_dir / hash_prefix / obj['hash'] | |
| asset_url = f"{self.resources_url}{hash_prefix}/{obj['hash']}" | |
| if i % 100 == 0: | |
| print(f" Progress: {i}/{total}") | |
| self.download_file(asset_url, asset_path, obj['hash']) | |
| def download_client(self, version_data, version_id): | |
| """Download the client JAR""" | |
| print("\n=== Downloading Client JAR ===") | |
| client = version_data['downloads']['client'] | |
| client_path = self.install_dir / "versions" / version_id / f"{version_id}.jar" | |
| self.download_file(client['url'], client_path, client['sha1']) | |
| # Save version JSON | |
| version_json_path = self.install_dir / "versions" / version_id / f"{version_id}.json" | |
| with open(version_json_path, 'w') as f: | |
| json.dump(version_data, f, indent=2) | |
| def create_launch_script(self, version_data, version_id): | |
| """Create launch scripts for the game""" | |
| print("\n=== Creating Launch Scripts ===") | |
| # Build classpath | |
| libs_dir = self.install_dir / "libraries" | |
| classpath_parts = [] | |
| for lib in version_data['libraries']: | |
| if not self.should_download_library(lib): | |
| continue | |
| if 'artifact' in lib['downloads']: | |
| artifact = lib['downloads']['artifact'] | |
| lib_path = libs_dir / artifact['path'] | |
| classpath_parts.append(str(lib_path)) | |
| # Add client JAR | |
| client_jar = self.install_dir / "versions" / version_id / f"{version_id}.jar" | |
| classpath_parts.append(str(client_jar)) | |
| # Classpath separator | |
| separator = ";" if self.os_name == "windows" else ":" | |
| classpath = separator.join(classpath_parts) | |
| # Java arguments | |
| main_class = version_data['mainClass'] | |
| natives_dir = self.install_dir / "natives" | |
| assets_dir = self.install_dir / "assets" | |
| # Offline authentication - use dummy values | |
| uuid = "00000000-0000-0000-0000-000000000000" | |
| access_token = "0" | |
| # Create Windows batch file WITH username prompt | |
| if self.os_name == "windows": | |
| script_path = self.install_dir / "launch.bat" | |
| with open(script_path, 'w') as f: | |
| f.write('@echo off\n') | |
| f.write('title Minecraft Launcher - Offline Mode\n') | |
| f.write('echo ========================================\n') | |
| f.write('echo Minecraft Offline Mode Launcher\n') | |
| f.write('echo ========================================\n') | |
| f.write('echo.\n') | |
| f.write('set /p USERNAME="Enter your username (or press Enter for OfflinePlayer): "\n') | |
| f.write('if "%USERNAME%"=="" set USERNAME=OfflinePlayer\n') | |
| f.write('echo.\n') | |
| f.write('echo Starting Minecraft...\n') | |
| f.write('echo Username: %USERNAME%\n') | |
| f.write('echo.\n') | |
| f.write(f'java -Xmx2G -Xms1G -Djava.library.path="{natives_dir}" ') | |
| f.write(f'-cp "{classpath}" ') | |
| f.write(f'{main_class} ') | |
| f.write(f'--username %USERNAME% ') | |
| f.write(f'--uuid {uuid} ') | |
| f.write(f'--accessToken {access_token} ') | |
| f.write(f'--userType legacy ') | |
| f.write(f'--assetsDir "{assets_dir}" ') | |
| f.write(f'--assetIndex {version_data["assetIndex"]["id"]} ') | |
| f.write(f'--version {version_id}\n') | |
| f.write('pause\n') | |
| print(f"Created: {script_path}") | |
| # Also create a version with fixed username for convenience | |
| script_path_fixed = self.install_dir / "launch_quick.bat" | |
| with open(script_path_fixed, 'w') as f: | |
| f.write('@echo off\n') | |
| f.write('title Minecraft Launcher - Quick Launch\n') | |
| f.write('echo Starting Minecraft as OfflinePlayer...\n') | |
| f.write('echo.\n') | |
| f.write(f'java -Xmx2G -Xms1G -Djava.library.path="{natives_dir}" ') | |
| f.write(f'-cp "{classpath}" ') | |
| f.write(f'{main_class} ') | |
| f.write(f'--username OfflinePlayer ') | |
| f.write(f'--uuid {uuid} ') | |
| f.write(f'--accessToken {access_token} ') | |
| f.write(f'--userType legacy ') | |
| f.write(f'--assetsDir "{assets_dir}" ') | |
| f.write(f'--assetIndex {version_data["assetIndex"]["id"]} ') | |
| f.write(f'--version {version_id}\n') | |
| print(f"Created: {script_path_fixed} (quick launch without prompt)") | |
| # Create Unix shell script WITH username prompt | |
| else: | |
| script_path = self.install_dir / "launch.sh" | |
| with open(script_path, 'w') as f: | |
| f.write('#!/bin/bash\n') | |
| f.write('echo "========================================"\n') | |
| f.write('echo " Minecraft Offline Mode Launcher"\n') | |
| f.write('echo "========================================"\n') | |
| f.write('echo ""\n') | |
| f.write('read -p "Enter your username (or press Enter for OfflinePlayer): " USERNAME\n') | |
| f.write('USERNAME=${USERNAME:-OfflinePlayer}\n') | |
| f.write('echo ""\n') | |
| f.write('echo "Starting Minecraft..."\n') | |
| f.write('echo "Username: $USERNAME"\n') | |
| f.write('echo ""\n') | |
| f.write(f'java -Xmx2G -Xms1G -Djava.library.path="{natives_dir}" ') | |
| f.write(f'-cp "{classpath}" ') | |
| f.write(f'{main_class} ') | |
| f.write(f'--username "$USERNAME" ') | |
| f.write(f'--uuid {uuid} ') | |
| f.write(f'--accessToken {access_token} ') | |
| f.write(f'--userType legacy ') | |
| f.write(f'--assetsDir "{assets_dir}" ') | |
| f.write(f'--assetIndex {version_data["assetIndex"]["id"]} ') | |
| f.write(f'--version {version_id}\n') | |
| os.chmod(script_path, 0o755) | |
| print(f"Created: {script_path}") | |
| # Also create a version with fixed username for convenience | |
| script_path_fixed = self.install_dir / "launch_quick.sh" | |
| with open(script_path_fixed, 'w') as f: | |
| f.write('#!/bin/bash\n') | |
| f.write('echo "Starting Minecraft as OfflinePlayer..."\n') | |
| f.write('echo ""\n') | |
| f.write(f'java -Xmx2G -Xms1G -Djava.library.path="{natives_dir}" ') | |
| f.write(f'-cp "{classpath}" ') | |
| f.write(f'{main_class} ') | |
| f.write(f'--username OfflinePlayer ') | |
| f.write(f'--uuid {uuid} ') | |
| f.write(f'--accessToken {access_token} ') | |
| f.write(f'--userType legacy ') | |
| f.write(f'--assetsDir "{assets_dir}" ') | |
| f.write(f'--assetIndex {version_data["assetIndex"]["id"]} ') | |
| f.write(f'--version {version_id}\n') | |
| os.chmod(script_path_fixed, 0o755) | |
| print(f"Created: {script_path_fixed} (quick launch without prompt)") | |
| def download_version(self, version_id=None): | |
| """Download a specific Minecraft version""" | |
| # Get version manifest | |
| manifest = self.get_version_manifest() | |
| if version_id is None: | |
| self.list_versions(manifest) | |
| version_id = input("\nEnter version to download (or press Enter for latest release): ").strip() | |
| if not version_id: | |
| version_id = manifest['latest']['release'] | |
| # Find version | |
| version_info = None | |
| for v in manifest['versions']: | |
| if v['id'] == version_id: | |
| version_info = v | |
| break | |
| if not version_info: | |
| print(f"Error: Version {version_id} not found") | |
| return False | |
| print(f"\n{'='*60}") | |
| print(f"Downloading Minecraft {version_id}") | |
| print(f"{'='*60}") | |
| # Get version data | |
| version_data = self.get_version_data(version_info['url']) | |
| # Download everything | |
| self.download_client(version_data, version_id) | |
| self.download_libraries(version_data) | |
| self.download_assets(version_data) | |
| self.create_launch_script(version_data, version_id) | |
| print(f"\n{'='*60}") | |
| print("Download Complete!") | |
| print(f"{'='*60}") | |
| print(f"Installation directory: {self.install_dir}") | |
| print("\nTwo launch scripts created:") | |
| if self.os_name == "windows": | |
| print(" 1. launch.bat - Prompts for username each time") | |
| print(" 2. launch_quick.bat - Uses 'OfflinePlayer' (no prompt)") | |
| else: | |
| print(" 1. launch.sh - Prompts for username each time") | |
| print(" 2. launch_quick.sh - Uses 'OfflinePlayer' (no prompt)") | |
| print("\nNote: You need Java installed to run Minecraft") | |
| return True | |
| def main(): | |
| print("="*60) | |
| print("Minecraft Portable Downloader") | |
| print("="*60) | |
| if len(sys.argv) > 1: | |
| install_dir = sys.argv[1] | |
| else: | |
| install_dir = input("Enter installation directory (e.g., E:/minecraft or /media/usb/minecraft): ").strip() | |
| if not install_dir: | |
| install_dir = "./minecraft_portable" | |
| downloader = MinecraftDownloader(install_dir) | |
| version = sys.argv[2] if len(sys.argv) > 2 else None | |
| downloader.download_version(version) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment