diff --git a/src/pages/index.ts b/src/pages/index.ts
index 8389c22..84e73b5 100644
--- a/src/pages/index.ts
+++ b/src/pages/index.ts
@@ -5,6 +5,7 @@ import Window from '../ts/neutralino/Window';
 import Downloader from '../ts/Downloader';
 import Archive from '../ts/Archive';
 import Configs from '../ts/Configs';
+import Runners from '../ts/Runners';
 
 const app = Vue.createApp({
     data: () => ({
@@ -18,6 +19,21 @@ const app = Vue.createApp({
     mounted: () => {
         Window.current.show();
 
+        /*Downloader.download('https://github.com/GloriousEggroll/wine-ge-custom/releases/download/6.20-GE-1/wine-lutris-ge-6.20-1-x86_64.tar.xz', '123.tar.xz').then((stream) => {
+            stream.start(() => console.log('Downloading started'));
+            stream.finish(() => console.log('Downloading finished'));
+
+            stream.progress((current, total) => console.log(`${Math.round(current / total * 100)}%`));
+        });*/
+
+        /*Runners.download('wine-lutris-ge-6.20-1-x86_64').then((stream) => {
+            stream.downloadStart(() => console.log('Downloading started'));
+            stream.downloadFinish(() => console.log('Downloading finished'));
+
+            stream.unpackStart(() => console.log('Unpacking started'));
+            stream.unpackFinish(() => console.log('Unpacking finished'));
+        });*/
+
         /*Archive.unpack('Audio_English(US)_2.3.0.zip', 'tmp').then((stream) => {
             stream.progress((current, total) => {
                 console.log(`${Math.round(current / total * 100)}%`);
diff --git a/src/ts/Archive.ts b/src/ts/Archive.ts
index da7bfd3..3790679 100644
--- a/src/ts/Archive.ts
+++ b/src/ts/Archive.ts
@@ -18,10 +18,12 @@ class Stream
 
     protected archive: ArchiveInfo;
 
+    protected onStart?: () => void;
     protected onProgress?: (current: number, total: number, difference: number) => void;
     protected onFinish?: () => void;
     protected onError?: () => void;
 
+    protected started: boolean = false;
     protected finished: boolean = false;
     protected throwedError: boolean = false;
 
@@ -33,6 +35,10 @@ class Stream
     {
         this.path = path;
         this.unpackDir = unpackDir;
+        this.started = true;
+
+        if (this.onStart)
+            this.onStart();
 
         Archive.getInfo(path).then((info) => {
             if (info === null)
@@ -102,6 +108,19 @@ class Stream
         });
     }
 
+    /**
+     * Specify event that will be called when the unpacking will be started
+     * 
+     * @param callback
+     */
+    public start(callback: () => void)
+    {
+        this.onStart = callback;
+
+        if (this.started)
+            callback();
+    }
+
     /**
      * Specify event that will be called every [this.progressInterval] ms during archive unpacking
      * 
diff --git a/src/ts/Downloader.ts b/src/ts/Downloader.ts
index 440df77..10f36de 100644
--- a/src/ts/Downloader.ts
+++ b/src/ts/Downloader.ts
@@ -9,15 +9,21 @@ class Stream
     protected total: number;
     protected previous: number = 0;
 
+    protected onStart?: () => void;
     protected onProgress?: (current: number, total: number, difference: number) => void;
     protected onFinish?: () => void;
 
+    protected started: boolean = false;
     protected finished: boolean = false;
 
     public constructor(uri: string, output: string, total: number)
     {
         this.uri = uri;
         this.total = total;
+        this.started = true;
+
+        if (this.onStart)
+            this.onStart();
 
         // @ts-expect-error
         Neutralino.os.execCommand(`curl -s -L -N -o "${output}" "${uri}"`, {
@@ -51,6 +57,19 @@ class Stream
         setTimeout(updateProgress, this.progressInterval);
     }
 
+    /**
+     * Specify event that will be called when the downloading will be started
+     * 
+     * @param callback
+     */
+    public start(callback: () => void)
+    {
+        this.onStart = callback;
+
+        if (this.started)
+            callback();
+    }
+
     /**
      * Specify event that will be called every [this.progressInterval] ms during the file downloading
      * 
@@ -96,13 +115,16 @@ export default class Downloader
 
             else statsRaw = statsRaw.stdOut;
 
-            const length = parseInt(/content-length: ([\d]+)/i.exec(statsRaw)[1]);
+            let length = 0;
+
+            for (const match of statsRaw.matchAll(/content-length: ([\d]+)/gi))
+                length = match[1];
 
             resolve(new Stream(uri, output ?? this.fileFromUri(uri), length));
         });
     }
 
-    protected static fileFromUri(uri: string): string
+    public static fileFromUri(uri: string): string
     {
         const file = uri.split('/').pop().split('#')[0].split('?')[0];
 
diff --git a/src/ts/Runners.ts b/src/ts/Runners.ts
index 7cd94ec..88b0b22 100644
--- a/src/ts/Runners.ts
+++ b/src/ts/Runners.ts
@@ -1,9 +1,168 @@
-import type {
+import {
     Runner,
     RunnerFamily
 } from './types/Runners';
 
-import constants from './Constants';
+import Constants from './Constants';
+import Downloader from './Downloader';
+import Archive from './Archive';
+
+class Stream
+{
+    /**
+     * The interval in ms between progress event calls
+     */
+    public downloadProgressInterval: number = 200;
+
+    /**
+     * The interval in ms between progress event calls
+     */
+    public unpackProgressInterval: number = 500;
+
+    protected onDownloadStart?: () => void;
+    protected onUnpackStart?: () => void;
+
+    protected onDownloadProgress?: (current: number, total: number, difference: number) => void;
+    protected onUnpackProgress?: (current: number, total: number, difference: number) => void;
+
+    protected onDownloadFinish?: () => void;
+    protected onUnpackFinish?: () => void;
+
+    protected downloadStarted: boolean = false;
+    protected unpackStarted: boolean = false;
+
+    protected downloadFinished: boolean = false;
+    protected unpackFinished: boolean = false;
+
+    public constructor(runner: Runner)
+    {
+        Constants.paths.launcher.then((launcherDir) => {
+            const archivePath = `${launcherDir}/${Downloader.fileFromUri(runner.uri)}`;
+
+            // Download archive
+            Downloader.download(runner.uri, archivePath).then((stream) => {
+                stream.progressInterval = this.downloadProgressInterval;
+
+                stream.start(() => {
+                    this.downloadStarted = true;
+
+                    if (this.onDownloadStart)
+                        this.onDownloadStart();
+                });
+
+                stream.progress((current, total, difference) => {
+                    if (this.onDownloadProgress)
+                        this.onDownloadProgress(current, total, difference);
+                });
+
+                stream.finish(() => {
+                    this.downloadFinished = true;
+
+                    if (this.onDownloadFinish)
+                        this.onDownloadFinish();
+
+                    // And then unpack it
+                    Constants.paths.runners.then((runners) => {
+                        Archive.unpack(archivePath, runners).then((stream) => {
+                            stream.progressInterval = this.unpackProgressInterval;
+
+                            stream.start(() => {
+                                this.unpackStarted = true;
+            
+                                if (this.onUnpackStart)
+                                    this.onUnpackStart();
+                            });
+
+                            stream.progress((current, total, difference) => {
+                                if (this.onUnpackProgress)
+                                    this.onUnpackProgress(current, total, difference);
+                            });
+
+                            stream.finish(() => {
+                                this.unpackFinished = true;
+
+                                if (this.onUnpackFinish)
+                                    this.onUnpackFinish();
+                            });
+                        });
+                    });
+                });
+            });
+        });
+    }
+
+    /**
+     * Specify event that will be called after the runner will begin downloading
+     * 
+     * @param callback
+     */
+    public downloadStart(callback: () => void)
+    {
+        this.onDownloadStart = callback;
+
+        if (this.downloadStarted)
+            callback();
+    }
+
+    /**
+     * Specify event that will be called after the runner will begin unpacking
+     * 
+     * @param callback
+     */
+    public unpackStart(callback: () => void)
+    {
+        this.onUnpackStart = callback;
+
+        if (this.unpackStarted)
+            callback();
+    }
+
+    /**
+     * Specify event that will be called every [this.downloadProgressInterval] ms during the runner downloading
+     * 
+     * @param callback
+     */
+    public downloadProgress(callback: (current: number, total: number, difference: number) => void)
+    {
+        this.onDownloadProgress = callback;
+    }
+
+    /**
+     * Specify event that will be called every [this.unpackProgressInterval] ms during the runner unpacking
+     * 
+     * @param callback
+     */
+    public unpackProgress(callback: (current: number, total: number, difference: number) => void)
+    {
+        this.onUnpackProgress = callback;
+    }
+
+    /**
+     * Specify event that will be called after the runner will be downloaded
+     * 
+     * @param callback
+     */
+    public downloadFinish(callback: () => void)
+    {
+        this.onDownloadFinish = callback;
+
+        if (this.downloadFinished)
+            callback();
+    }
+
+    /**
+     * Specify event that will be called after the runner will be unpacked
+     * 
+     * @param callback
+     */
+    public unpackFinish(callback: () => void)
+    {
+        this.onUnpackFinish = callback;
+
+        if (this.unpackFinished)
+            callback();
+    }
+}
 
 class Runners
 {
@@ -12,17 +171,17 @@ class Runners
      * 
      * @returns Promise<Runner[]>
      */
-    public static get(): Promise<Runner[]>
+    public static get(): Promise<RunnerFamily[]>
     {
         return new Promise((resolve) => {
-            constants.paths.runners.then(async (runnersDir: string) => {
+            Constants.paths.runners.then(async (runnersDir: string) => {
                 // @ts-expect-error
-                let list: RunnerFamily[] = JSON.parse(await Neutralino.filesystem.readFile(`${constants.dirs.app}/public/runners.json`));
+                let list: RunnerFamily[] = JSON.parse(await Neutralino.filesystem.readFile(`${Constants.paths.app}/public/runners.json`));
 
                 // @ts-expect-error
                 const installed: { entry: string, type: string }[] = await Neutralino.filesystem.readDirectory(runnersDir);
 
-                let runners = [];
+                let runners: RunnerFamily[] = [];
 
                 list.forEach((family) => {
                     let newFamily: RunnerFamily = {
@@ -51,16 +210,35 @@ class Runners
         });
     }
 
-    public static download(runner: Runner|Runner['name']): Promise<boolean>
+    public static download(runner: Runner|Runner['name']): Promise<null|Stream>
     {
-        return new Promise((resolve) => {
-            
+        return new Promise(async (resolve) => {
+            // If we provided runner property as a name of the runner
+            // then we should find this runner and call this method from it
+            if (typeof runner == 'string')
+            {
+                let foundRunner = null;
+
+                (await this.get()).forEach((family) => {
+                    family.runners.forEach((familyRunner) => {
+                        if (familyRunner.name == runner)
+                            foundRunner = familyRunner;
+                    });
+                });
+
+                resolve(foundRunner === null ? null : new Stream(foundRunner));
+            }
+
+            // Otherwise we can use runner.uri and so on to download runner
+            else resolve(new Stream(runner));
         });
     }
 }
 
 export default Runners;
 
+export { Stream };
+
 export type {
     Runner,
     RunnerFamily