Skip to content

Instantly share code, notes, and snippets.

@tirbofish
Created December 16, 2025 06:48
Show Gist options
  • Select an option

  • Save tirbofish/230ce140bd6820bb8459ce2f23ba8834 to your computer and use it in GitHub Desktop.

Select an option

Save tirbofish/230ce140bd6820bb8459ce2f23ba8834 to your computer and use it in GitHub Desktop.
Minecraft downloader (deadass it actually works LMAO)
#!/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