Ascendara Downloader
Overview#
The Ascendara Downloader is a high-performance multi-threaded downloader responsible for downloading, verifying, and extracting game files. It features a robust download system with progress tracking, verification, and automatic error handling.
Features#
- Multi-threaded downloads with configurable thread count
- Progress tracking with speed and ETA
- File verification and integrity checks
- Content-Type validation
- Automatic error reporting with crash reporter integration
- Resume capability
- Archive extraction (.rar, .zip)
- Desktop notifications with theme support
Usage#
Command Line Arguments#
AscendaraDownloader.exe [link] [game] [online] [dlc] [isVr] [version] [size] [download_dir] [--withNotification theme]
Parameters#
- link Download URL
- game Game identifier
- online Online-only flag (true/false)
- dlc DLC identifier (true/false)
- isVr VR game flag (true/false)
- version Game version
- size Expected file size
- download_dir Target download directory
- --withNotification Optional theme for notifications (light, dark, blue)
Example#
AscendaraDownloader.exe "https://example.com/game.rar" "MyGame" false false false "1.0" "1000000" "C:/Games" --withNotification dark
Development#
Key Classes#
DownloadManager
Manages multi-threaded downloads with configurable thread count:
class DownloadManager: def __init__(self, url, total_size, num_threads=None): self.url = url self.total_size = total_size # Thread count from settings or default to 4 self.num_threads = self._get_thread_count() self.chunks = [] self.downloaded_size = 0 self.lock = threading.Lock() self.max_retries = 3 # Maximum number of retries per chunk def split_chunks(self): # Split download into equal chunks chunk_size = self.total_size // self.num_threads for i in range(self.num_threads): start = i * chunk_size end = start + chunk_size - 1 if i < self.num_threads - 1 else self.total_size - 1 self.chunks.append(DownloadChunk(start, end, self.url)) def download_chunk(self, chunk, session, callback=None): # Download a specific chunk with range headers headers = {'Range': f'bytes={chunk.start}-{chunk.end}'} response = session.get(chunk.url, headers=headers, stream=True) for data in response.iter_content(chunk_size=1024*1024): if not data: break chunk.data += data chunk.downloaded += len(data) with self.lock: self.downloaded_size += len(data) if callback: callback(len(data))
DownloadChunk
Represents a chunk of the download:
class DownloadChunk: def __init__(self, start, end, url): self.start = start self.end = end self.url = url self.data = b"" self.downloaded = 0
Progress Tracking#
The downloader maintains a JSON file with detailed progress information:
game_info = { "game": game, "online": online, "dlc": dlc, "isVr": isVr, "version": version, "size": size, "executable": executable_path, "isRunning": False, "downloadingData": { "downloading": False, "extracting": False, "updating": False, "verifying": False, "progressCompleted": "0.00", "progressDownloadSpeeds": "0.00 KB/s", "timeUntilComplete": "0s" } }
Download Process#
- Validates content type and gets file size
- Determines archive type (.rar/.zip)
- Initializes DownloadManager with configured thread count
- Splits download into equal chunks
- Downloads chunks in parallel using ThreadPoolExecutor
- Updates progress with speed and ETA
- Combines chunks into final file
- Extracts archive contents
- Verifies extracted files
- Sends completion notification
Error Handling#
Implements comprehensive error handling with crash reporter integration:
def handleerror(game_info, game_info_path, e): game_info['downloadingData'] = { "error": True, "message": str(e) } safe_write_json(game_info_path, game_info) def launch_crash_reporter(error_code, error_message): # Register crash reporter to launch on exit atexit.register(_launch_crash_reporter_on_exit, error_code, error_message)
Notifications#
Supports desktop notifications with theme customization:
def _launch_notification(theme, title, message): subprocess.Popen( [notification_helper_path, "--theme", theme, "--title", title, "--message", message], creationflags=subprocess.CREATE_NO_WINDOW )
Configuration#
Thread count is configurable via settings:
settings_path = os.path.join(os.getenv('APPDATA'), 'ascendara', 'ascendarasettings.json') settings = { "threadCount": 4 # Default value }