Forráskód Böngészése

Merge branch 'APIv2'

Iñigo Valentin 6 éve
szülő
commit
de063587fd

+ 1 - 0
.gitignore

@@ -7,6 +7,7 @@ data/*.sqlite
 .php_pid
 .php_pid
 *.log
 *.log
 doc/.tmp
 doc/.tmp
+application/API/v2/bin/__pycache__/
 
 
 #IDE / OS files
 #IDE / OS files
 .kateproject.d/
 .kateproject.d/

+ 49 - 0
application/API/v1/API_Controller.php

@@ -0,0 +1,49 @@
+<?php
+
+    /**
+     * v1 API Controller file.
+     *
+     * Provides a class to handle all posible API requests.
+     * 
+     * @category Constroller
+     */
+
+    /**
+     * v1 API controller.
+     *
+     * Handles every API request.
+     *
+     * @category Controller
+     */
+    class API_Controller {
+
+        /**
+         * Constructor.
+         *
+         * Handles every request, creating the required models and selecting the
+         * view.
+         *
+         * @param mixed[] $params GET parameters of the request.
+         */
+        public function __construct($params){
+
+            // API call: Use API controller
+            if (count($params) == 0){
+                require_once(__DIR__ . "main.php");
+            }
+            else{
+                $command = strtoupper$pars[1];
+                switch ($command){
+                    case "log-profile":
+                        require_once(__DIR__ . "log-profile/index.php");
+                        break;
+                    case "log-run":
+                        require_once(__DIR__ . "log-run/index.php");
+                        break;
+                    default:
+                        header("HTTP/1.1 404");
+                }
+            }
+        }
+    }
+?>

+ 0 - 0
application/bin/log-profile.py → application/API/v1/bin/log-profile.py


+ 0 - 0
application/bin/log-run.py → application/API/v1/bin/log-run.py


+ 54 - 0
application/API/v1/log-profile/index.php

@@ -0,0 +1,54 @@
+<?php
+    /**
+     * Profile uploader script.
+     *
+     * Exposes an API to save a run to the database.
+     * Reads post data and calls the log-profile.py script.
+     * It also saves the data to a JSON file in the da directory.
+     * Mandatory POST parameters are:
+     *  - data: Received JSON file after a run.
+     *  - key: User API key.
+     *
+     * @category API
+     */
+
+    $status = 200;
+    if (isset($_POST["data"]) && isset($_POST["key"])){
+        $data = $_POST["data"];
+        $key = $_POST["key"];
+        if (json_decode($data) === null){
+            error_log("ERROR: API v1 log-profile: Invalid JSON");
+            $status = 400;
+        }
+        else{
+            $json = json_decode($data);
+            $winfo = $json->{"wizard_info"};
+            $uname = $winfo->{"wizard_name"};
+            $uid = $winfo->{"wizard_id"};
+            $dtime = (new DateTime())->format('Y-m-dTH:i:s');
+            $fname = ($_SERVER["DOCUMENT_ROOT"] . "/../data/profile_" . $uid . "_" . $uname . "_" . $dtime . ".json");
+            file_put_contents($fname, $data);
+            $cmd =  __DIR__ . "../bin/log-profile.py " . $key . " " . $fname;
+            $out = [];
+            $ret = 0;
+            exec($cmd, $out, $ret);
+            if ($ret != 0){
+                error_log("ERROR: API v1 log-profile exited with status: " . $ret);
+                error_log("====================");
+                error_log("|| SCRIPT OUTPUT: ||");
+                error_log("===============================================================================");
+                foreach($out as $line){
+                    error_log("|| " . $line);
+                }
+                error_log("===============================================================================");
+                $status = 400;
+            }
+        }
+    }
+    else{
+        error_log("ERROR: API v1 log-profile: Invalid JSON");
+        $status = 400;
+    }
+    http_response_code($status);
+    return $status;
+?>

+ 46 - 0
application/API/v1/log-run/index.php

@@ -0,0 +1,46 @@
+<?php
+    /**
+     * Run logger script.
+     *
+     * Exposes an API to save a run to the database.
+     * Reads post data and calls the log-run.py script.
+     * Mandatory POST parameters are:
+     *  - data: Received JSON file after a run.
+     *  - key: User API key.
+     *
+     * @category API
+     */
+
+    $status = 200;
+    if (isset($_POST["data"]) && isset($_POST["key"])){
+        $data = $_POST["data"];
+        $key = $_POST["key"];
+        if (json_decode($data) === null){
+            error_log("ERROR: API v1 log-run: Invalid JSON");
+            $status = 400;
+        }
+        else{
+            $cmd = __DIR__ . "../bin/log-run.py " . $key . " " . escapeshellarg($data);
+            $out = [];
+            $ret = 0;
+            exec($cmd, $out, $ret);
+            if ($ret != 0){
+                error_log("ERROR: API v1 log-run exited with status: " . $ret);
+                error_log("====================");
+                error_log("|| SCRIPT OUTPUT: ||");
+                error_log("===============================================================================");
+                foreach($out as $line){
+                    error_log("|| " . $line);
+                }
+                error_log("===============================================================================");
+                $status = 400;
+            }
+        }
+    }
+    else{
+        error_log("ERROR: API v1 log-run: Invalid JSON");
+        $status = 400;
+    }
+    http_response_code($status);
+    return $status;
+?>

+ 65 - 0
application/API/v2/API_Controller.php

@@ -0,0 +1,65 @@
+<?php
+
+    /**
+     * v2 API Controller file.
+     *
+     * Provides a class to handle all posible API requests.
+     * 
+     * @category Constroller
+     */
+
+    /**
+     * v2 API controller.
+     *
+     * Handles every API request.
+     *
+     * @category Controller
+     */
+    class API_Controller {
+
+        /**
+         * Constructor.
+         *
+         * Handles every request, creating the required models and selecting the
+         * view.
+         *
+         * @param mixed[] $params GET parameters of the request.
+         */
+        public function __construct($params){
+            array_shift($params);
+            array_shift($params);
+            array_shift($params);
+            // API call: Use API controller
+            if (count($params) == 0){
+                require_once(__DIR__ . "main.php");
+            }
+            else{
+                $command = strtolower($params[0]);
+                switch ($command){
+                    case "help":
+                        require_once(__DIR__ . "/help/index.php");
+                    case "upload_profile":
+                        require_once(__DIR__ . "/upload_profile.php");
+                        break;
+                    case "upload_run_dungeon":
+                        require_once(__DIR__ . "/upload_run_dungeon.php");
+                        break;
+                    case "upload_run_dimension":
+                        require_once(__DIR__ . "/upload_run_dimension.php");
+                        break;
+                    case "upload_run_scenario":
+                        require_once(__DIR__ . "/upload_run_scenario.php");
+                        break;
+                    case "upload_run_toa":
+                        require_once(__DIR__ . "/upload_run_toa.php");
+                        break;
+                    case "units":
+                        require_once(__DIR__ . "/units.php");
+                        break;
+                    default:
+                        header("HTTP/1.1 404");
+                }
+            }
+        }
+    }
+?>

+ 382 - 0
application/API/v2/bin/MAPPING.py

@@ -0,0 +1,382 @@
+import json
+import math
+
+MAINSTAT = json.loads("""
+{
+  "mainstat": {
+    "1": {
+      "max": {
+        "1": 804,
+        "2": 1092,
+        "3": 1380,
+        "4": 1704,
+        "5": 2088,
+        "6": 2448
+      }
+    },
+    "2": {
+      "max": {
+        "1": 18,
+        "2": 20,
+        "3": 38,
+        "4": 43,
+        "5": 51,
+        "6": 63
+      }
+    },
+    "3": {
+      "max": {
+        "1": 54,
+        "2": 74,
+        "3": 93,
+        "4": 113,
+        "5": 135,
+        "6": 160
+      }
+    },
+    "4": {
+      "max": {
+        "1": 18,
+        "2": 20,
+        "3": 38,
+        "4": 43,
+        "5": 51,
+        "6": 63
+      }
+    },
+    "5": {
+      "max": {
+        "1": 54,
+        "2": 74,
+        "3": 93,
+        "4": 113,
+        "5": 135,
+        "6": 160
+      }
+    },
+    "6": {
+      "max": {
+        "1": 18,
+        "2": 20,
+        "3": 38,
+        "4": 43,
+        "5": 51,
+        "6": 63
+      }
+    },
+    "8": {
+      "max": {
+        "1": 18,
+        "2": 19,
+        "3": 25,
+        "4": 30,
+        "5": 39,
+        "6": 42
+      }
+    },
+    "9": {
+      "max": {
+        "1": 18,
+        "2": 20,
+        "3": 37,
+        "4": 41,
+        "5": 47,
+        "6": 58
+      }
+    },
+    "10": {
+      "max": {
+        "1": 20,
+        "2": 37,
+        "3": 43,
+        "4": 58,
+        "5": 65,
+        "6": 80
+      }
+    },
+    "11": {
+      "max": {
+        "1": 18,
+        "2": 20,
+        "3": 38,
+        "4": 44,
+        "5": 51,
+        "6": 64
+      }
+    },
+    "12": {
+      "max": {
+        "1": 18,
+        "2": 20,
+        "3": 38,
+        "4": 44,
+        "5": 51,
+        "6": 64
+      }
+    }
+  },
+  "substat": {
+    "1": {
+      "max": {
+        "1": 300,
+        "2": 525,
+        "3": 825,
+        "4": 1125,
+        "5": 1500,
+        "6": 1875
+      }
+    },
+    "2": {
+      "max": {
+        "1": 10,
+        "2": 15,
+        "3": 25,
+        "4": 30,
+        "5": 35,
+        "6": 40
+      }
+    },
+    "3": {
+      "max": {
+        "1": 20,
+        "2": 25,
+        "3": 40,
+        "4": 50,
+        "5": 75,
+        "6": 100
+      }
+    },
+    "4": {
+      "max": {
+        "1": 10,
+        "2": 15,
+        "3": 25,
+        "4": 30,
+        "5": 35,
+        "6": 40
+      }
+    },
+    "5": {
+      "max": {
+        "1": 20,
+        "2": 25,
+        "3": 40,
+        "4": 50,
+        "5": 75,
+        "6": 100
+      }
+    },
+    "6": {
+      "max": {
+        "1": 10,
+        "2": 15,
+        "3": 25,
+        "4": 30,
+        "5": 35,
+        "6": 40
+      }
+    },
+    "8": {
+      "max": {
+        "1": 5,
+        "2": 10,
+        "3": 15,
+        "4": 20,
+        "5": 25,
+        "6": 30
+      }
+    },
+    "9": {
+      "max": {
+        "1": 5,
+        "2": 10,
+        "3": 15,
+        "4": 20,
+        "5": 25,
+        "6": 30
+      }
+    },
+    "10": {
+      "max": {
+        "1": 10,
+        "2": 15,
+        "3": 20,
+        "4": 25,
+        "5": 25,
+        "6": 35
+      }
+    },
+    "11": {
+      "max": {
+        "1": 10,
+        "2": 15,
+        "3": 20,
+        "4": 25,
+        "5": 35,
+        "6": 40
+      }
+    },
+    "12": {
+      "max": {
+        "1": 10,
+        "2": 15,
+        "3": 20,
+        "4": 25,
+        "5": 35,
+        "6": 40
+      }
+    }
+  }
+}
+""")
+
+SUBSTAT = json.loads("""
+{
+  "substat": {
+    "1": {
+      "max": {
+        "1": 300,
+        "2": 525,
+        "3": 825,
+        "4": 1125,
+        "5": 1500,
+        "6": 1875
+      }
+    },
+    "2": {
+      "max": {
+        "1": 10,
+        "2": 15,
+        "3": 25,
+        "4": 30,
+        "5": 35,
+        "6": 40
+      }
+    },
+    "3": {
+      "max": {
+        "1": 20,
+        "2": 25,
+        "3": 40,
+        "4": 50,
+        "5": 75,
+        "6": 100
+      }
+    },
+    "4": {
+      "max": {
+        "1": 10,
+        "2": 15,
+        "3": 25,
+        "4": 30,
+        "5": 35,
+        "6": 40
+      }
+    },
+    "5": {
+      "max": {
+        "1": 20,
+        "2": 25,
+        "3": 40,
+        "4": 50,
+        "5": 75,
+        "6": 100
+      }
+    },
+    "6": {
+      "max": {
+        "1": 10,
+        "2": 15,
+        "3": 25,
+        "4": 30,
+        "5": 35,
+        "6": 40
+      }
+    },
+    "8": {
+      "max": {
+        "1": 5,
+        "2": 10,
+        "3": 15,
+        "4": 20,
+        "5": 25,
+        "6": 30
+      }
+    },
+    "9": {
+      "max": {
+        "1": 5,
+        "2": 10,
+        "3": 15,
+        "4": 20,
+        "5": 25,
+        "6": 30
+      }
+    },
+    "10": {
+      "max": {
+        "1": 10,
+        "2": 15,
+        "3": 20,
+        "4": 25,
+        "5": 25,
+        "6": 35
+      }
+    },
+    "11": {
+      "max": {
+        "1": 10,
+        "2": 15,
+        "3": 20,
+        "4": 25,
+        "5": 35,
+        "6": 40
+      }
+    },
+    "12": {
+      "max": {
+        "1": 10,
+        "2": 15,
+        "3": 20,
+        "4": 25,
+        "5": 35,
+        "6": 40
+      }
+    }
+  }
+}
+""")
+
+"""
+Calculates a rune current and max efficiency.
+
+:param rune: JSON woth rune info.
+:return: current, max efficiency.
+"""
+def calculate_efficiency(rune):
+    #getRuneEfficiency(rune, toFixed = 2) {
+    ratio = 0.0;
+
+    # Get actual stars (ancient are +10)
+    r_class = rune["class"]
+    if r_class > 10:
+        r_class -= 10
+    r_class = str(r_class)
+
+    # Main stat
+    ratio += MAINSTAT["mainstat"][str(rune["pri_eff"][0])]["max"][r_class] / MAINSTAT["mainstat"][str(rune["pri_eff"][0])]["max"]["6"]
+
+    # Innate stat
+    if (rune["prefix_eff"] and rune["prefix_eff"][0] > 0):
+      ratio += rune["prefix_eff"][1] / SUBSTAT["substat"][str(rune["prefix_eff"][0])]["max"]["6"];
+
+    # Sub stats
+    for substat in rune["sec_eff"]:
+        value = substat[1]
+        if "3" in substat and substat[3] > 0:
+            value += substat[3]
+        ratio += (value / SUBSTAT["substat"][str(substat[0])]["max"]["6"])
+
+    efficiency = (ratio / 2.8) * 100;
+
+    return ((ratio / 2.8) * 100), (efficiency + ((max(math.ceil((12 - rune["upgrade_curr"]) / 3.0), 0) * 0.2) / 2.8) * 100)

+ 1155 - 0
application/API/v2/bin/upload_profile.py

@@ -0,0 +1,1155 @@
+#!/usr/bin/python3
+
+import sys
+import os
+import sqlite3
+import json
+import math
+
+"""
+Reads the API KEY, that must be passed as first command line argument.
+
+:returns: Recovered API KEY.
+:raises Exception: Th KEY couldn't be red.
+"""
+def readKey():
+    try:
+        key = sys.argv[1]
+        return key
+    except Exception as e:
+        print("Error parsing API KEY: " + str(e))
+        raise
+
+"""
+Reads the JSON data, that must be passed as second command line argument.
+
+:returns: Recovered data, in JSON format.
+:raises Exception: The data couldn't be red or converted to JSON.
+"""
+def readData():
+    try:
+        #print(sys.argv[1])
+        #data = json.loads(sys.argv[1])
+        with open(sys.argv[2], 'r') as f:
+            content = f.read()
+        data = json.loads(content)
+        return data
+    except Exception as e:
+        print("Error parsing data: " + str(e))
+        raise
+
+"""
+Verifies that the API key matches the player data and that it exists in th DB.
+
+:param db: Connection to the database.
+:returns: Connection to the database.
+:param data: Data in json format.
+:param key: API KEY.
+:returns: True if key and player match, False otherwise.
+:raises IntegrityError: The queryes couldn't bre executed.
+"""
+def verifyKey(db, data, key):
+    print('Verifying KEY...')
+    status = False
+    try:
+        uid = data["wizard_info"]["wizard_id"]
+        cursor = db.cursor()
+        cursor.execute('SELECT count(uid) AS c FROM player WHERE uid = ? AND api_key = ?;', (uid, key))
+        if cursor.fetchone()[0] == 1:
+            status = True
+        cursor.close()
+    except sqlite3.IntegrityError as e:
+        print("Error executing statement: " + str(e))
+        raise
+    return status
+
+"""
+Opens the database file and cleans the user tables.
+
+It also disables referencial integrity.
+
+:param name: The path to the sqlite database.
+:returns: Connection to the database.
+:raises IntegrityError: The queryes couldn't bre executed.
+:raises IOError: The sqlite file couldn't be created.
+"""
+def openDatabase():
+    kdb = os.path.dirname(os.path.realpath(sys.argv[0])) + '/../../../sw.sqlite'
+    udb = os.path.dirname(os.path.realpath(sys.argv[0])) + '/../../../../data/sw.sqlite'
+    print('Configuring database...')
+    try:
+        db = sqlite3.connect(kdb)
+        cursor = db.cursor()
+        cursor.execute("attach '" + udb + "' as data;")
+        cursor.close()
+        return db
+    except sqlite3.IntegrityError as e:
+        print("Error executing statement: " + str(e))
+        raise
+    except IOError as e:
+        print("I/O Error creating database " + name + ": " + str(e))
+        raise
+
+"""
+Clears the user tables.
+
+It also disables referencial integrity.
+
+:param db: Sqlite database connection.
+:param data: Data in json format.
+:raises IntegrityError: The queryes couldn't bre executed.
+"""
+def clearData(db, data):
+    print('Clearing previous data...')
+    try:
+        uid = str(data["wizard_info"]["wizard_id"])
+        cursor = db.cursor()
+        cursor.execute('PRAGMA foreign_keys = OFF;')
+        cursor.execute('DELETE FROM scenario WHERE uid = ?', [uid])
+        cursor.execute('DELETE FROM defense WHERE uid = ?', [uid])
+        cursor.execute('DELETE FROM unit_skill WHERE unit IN (SELECT id FROM unit WHERE uid = ?)', [uid])
+        cursor.execute('DELETE FROM unit WHERE uid = ?', [uid])
+        cursor.execute('DELETE FROM rune WHERE uid = ?', [uid])
+        cursor.execute('DELETE FROM building WHERE uid = ?', [uid])
+        cursor.execute('DELETE FROM decoration WHERE uid = ?', [uid])
+        cursor.execute('DELETE FROM inventory WHERE uid = ?', [uid])
+        cursor.execute('DELETE FROM summon_special') # For every player, will be reloaded.
+        cursor.execute('DELETE FROM rune_craft WHERE uid = ?', [uid])
+        # 'guild' and 'guild_member' tables are cleared before inserting.
+        db.commit()
+        cursor.close();
+    except sqlite3.IntegrityError as e:
+        print("Error executing statement: " + str(e))
+        raise
+    return
+
+
+"""
+Closes the database.
+
+Before doing so, it also disables referencial integrity.
+
+:param db: Connection to the database.
+:returns: Connection to the database.
+:raises IntegrityError: The queryes couldn't bre executed.
+"""
+def closeDatabase(name):
+    print('Closing database connection...')
+    try:
+        cursor = db.cursor()
+        cursor.execute('PRAGMA foreign_keys = ON;')
+        cursor.close()
+        db.commit()
+        db.close()
+    except sqlite3.IntegrityError as e:
+        print("Error executing statement: " + str(e))
+        raise
+
+"""
+Inserts a row into the database.
+
+:param db: Connection to the database.
+:param table: Name of the table to insert into.
+:param values: List of values to insert.
+:raises IntegrityError: The insert query was unsuccesfull.
+"""
+def insert(db, table, values):
+    cursor = db.cursor()
+    db.execute('PRAGMA foreign_keys = OFF;')
+    placeholders = ''
+    for x in range(0, len(values)):
+        placeholders = placeholders + '?, '
+    placeholders = placeholders[:len(placeholders) - 2]
+    query = 'INSERT INTO ' + table + ' VALUES (' + placeholders + ')'
+    try:
+        cursor.execute(query, values)
+    except sqlite3.IntegrityError as e:
+        print('Error Inserting into ' + table + ' with values (' + str(values) + '): ' + str(e))
+        raise
+    cursor.close;
+
+"""
+Parses player (table player).
+
+:param db: Sqlite database connection.
+:param data: Data in json format.
+"""
+def parsePlayer(db, data):
+    print("Parsing player...")
+    cursor = db.cursor()
+    uid = data["wizard_info"]["wizard_id"]
+    name = data["wizard_info"]["wizard_name"]
+    # TODO: Generate data
+    mail = data["wizard_info"]["wizard_name"]
+    password = data["wizard_info"]["wizard_name"]
+    api_key = data["wizard_info"]["wizard_name"]
+    mana = data["wizard_info"]["wizard_mana"]
+    crystal = data["wizard_info"]["wizard_crystal"]
+    country = data["wizard_info"]["wizard_last_country"]
+    lang = data["wizard_info"]["wizard_last_lang"]
+    level = data["wizard_info"]["wizard_level"]
+    experience = data["wizard_info"]["experience"]
+    energy = data["wizard_info"]["wizard_energy"]
+    energy_max = data["wizard_info"]["energy_max"]
+    energy_per_min = data["wizard_info"]["energy_per_min"]
+    arena_energy = data["wizard_info"]["arena_energy"]
+    arena_energy_max = data["wizard_info"]["arena_energy_max"]
+    rep = data["wizard_info"]["rep_unit_id"]
+    social_point = data["wizard_info"]["social_point_current"]
+    honor_point = data["wizard_info"]["honor_point"]
+    guild_point = data["wizard_info"]["guild_point"]
+    darkportal_energy = data["wizard_info"]["darkportal_energy"]
+    darkportal_energy_max = data["wizard_info"]["darkportal_energy_max"]
+    dimension_energy = data["dimension_hole_info"]["energy"]
+    dimension_energy_max = data["dimension_hole_info"]["energy_max"]
+    costume_point = data["wizard_info"]["costume_point"]
+    costume_point_max = data["wizard_info"]["costume_point_max"]
+    honor_medal = data["wizard_info"]["honor_medal"]
+    honor_mark = data["wizard_info"]["honor_mark"]
+    event_coin = data["wizard_info"]["event_coin"]
+    storage_slots = data["unit_depository_slots"]["number"]
+    island_upgrade = 0
+    for island in data["island_info"]:
+        if island["id"] <= 7 and island["open"] == 1:
+            island_upgrade = island["id"]
+    query = '''
+        UPDATE player
+        SET mana = ?,
+            crystal = ?,
+            country = ?,
+            lang = ?,
+            level = ?,
+            experience = ?,
+            energy = ?,
+            energy_max = ?,
+            energy_per_min = ?,
+            arena_energy = ?,
+            arena_energy_max = ?,
+            rep = ?,
+            social_point = ?,
+            honor_point = ?,
+            guild_point = ?,
+            darkportal_energy = ?,
+            darkportal_energy_max = ?,
+            dimension_energy = ?,
+            dimension_energy_max = ?,
+            costume_point = ?,
+            costume_point_max = ?,
+            honor_medal = ?,
+            honor_medal = ?,
+            event_coin = ?,
+            storage_slots = ?,
+            island = ?
+        WHERE uid = ?;
+    '''
+    values = (
+        mana,                 crystal,               country,
+        lang,                 level,                 experience,
+        energy,               energy_max,            energy_per_min,
+        arena_energy,         arena_energy_max,      rep,
+        social_point,         honor_point,           guild_point,
+        darkportal_energy,    darkportal_energy_max, dimension_energy,
+        dimension_energy_max, costume_point,         costume_point_max,
+        honor_medal,          honor_medal,           event_coin,
+        storage_slots,        island_upgrade,        uid
+    )
+    try:
+        cursor.execute(query, values)
+        db.commit()
+    except sqlite3.IntegrityError as e:
+        print('Error updating player table with: ' + str(query) + ' <== ' + str(values) + ' || Error message:' +  str(e))
+        raise
+
+"""
+Parses scenarios (table scenario).
+
+:param db: Sqlite database connection.
+:param data: Data in json format.
+"""
+def parseScenarios(db, data):
+    print("Parsing scenarios...")
+    uid = data["wizard_info"]["wizard_id"]
+    for sce in data["scenario_list"]:
+        region = sce["region_id"]
+        difficulty = sce["difficulty"]
+        cleared = sce["cleared"]
+        max_cleared = 0
+        for stage in sce["stage_list"]:
+            if stage["cleared"] == 1:
+                max_cleared = stage["stage_no"]
+        insert(db, "scenario", (uid, region, difficulty, cleared, max_cleared))
+    db.commit()
+
+"""
+Parses arena defense units (table defense).
+
+:param db: Sqlite database connection.
+:param data: Data in json format.
+"""
+def parseDefense(db, data):
+    print("Parsing arena defense...")
+    uid = data["wizard_info"]["wizard_id"]
+    for defense in data["defense_unit_list"]:
+        unit = defense["unit_id"]
+        position = defense["pos_id"]
+        insert(db, "defense", (uid, unit, position))
+    db.commit()
+
+"""
+Parses buildings (table building).
+
+:param db: Sqlite database connection.
+:param data: Data in json format.
+"""
+def parseBuildings(db, data):
+    print("Parsing buildings...")
+    uid = data["wizard_info"]["wizard_id"]
+    for bui in data["building_list"]:
+        id = bui["building_id"]
+        building = bui["building_master_id"]
+        gain = bui["gain_per_hour"]
+        insert(db, "building", (uid, id, building, gain))
+    db.commit()
+
+"""
+Parses decoration buildings (table dcoration).
+
+:param db: Sqlite database connection.
+:param data: Data in json format.
+"""
+def parseDecorations(db, data):
+    print("Parsing decorations...")
+    uid = data["wizard_info"]["wizard_id"]
+    for bui in data["deco_list"]:
+        id = bui["deco_id"]
+        building = bui["master_id"]
+        level = bui["level"]
+        insert(db, "decoration", (uid, id, building, level))
+    db.commit()
+
+"""
+Parses unit data (tables unit, unit_skill).
+
+:param db: Sqlite database connection.
+:param data: Data in json format.
+"""
+def parseUnits(db, data):
+    print("Parsing monsters...")
+    uid = data["wizard_info"]["wizard_id"]
+    lock_list = data["unit_lock_list"]
+    for mon in data["unit_list"]:
+        id = mon["unit_id"]
+        building = mon["building_id"]
+        monster = mon["unit_master_id"]
+        level = mon["unit_level"]
+        stars = mon["class"]
+        hp = mon["con"] * 15 # Always x15
+        attack = mon["atk"]
+        defense = mon["def"]
+        speed = mon["spd"]
+        crit_rate = mon["critical_rate"]
+        crit_damage = mon["critical_damage"]
+        resistance = mon["resist"]
+        accuracy = mon["accuracy"]
+        experience = mon["experience"]
+        exp_gained = mon["exp_gained"]
+        exp_gain_rate = mon["exp_gain_rate"]
+        costume = mon["costume_master_id"]
+        source = mon["source"]
+        create_time = mon["create_time"]
+        homunculus_name = mon["homunculus_name"]
+        lock = 0
+        if id in lock_list:
+            lock = 1
+        insert(db, "unit", (uid, id, building, monster, level, stars, hp, attack, defense, speed, crit_rate, crit_damage, resistance, accuracy, experience, exp_gained, exp_gain_rate, costume, source, create_time, homunculus_name, lock))
+        for skill in mon["skills"]:
+            skill_id = skill[0]
+            skill_level = skill[1]
+            insert(db, "unit_skill", (id, skill_id, skill_level))
+    db.commit()
+
+"""
+Parses inventory data (table inventory).
+
+:param db: Sqlite database connection.
+:param data: Data in json format.
+"""
+def parseInventory(db, data):
+    print("Parsing inventory...")
+    uid = data["wizard_info"]["wizard_id"]
+    for item in data["inventory_info"]:
+        id = item["item_master_id"]
+        type = item["item_master_type"]
+        amount = item["item_quantity"]
+        insert(db, "inventory", (uid, id, type, amount))
+    # Fix rune craft item type.
+    cursor = db.cursor()
+    cursor.execute("UPDATE inventory SET type = 27 WHERE type = 29 AND id IN (2001, 4001, 4002, 4003, 9001, 9002, 9003, 8001);")
+    cursor.close()
+
+"""
+Parses summon stone list (table summon_special).
+
+:param db: Sqlite database connection.
+:param data: Data in json format.
+"""
+def parseSummonSpecial(db, data):
+    print("Parsing Summon stone monster list...")
+    uid = data["wizard_info"]["wizard_id"]
+    for unit in data["summon_special_info"]["this"]:
+        insert(db, "summon_special", (0, unit))
+    for unit in data["summon_special_info"]["next"]:
+        insert(db, "summon_special", (1, unit))
+    for unit in data["summon_special_info"]["third"]:
+        insert(db, "summon_special", (2, unit))
+    for unit in data["summon_special_info"]["fourth"]:
+        insert(db, "summon_special", (3, unit))
+
+"""
+Parses rune data (table rune).
+
+:param db: Sqlite database connection.
+:param data: Data in json format.
+"""
+def parseRunes(db, data):
+    print("Parsing runes...")
+    STAT_HP = 1
+    STAT_HP_PCT = 2
+    STAT_ATK = 3
+    STAT_ATK_PCT = 4
+    STAT_DEF = 5
+    STAT_DEF_PCT = 6
+    STAT_SPD = 8
+    STAT_CRIT_RATE_PCT = 9
+    STAT_CRIT_DMG_PCT = 10
+    STAT_RESIST_PCT = 11
+    STAT_ACCURACY_PCT = 12
+    MAIN_STAT_VALUES = {
+        # [stat][stars][level]: value
+        STAT_HP: {
+            1: [40, 85, 130, 175, 220, 265, 310, 355, 400, 445, 490, 535, 580, 625, 670, 804],
+            2: [70, 130, 190, 250, 310, 370, 430, 490, 550, 610, 670, 730, 790, 850, 910, 1092],
+            3: [100, 175, 250, 325, 400, 475, 550, 625, 700, 775, 850, 925, 1000, 1075, 1150, 1380],
+            4: [160, 250, 340, 430, 520, 610, 700, 790, 880, 970, 1060, 1150, 1240, 1330, 1420, 1704],
+            5: [270, 375, 480, 585, 690, 795, 900, 1005, 1110, 1215, 1320, 1425, 1530, 1635, 1740, 2088],
+            6: [360, 480, 600, 720, 840, 960, 1080, 1200, 1320, 1440, 1560, 1680, 1800, 1920, 2040, 2448],
+        },
+        STAT_HP_PCT: {
+            1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18],
+            2: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19],
+            3: [4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 38],
+            4: [5, 7, 9, 11, 13, 16, 18, 20, 22, 24, 27, 29, 31, 33, 36, 43],
+            5: [8, 10, 12, 15, 17, 20, 22, 24, 27, 29, 32, 34, 37, 40, 43, 51],
+            6: [11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 63],
+        },
+        STAT_ATK: {
+            1: [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 54],
+            2: [5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 73],
+            3: [7, 12, 17, 22, 27, 32, 37, 42, 47, 52, 57, 62, 67, 72, 77, 92],
+            4: [10, 16, 22, 28, 34, 40, 46, 52, 58, 64, 70, 76, 82, 88, 94, 112],
+            5: [15, 22, 29, 36, 43, 50, 57, 64, 71, 78, 85, 92, 99, 106, 113, 135],
+            6: [22, 30, 38, 46, 54, 62, 70, 78, 86, 94, 102, 110, 118, 126, 134, 160],
+        },
+        STAT_ATK_PCT: {
+            1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18],
+            2: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19],
+            3: [4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 38],
+            4: [5, 7, 9, 11, 13, 16, 18, 20, 22, 24, 27, 29, 31, 33, 36, 43],
+            5: [8, 10, 12, 15, 17, 20, 22, 24, 27, 29, 32, 34, 37, 40, 43, 51],
+            6: [11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 63],
+        },
+        STAT_DEF: {
+            1: [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 54],
+            2: [5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 73],
+            3: [7, 12, 17, 22, 27, 32, 37, 42, 47, 52, 57, 62, 67, 72, 77, 92],
+            4: [10, 16, 22, 28, 34, 40, 46, 52, 58, 64, 70, 76, 82, 88, 94, 112],
+            5: [15, 22, 29, 36, 43, 50, 57, 64, 71, 78, 85, 92, 99, 106, 113, 135],
+            6: [22, 30, 38, 46, 54, 62, 70, 78, 86, 94, 102, 110, 118, 126, 134, 160],
+        },
+        STAT_DEF_PCT: {
+            1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18],
+            2: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19],
+            3: [4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 38],
+            4: [5, 7, 9, 11, 13, 16, 18, 20, 22, 24, 27, 29, 31, 33, 36, 43],
+            5: [8, 10, 12, 15, 17, 20, 22, 24, 27, 29, 32, 34, 37, 40, 43, 51],
+            6: [11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 63],
+        },
+        STAT_SPD: {
+            1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18],
+            2: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19],
+            3: [3, 4, 5, 6, 8, 9, 10, 12, 13, 14, 16, 17, 18, 19, 21, 25],
+            4: [4, 5, 7, 8, 10, 11, 13, 14, 16, 17, 19, 20, 22, 23, 25, 30],
+            5: [5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 39],
+            6: [7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 42],
+        },
+        STAT_CRIT_RATE_PCT: {
+            1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18],
+            2: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19],
+            3: [3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 37],
+            4: [4, 6, 8, 11, 13, 15, 17, 19, 22, 24, 26, 28, 30, 33, 35, 41],
+            5: [5, 7, 10, 12, 15, 17, 19, 22, 24, 27, 29, 31, 34, 36, 39, 47],
+            6: [7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, 58],
+        },
+        STAT_CRIT_DMG_PCT: {
+            1: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19],
+            2: [3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 37],
+            3: [4, 6, 9, 11, 13, 16, 18, 20, 22, 25, 27, 29, 32, 34, 36, 43],
+            4: [6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 57],
+            5: [8, 11, 15, 18, 21, 25, 28, 31, 34, 38, 41, 44, 48, 51, 54, 65],
+            6: [11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51, 55, 59, 63, 67, 80],
+        },
+        STAT_RESIST_PCT: {
+            1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18],
+            2: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19],
+            3: [4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 38],
+            4: [6, 8, 10, 13, 15, 17, 19, 21, 24, 26, 28, 30, 32, 35, 37, 44],
+            5: [9, 11, 14, 16, 19, 21, 23, 26, 28, 31, 33, 35, 38, 40, 43, 51],
+            6: [12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 64],
+        },
+        STAT_ACCURACY_PCT: {
+            1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18],
+            2: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 19],
+            3: [4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 38],
+            4: [6, 8, 10, 13, 15, 17, 19, 21, 24, 26, 28, 30, 32, 35, 37, 44],
+            5: [9, 11, 14, 16, 19, 21, 23, 26, 28, 31, 33, 35, 38, 40, 43, 51],
+            6: [12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 64],
+        },
+    }
+    SUBSTAT_INCREMENTS = {
+        # [stat][stars]: value
+        # Max possible substat value can be found by multiplying by 5
+        STAT_HP: {
+            1: 60,
+            2: 105,
+            3: 165,
+            4: 225,
+            5: 300,
+            6: 375,
+        },
+        STAT_HP_PCT: {
+            1: 2,
+            2: 3,
+            3: 5,
+            4: 6,
+            5: 7,
+            6: 8,
+        },
+        STAT_ATK: {
+            1: 4,
+            2: 5,
+            3: 8,
+            4: 10,
+            5: 15,
+            6: 20,
+        },
+        STAT_ATK_PCT: {
+            1: 2,
+            2: 3,
+            3: 5,
+            4: 6,
+            5: 7,
+            6: 8,
+        },
+        STAT_DEF: {
+            1: 4,
+            2: 5,
+            3: 8,
+            4: 10,
+            5: 15,
+            6: 20,
+        },
+        STAT_DEF_PCT: {
+            1: 2,
+            2: 3,
+            3: 5,
+            4: 6,
+            5: 7,
+            6: 8,
+        },
+        STAT_SPD: {
+            1: 1,
+            2: 2,
+            3: 3,
+            4: 4,
+            5: 5,
+            6: 6,
+        },
+        STAT_CRIT_RATE_PCT: {
+            1: 1,
+            2: 2,
+            3: 3,
+            4: 4,
+            5: 5,
+            6: 6,
+        },
+        STAT_CRIT_DMG_PCT: {
+            1: 2,
+            2: 3,
+            3: 4,
+            4: 5,
+            5: 6,
+            6: 7,
+        },
+        STAT_RESIST_PCT: {
+            1: 2,
+            2: 3,
+            3: 5,
+            4: 6,
+            5: 7,
+            6: 8,
+        },
+        STAT_ACCURACY_PCT: {
+            1: 2,
+            2: 3,
+            3: 5,
+            4: 6,
+            5: 7,
+            6: 8,
+        },
+    }
+    uid = data["wizard_info"]["wizard_id"]
+    # Unequipped runes
+    for rune in data["runes"]:
+        id = rune["rune_id"]
+        assigned_to = rune["occupied_id"]
+        if assigned_to == 0:
+            assigned_to = None
+        runeset = rune["set_id"]
+        slot = rune["slot_no"]
+        stars = rune["class"]
+        ancient = 0
+        level = rune["upgrade_curr"]
+        original_quality = rune["extra"]
+        value = rune["sell_value"]
+        if stars > 10:
+            ancient = 1
+            stars = stars - 10
+            original_quality = original_quality - 10
+        if level >= 12:
+            quality = 5
+        elif level >= 9:
+            quality = 4
+        elif level >= 6:
+            quality = 3
+        elif level >= 3:
+            quality = 2
+        else:
+            quality = 1
+        if original_quality > quality:
+            quality = original_quality
+        efficiency = 0
+        max_efficiency = 0
+        main_stat = rune["pri_eff"][0]
+        main_stat_value = rune["pri_eff"][1]
+        if rune["prefix_eff"][0] > 0:
+            innate_stat = rune["prefix_eff"][0]
+            innate_stat_value = rune["prefix_eff"][1]
+        else:
+            innate_stat = None
+            innate_stat_value = 0
+        if len(rune["sec_eff"]) > 0:
+            substat_1 = rune["sec_eff"][0][0]
+            substat_1_value = rune["sec_eff"][0][1]
+            substat_1_enchant = rune["sec_eff"][0][2]
+            substat_1_grind = rune["sec_eff"][0][3]
+        else:
+            substat_1 = None
+            substat_1_value = 0
+            substat_1_enchant = 0
+            substat_1_grind = 0
+        if len(rune["sec_eff"]) > 1:
+            substat_2 = rune["sec_eff"][1][0]
+            substat_2_value = rune["sec_eff"][1][1]
+            substat_2_enchant = rune["sec_eff"][1][2]
+            substat_2_grind = rune["sec_eff"][1][3]
+        else:
+            substat_2 = None
+            substat_2_value = 0
+            substat_2_enchant = 0
+            substat_2_grind = 0
+        if len(rune["sec_eff"]) > 2:
+            substat_3 = rune["sec_eff"][2][0]
+            substat_3_value = rune["sec_eff"][2][1]
+            substat_3_enchant = rune["sec_eff"][2][2]
+            substat_3_grind = rune["sec_eff"][2][3]
+        else:
+            substat_3 = None
+            substat_3_value = 0
+            substat_3_enchant = 0
+            substat_3_grind = 0
+        if len(rune["sec_eff"]) > 3:
+            substat_4 = rune["sec_eff"][3][0]
+            substat_4_value = rune["sec_eff"][3][1]
+            substat_4_enchant = rune["sec_eff"][3][2]
+            substat_4_grind = rune["sec_eff"][3][3]
+        else:
+            substat_4 = None
+            substat_4_value = 0
+            substat_4_enchant = 0
+            substat_4_grind = 0
+
+        # Calculate efficiences
+        efficiency = 0
+        substats = []
+        efficiency += float(MAIN_STAT_VALUES[main_stat][stars][15]) / float(MAIN_STAT_VALUES[main_stat][6][15])
+        if innate_stat is not None:
+            efficiency += innate_stat_value / float(SUBSTAT_INCREMENTS[innate_stat][6] * 5)
+        if substat_1 is not None:
+            substats.append(substat_1)
+            efficiency += substat_1_value / float(SUBSTAT_INCREMENTS[substat_1][6] * 5)
+        if substat_2 is not None:
+            substats.append(substat_2)
+            efficiency += substat_2_value / float(SUBSTAT_INCREMENTS[substat_2][6] * 5)
+        if substat_3 is not None:
+            substats.append(substat_3)
+            efficiency += substat_3_value / float(SUBSTAT_INCREMENTS[substat_3][6] * 5)
+        if substat_4 is not None:
+            substats.append(substat_4)
+            efficiency += substat_4_value / float(SUBSTAT_INCREMENTS[substat_4][6] * 5)
+        efficiency = efficiency / 2.8 * 100
+        max_efficiency = get_max_efficiency(substats, efficiency, level, stars, original_quality)
+        if efficiency > 100:
+            efficiency = 100;
+        if max_efficiency > 100:
+            max_efficiency = 100;
+        insert(db, "rune", (uid, id, assigned_to, runeset, slot, stars, ancient, level, quality, original_quality, value, efficiency, max_efficiency, main_stat, main_stat_value, innate_stat, innate_stat_value, substat_1, substat_1_value, substat_1_enchant, substat_1_grind, substat_2, substat_2_value, substat_2_enchant, substat_2_grind, substat_3, substat_3_value, substat_3_enchant, substat_3_grind, substat_4, substat_4_value, substat_4_enchant, substat_4_grind))
+    # Equipped runes
+    for mon in data["unit_list"]:
+        for rune in mon["runes"]:
+            id = rune["rune_id"]
+            assigned_to = rune["occupied_id"]
+            if assigned_to == 0:
+                assigned_to = None
+            runeset = rune["set_id"]
+            slot = rune["slot_no"]
+            stars = rune["class"]
+            ancient = 0
+            level = rune["upgrade_curr"]
+            original_quality = rune["extra"]
+            value = rune["sell_value"]
+            if stars > 10:
+                ancient = 1
+                stars = stars - 10
+                original_quality = original_quality - 10
+            if level >= 12:
+                quality = 5
+            elif level >= 9:
+                quality = 5
+            elif level >= 6:
+                quality = 3
+            elif level >= 3:
+                quality = 2
+            else:
+                quality = 1
+            if original_quality > quality:
+                quality = original_quality
+            efficiency = 0
+            max_efficiency = 0
+            main_stat = rune["pri_eff"][0]
+            main_stat_value = rune["pri_eff"][1]
+            if rune["prefix_eff"][0] > 0:
+                innate_stat = rune["prefix_eff"][0]
+                innate_stat_value = rune["prefix_eff"][1]
+            else:
+                innate_stat = None
+                innate_stat_value = 0
+            if len(rune["sec_eff"]) > 0:
+                substat_1 = rune["sec_eff"][0][0]
+                substat_1_value = rune["sec_eff"][0][1]
+                substat_1_enchant = rune["sec_eff"][0][2]
+                substat_1_grind = rune["sec_eff"][0][3]
+            else:
+                substat_1 = None
+                substat_1_value = 0
+                substat_1_enchant = 0
+                substat_1_grind = 0
+            if len(rune["sec_eff"]) > 1:
+                substat_2 = rune["sec_eff"][1][0]
+                substat_2_value = rune["sec_eff"][1][1]
+                substat_2_enchant = rune["sec_eff"][1][2]
+                substat_2_grind = rune["sec_eff"][1][3]
+            else:
+                substat_2 = None
+                substat_2_value = 0
+                substat_2_enchant = 0
+                substat_2_grind = 0
+            if len(rune["sec_eff"]) > 2:
+                substat_3 = rune["sec_eff"][2][0]
+                substat_3_value = rune["sec_eff"][2][1]
+                substat_3_enchant = rune["sec_eff"][2][2]
+                substat_3_grind = rune["sec_eff"][2][3]
+            else:
+                substat_3 = None
+                substat_3_value = 0
+                substat_3_enchant = 0
+                substat_3_grind = 0
+            if len(rune["sec_eff"]) > 3:
+                substat_4 = rune["sec_eff"][3][0]
+                substat_4_value = rune["sec_eff"][3][1]
+                substat_4_enchant = rune["sec_eff"][3][2]
+                substat_4_grind = rune["sec_eff"][3][3]
+            else:
+                substat_4 = None
+                substat_4_value = 0
+                substat_4_enchant = 0
+                substat_4_grind = 0
+
+            # Calculate efficiences
+            efficiency = 0
+            substats = []
+            efficiency += float(MAIN_STAT_VALUES[main_stat][stars][15]) / float(MAIN_STAT_VALUES[main_stat][6][15])
+            if innate_stat is not None:
+                efficiency += innate_stat_value / float(SUBSTAT_INCREMENTS[innate_stat][6] * 5)
+            if substat_1 is not None:
+                substats.append(substat_1)
+                efficiency += substat_1_value / float(SUBSTAT_INCREMENTS[substat_1][6] * 5)
+            if substat_2 is not None:
+                substats.append(substat_2)
+                efficiency += substat_2_value / float(SUBSTAT_INCREMENTS[substat_2][6] * 5)
+            if substat_3 is not None:
+                substats.append(substat_3)
+                efficiency += substat_3_value / float(SUBSTAT_INCREMENTS[substat_3][6] * 5)
+            if substat_4 is not None:
+                substats.append(substat_4)
+                efficiency += substat_4_value / float(SUBSTAT_INCREMENTS[substat_4][6] * 5)
+            efficiency = efficiency / 2.8 * 100
+            max_efficiency = get_max_efficiency(substats, efficiency, level, stars, original_quality)
+            if efficiency > 100:
+                efficiency = 100;
+            if max_efficiency > 100:
+                max_efficiency = 100;
+            insert(db, "rune", (uid, id, assigned_to, runeset, slot, stars, ancient, level, quality, original_quality, value, efficiency, max_efficiency, main_stat, main_stat_value, innate_stat, innate_stat_value, substat_1, substat_1_value, substat_1_enchant, substat_1_grind, substat_2, substat_2_value, substat_2_enchant, substat_2_grind, substat_3, substat_3_value, substat_3_enchant, substat_3_grind, substat_4, substat_4_value, substat_4_enchant, substat_4_grind))
+    db.commit()
+
+"""
+calculates the max efficiency of a rune.
+
+:param substats: Array of substat types, dont include empty ones.
+:param efficiency: Current rune efficiency.
+:param level: Current rune level.
+:param stars: Rune stars.
+:param quality: Rune original quality.
+:return Max. efficiency.
+"""
+def get_max_efficiency(substats, efficiency, level, stars, quality):
+    STAT_HP = 1
+    STAT_HP_PCT = 2
+    STAT_ATK = 3
+    STAT_ATK_PCT = 4
+    STAT_DEF = 5
+    STAT_DEF_PCT = 6
+    STAT_SPD = 8
+    STAT_CRIT_RATE_PCT = 9
+    STAT_CRIT_DMG_PCT = 10
+    STAT_RESIST_PCT = 11
+    STAT_ACCURACY_PCT = 12
+    SUBSTAT_INCREMENTS = {
+        # [stat][stars]: value
+        STAT_HP: {
+            1: 60,
+            2: 105,
+            3: 165,
+            4: 225,
+            5: 300,
+            6: 375,
+        },
+        STAT_HP_PCT: {
+            1: 2,
+            2: 3,
+            3: 5,
+            4: 6,
+            5: 7,
+            6: 8,
+        },
+        STAT_ATK: {
+            1: 4,
+            2: 5,
+            3: 8,
+            4: 10,
+            5: 15,
+            6: 20,
+        },
+        STAT_ATK_PCT: {
+            1: 2,
+            2: 3,
+            3: 5,
+            4: 6,
+            5: 7,
+            6: 8,
+        },
+        STAT_DEF: {
+            1: 4,
+            2: 5,
+            3: 8,
+            4: 10,
+            5: 15,
+            6: 20,
+        },
+        STAT_DEF_PCT: {
+            1: 2,
+            2: 3,
+            3: 5,
+            4: 6,
+            5: 7,
+            6: 8,
+        },
+        STAT_SPD: {
+            1: 1,
+            2: 2,
+            3: 3,
+            4: 4,
+            5: 5,
+            6: 6,
+        },
+        STAT_CRIT_RATE_PCT: {
+            1: 1,
+            2: 2,
+            3: 3,
+            4: 4,
+            5: 5,
+            6: 6,
+        },
+        STAT_CRIT_DMG_PCT: {
+            1: 2,
+            2: 3,
+            3: 4,
+            4: 5,
+            5: 6,
+            6: 7,
+        },
+        STAT_RESIST_PCT: {
+            1: 2,
+            2: 3,
+            3: 5,
+            4: 6,
+            5: 7,
+            6: 8,
+        },
+        STAT_ACCURACY_PCT: {
+            1: 2,
+            2: 3,
+            3: 5,
+            4: 6,
+            5: 7,
+            6: 8,
+        },
+    }
+    UPGRADE_VALUES = {
+        rune_type: {
+            stars: value/level_data[6]
+            for stars, value in level_data.items()
+        }
+        for rune_type, level_data in SUBSTAT_INCREMENTS.items()
+    }
+    #substat_upgrades_remaining = (4 - math.floor((min(level, 12) / 3))) - (3 - quality)
+    substat_upgrades_remaining = (4 - math.floor((min(level, 12) / 3))) - max((3 - quality), 0)
+    new_stats = max(min(4 - len(substats), substat_upgrades_remaining), 0)
+    old_stats = substat_upgrades_remaining - new_stats
+
+    if old_stats > 0:
+        # we can repeatedly upgrade the most value of the existing stats
+        best_stat = max(
+            0,  # ensure max() doesn't error if we only have one stat
+            *[UPGRADE_VALUES[stat][stars] for stat in substats]
+        )
+        efficiency += best_stat * old_stats * 0.2 / 2.8 * 100
+    if new_stats:
+        # add the top N stats
+        available_upgrades = sorted(
+            [
+                upgrade_value[stars]
+                for stat, upgrade_value in UPGRADE_VALUES.items()
+                if stat not in substats
+            ],
+            reverse=True
+        )
+        efficiency += sum(available_upgrades[:new_stats]) * 0.2 / 2.8 * 100
+
+    return efficiency
+
+"""
+Parses rune craft item data (table rune_craft).
+
+:param db: Sqlite database connection.
+:param data: Data in json format.
+"""
+def parseRuneCraft(db, data):
+    print("Parsing rune craft itmes...")
+    uid = data["wizard_info"]["wizard_id"]
+    for item in data["rune_craft_item_list"]:
+        id = item["craft_item_id"]
+        type = item["craft_type"]
+        value = item["sell_value"]
+        info = str(item["craft_type_id"])
+        """
+        info : 5 or 6 digit number: RRSSQ
+          RR: Rune
+          SS: Stat
+          Q:  Quality
+        """
+        quality = int(info[-1:])
+        stat = int(info[-4:-2])
+        rune = int(info[:-4])
+        insert(db, "rune_craft", (uid, id, type, quality, rune, stat, value))
+
+"""
+Parses guild data (tables guild, guild_member).
+
+:param db: Sqlite database connection.
+:param data: Data in json format.
+"""
+def parseGuild(db, data):
+    print("Parsing guild...")
+    cursor = db.cursor()
+    if (data["guild"]["guild_info"] == None):
+        # No guild
+        return
+    if len(data["guild"]["guild_info"]) < 1:
+        return
+    guild = data["guild"]["guild_info"]
+    id = guild["guild_id"]
+    name = guild["name"]
+    level = guild["level"]
+    experience = guild["experience"]
+    recruiting = guild["recruit_status"]
+    members = guild["member_now"]
+    leader = guild["master_wizard_id"]
+    comment = guild["comment"]
+    notice = guild["notice"]
+    # Delete selected guild before inserting
+    cursor.execute("DELETE FROM guild WHERE id = ?", [id])
+    cursor.execute("DELETE FROM guild_member WHERE guild = ?", [id])
+    db.commit()
+    insert(db, "guild", (id, name, level, experience, recruiting, members, leader, comment, notice))
+    # Single quates ahead, dirty fix
+    for k, member in data["guild"]["guild_members"].items():
+        m = json.loads(str(member).replace("'", '"'))
+        id = m["wizard_id"]
+        guild = member["guild_id"]
+        grade = member["grade"]
+        name = member["wizard_name"]
+        level = member["wizard_level"]
+        rating = member["rating_id"]
+        arena_score = member["arena_score"]
+        joined = member["join_timestamp"]
+        last_login = member["last_login_timestamp"]
+        in_war = 0 # Checked later
+        has_defense = 0 # Checked later
+        insert(db, "guild_member", (id, guild, grade, name, level, rating, arena_score, joined, last_login, in_war, has_defense))
+    for war in data["guildwar_member_list"]:
+        cursor.execute("UPDATE guild_member SET in_war = 1 WHERE guild = ? AND id = ?;", (war["guild_id"], war["wizard_id"]))
+    for defense in data["guild_member_defense_list"]:
+        if len(defense["unit_list"]) > 0:
+            cursor.execute("UPDATE guild_member SET has_defense = 1 WHERE id = ?", [defense["wizard_id"]])
+    db.commit()
+    cursor.close()
+
+"""
+Deletes teams whose members no longer exists.
+
+:param db: Sqlite database connection.
+"""
+def reconfigureTeams(db):
+    print("Fixing teams...")
+    uid = data["wizard_info"]["wizard_id"]
+    cursor = db.cursor()
+    cursor.execute("DELETE FROM team WHERE id IN (SELECT DISTINCT team FROM team_unit WHERE unit NOT IN (SELECT DISTINCT id FROM unit WHERE uid = ?)) AND uid = ?",  (uid, uid))
+    cursor.execute("DELETE FROM team_unit WHERE unit NOT IN (SELECT DISTINCT id FROM unit) OR team NOT IN (SELECT DISTINCT id FROM team);")
+    db.commit()
+    cursor.close()
+
+"""
+Begin script
+"""
+try:
+    data = readData()
+except Exception as e:
+    print("Error reading data: " + str(e))
+    sys.exit(400)
+try:
+    key = readKey()
+except Exception as e:
+    print("Error fetching API key: " + str(e))
+    sys.exit(400)
+try:
+    db = openDatabase()
+except Exception as e:
+    print("Error opening database: " + str(e))
+    sys.exit(500)
+if verifyKey(db, data, key) == False:
+    print("Invalid API KEY...")
+    sys.exit(401)
+else:
+    try:
+        clearData(db, data)
+    except Exception as e:
+        print("Error clearing data: " + str(e))
+        sys.exit(500)
+    try:
+        parsePlayer(db, data)
+    except Exception as e:
+        print("Error parsing player data: " + str(e))
+        sys.exit(400)
+    try:
+        parseScenarios(db, data)
+    except Exception as e:
+        print("Error parsing scenario data: " + str(e))
+        sys.exit(400)
+    try:
+        parseDefense(db, data)
+    except Exception as e:
+        print("Error parsing defense data: " + str(e))
+        sys.exit(400)
+    try:
+        parseBuildings(db, data)
+    except Exception as e:
+        print("Error parsing building data: " + str(e))
+        sys.exit(400)
+    try:
+        parseDecorations(db, data)
+    except Exception as e:
+        print("Error parsing decoration data: " + str(e))
+        sys.exit(400)
+    # TODO homunculus_skill_list
+    try:
+        parseUnits(db, data)
+    except Exception as e:
+        print("Error parsing unit data: " + str(e))
+        sys.exit(400)
+    try:
+        parseSummonSpecial(db, data)
+    except Exception as e:
+        print("Error parsing special summon data: " + str(e))
+        sys.exit(400)
+    try:
+        parseInventory(db, data)
+    except Exception as e:
+        print("Error parsing inventory data: " + str(e))
+        sys.exit(400)
+    try:
+        parseRunes(db, data)
+    except Exception as e:
+        print("Error parsing runes data: " + str(e))
+        sys.exit(400)
+    try:
+        parseRuneCraft(db, data)
+    except Exception as e:
+        print("Error parsing rune craft data: " + str(e))
+        sys.exit(400)
+    try:
+        parseGuild(db, data)
+    except Exception as e:
+        print("Error parsing guild data: " + str(e))
+        sys.exit(400)
+    try:
+        reconfigureTeams(db)
+    except Exception as e:
+        print("Error reconfiguring teams: " + str(e))
+        sys.exit(400)
+    try:
+        closeDatabase(db)
+    except Exception as e:
+        print("Error closing database: " + str(e))
+        # No error
+sys.exit(200);
+

+ 246 - 0
application/API/v2/bin/upload_run_dimension.py

@@ -0,0 +1,246 @@
+#!/usr/bin/python3
+
+import sqlite3
+import json
+import sys
+import os
+
+import MAPPING
+
+"""
+Reads the API KEY, that must be passed as first command line argument.
+
+:returns: Recovered API KEY.
+:raises Exception: Th KEY couldn't be red.
+"""
+def readKey():
+    try:
+        key = sys.argv[1]
+        return key
+    except Exception as e:
+        print("Error parsing API KEY: " + str(e))
+        raise
+
+"""
+Reads the JSON data, that must be passed as second command line argument.
+
+:param: index 2 for start data, 3 for result data
+:returns: Recovered data, in JSON format.
+:raises Exception: The data couldn't be red or converted to JSON.
+"""
+def readData(index):
+    try:
+        data = json.loads(sys.argv[index])
+        return data
+    except Exception as e:
+        print("Error parsing data: " + str(e))
+        raise
+
+"""
+Verifies that the API key matches the player data and that it exists in th DB.
+
+:param db: Connection to the database.
+:returns: Connection to the database.
+:param data: Data in json format.
+:param key: API KEY.
+:returns: True if key and player match, False otherwise.
+:raises IntegrityError: The queryes couldn't bre executed.
+"""
+def verifyKey(db, data, key):
+    print('Verifying KEY...')
+    status = False
+    try:
+        uid = data["wizard_info"]["wizard_id"]
+        cursor = db.cursor()
+        cursor.execute('SELECT count(uid) AS c FROM player WHERE uid = ? AND api_key = ?;', (uid, key))
+        if cursor.fetchone()[0] == 1:
+            status = True
+        cursor.close()
+    except sqlite3.IntegrityError as e:
+        print("Error executing statement: " + str(e))
+        raise
+    return status
+
+"""
+Opens the database file and deletes from the user tables
+
+:param name: The path to the sqlite database.
+:returns: Connection to the database.
+:raises IntegrityError: The queryes couldn't bre executed.
+:raises IOError: The sqlite file couldn't be created.
+"""
+def openDatabase():
+    kdb = os.path.dirname(os.path.realpath(sys.argv[0])) + '/../../../sw.sqlite'
+    udb = os.path.dirname(os.path.realpath(sys.argv[0])) + '/../../../../data/sw.sqlite'
+    print('Configuring database...')
+    try:
+        db = sqlite3.connect(kdb)
+        cursor = db.cursor()
+        cursor.execute('attach "' + udb + '" as data;')
+        cursor.close()
+        return db
+    except sqlite3.IntegrityError as e:
+        print("Error executing statement: " + str(e))
+        raise
+    except IOError as e:
+        print("I/O Error creating database " + name + ": " + str(e))
+        raise
+
+"""
+Inserts a row into the database.
+
+:param db: Connection to the database.
+:param table: Name of the table to insert into.
+:param values: List of values to insert.
+:raises IntegrityError: The insert query was unsuccesfull.
+"""
+def insert(db, table, values):
+    cursor = db.cursor()
+    placeholders = ''
+    for x in range(0, len(values)):
+        placeholders = placeholders + '?, '
+    placeholders = placeholders[:len(placeholders) - 2]
+    query = 'INSERT INTO ' + table + ' VALUES (' + placeholders + ')'
+    try:
+        cursor.execute(query, values)
+    except sqlite3.IntegrityError as e:
+        print('Error Inserting into ' + table + ' with values (' + str(values) + '): ' + str(e))
+        raise
+    cursor.close;
+
+"""
+Parses run data (tables run, run_party, run_drop_rune, run_drop_runecraft,
+run_drop_item, run_drop_unit, run_drop_sd, run_drop_unit_pieces,
+run_drop_shapeshifting).
+
+:param db: Sqlite database connection.
+:param data_start: JSON data of the run start.
+:param data_result: JSON data of the run result.
+"""
+def parseRun(db, data_request, data_response):
+    print("Parsing run...")
+    cursor = db.cursor()
+
+    # Read basic data
+    uid = data_request["wizard_id"]
+    dtime = data_response["tvaluelocal"]
+
+    # Check if run has already been inserted.
+    cursor.execute("SELECT count(id) AS c FROM run WHERE uid = ? AND dtime = ?;", (uid, dtime))
+    if (cursor.fetchone()[0] > 0):
+        print("    Run already in database. Stopping....")
+        return 409;
+
+    # We are inserting, get aditional info
+    area_type = 5 # Dimension Hole Dungeon
+    area = data_request["dungeon_id"]
+    stage = data_request["difficulty"] # Stage is difficulty
+    difficulty = None
+    win = data_response["win_lose"]
+    time = data_response["clear_time"]["current_time"]
+    if ("mana" in data_response["reward"]):
+        mana = data_response["reward"]["mana"]
+    else:
+        mana = 0
+    if ("energy" in data_response["reward"]):
+        energy = mana = data_response["reward"]["energy"]
+    else:
+        energy = 0
+    if ("crystal" in data_response["reward"]):
+        crystal = mana = data_response["reward"]["crystal"]
+    else:
+        crystal = 0
+    helper = 0
+
+    # Get the new ID and insert
+    cursor.execute("SELECT max(id) + 1 AS id FROM run;")
+    id = cursor.fetchone()[0]
+    if id == None:
+        id = 1
+    insert(db, "run", (uid, id, dtime, area_type, area, stage, difficulty, win, time, mana, energy, crystal, helper))
+
+    # Parse various types of reward
+    crate = data_response["reward"]["crate"]
+
+    if "item" in crate:
+        for item in crate["item"]:
+            insert(db, "run_drop_item", (id, item["item_id"], item["item_quantity"]))
+    if "craft_stuff" in crate:
+        for item in crate["craft_stuff"]:
+            insert(db, ".run_drop_rune_craft", (id, item["item_id"], item["item_quantity"]))
+
+    # Parse rune reward
+    if "rune" in crate:
+        rune = crate["rune"]
+        rune_id = rune["rune_id"]
+        rune_type = rune["set_id"]
+        slot = rune["slot_no"]
+        stars = rune["class"]
+        ancient = 0
+        quality = rune["rank"]
+        value = rune["sell_value"]
+        efficiency, max_efficiency = MAPPING.calculate_efficiency(rune)
+        main_stat = rune["pri_eff"][0]
+        main_stat_value = rune["pri_eff"][1]
+        if "prefix_eff" in rune:
+            innate_stat = rune["prefix_eff"][0]
+            innate_stat_value = rune["prefix_eff"][1]
+        else:
+            innate_stat = 0
+            innate_stat_value = 0
+        substat_1 = 0
+        substat_1_value = 0
+        substat_2 = 0
+        substat_2_value = 0
+        substat_3 = 0
+        substat_3_value = 0
+        substat_4 = 0
+        substat_4_value = 0
+        if "0" in rune["sec_eff"]:
+            substat_1 = rune["sec_eff"]["0"]["0"]
+            substat_1_value = rune["sec_eff"]["0"]["1"]
+        if "1" in rune["sec_eff"]:
+            substat_1 = rune["sec_eff"]["1"]["0"]
+            substat_1_value = rune["sec_eff"]["1"]["1"]
+        if "2" in rune["sec_eff"]:
+            substat_1 = rune["sec_eff"]["2"]["0"]
+            substat_1_value = rune["sec_eff"]["2"]["1"]
+        if "3" in rune["sec_eff"]:
+            substat_1 = rune["sec_eff"]["3"]["0"]
+            substat_1_value = rune["sec_eff"]["3"]["1"]
+        insert(db, "run_drop_rune", (id, rune_id, rune_type, slot, stars, ancient, quality, value, efficiency, max_efficiency, main_stat, main_stat_value, innate_stat, innate_stat_value, substat_1, substat_1_value, substat_2, substat_2_value, substat_3, substat_3_value, substat_4, substat_4_value))
+
+    # Parse units
+    for unit in data_request["unit_id_list"]:
+        unit_id = unit["unit_id"]
+        unit_master_id = None
+        for k_unit in data_response["unit_list"]:
+            if k_unit["unit_id"] == unit["unit_id"]:
+                unit_master_id = k_unit["unit_master_id"];
+                break;
+        #unit_master_id = data_response["unit_list"][attribute]["unit_master_id"]
+        leader = unit["is_leader"]
+        front = 0
+        insert(db, "run_party", (id, unit_master_id, unit_id, leader, front))
+    db.commit()
+
+
+"""
+Begin script
+"""
+data_request = readData(2)
+data_response = readData(3)
+key = readKey()
+db = openDatabase()
+if verifyKey(db, data_response, key) == False:
+    print("Invalid API KEY...")
+    sys.exit(401)
+else:
+    try:
+        parseRun(db, data_request, data_response)
+    except Exception as e:
+        print("Error parsing Dimension Hole Dungeon run: " + str(e))
+        sys.exit(400)
+sys.exit(201)
+
+

+ 252 - 0
application/API/v2/bin/upload_run_dungeon.py

@@ -0,0 +1,252 @@
+#!/usr/bin/python3
+
+import sqlite3
+import json
+import sys
+import os
+
+import MAPPING
+
+"""
+Reads the API KEY, that must be passed as first command line argument.
+
+:returns: Recovered API KEY.
+:raises Exception: Th KEY couldn't be red.
+"""
+def readKey():
+    try:
+        key = sys.argv[1]
+        return key
+    except Exception as e:
+        print("Error parsing API KEY: " + str(e))
+        raise
+
+"""
+Reads the JSON data, that must be passed as second command line argument.
+
+:param: index 2 for start data, 3 for result data
+:returns: Recovered data, in JSON format.
+:raises Exception: The data couldn't be red or converted to JSON.
+"""
+def readData(index):
+    try:
+        data = json.loads(sys.argv[index])
+        return data
+    except Exception as e:
+        print("Error parsing data: " + str(e))
+        raise
+
+"""
+Verifies that the API key matches the player data and that it exists in th DB.
+
+:param db: Connection to the database.
+:returns: Connection to the database.
+:param data: Data in json format.
+:param key: API KEY.
+:returns: True if key and player match, False otherwise.
+:raises IntegrityError: The queryes couldn't bre executed.
+"""
+def verifyKey(db, data, key):
+    print('Verifying KEY...')
+    status = False
+    try:
+        uid = data["wizard_info"]["wizard_id"]
+        cursor = db.cursor()
+        cursor.execute('SELECT count(uid) AS c FROM player WHERE uid = ? AND api_key = ?;', (uid, key))
+        if cursor.fetchone()[0] == 1:
+            status = True
+        cursor.close()
+    except sqlite3.IntegrityError as e:
+        print("Error executing statement: " + str(e))
+        raise
+    return status
+
+"""
+Opens the database file and deletes from the user tables
+
+:param name: The path to the sqlite database.
+:returns: Connection to the database.
+:raises IntegrityError: The queryes couldn't bre executed.
+:raises IOError: The sqlite file couldn't be created.
+"""
+def openDatabase():
+    kdb = os.path.dirname(os.path.realpath(sys.argv[0])) + '/../../../sw.sqlite'
+    udb = os.path.dirname(os.path.realpath(sys.argv[0])) + '/../../../../data/sw.sqlite'
+    print('Configuring database...')
+    try:
+        db = sqlite3.connect(kdb)
+        cursor = db.cursor()
+        cursor.execute('attach "' + udb + '" as data;')
+        cursor.close()
+        return db
+    except sqlite3.IntegrityError as e:
+        print("Error executing statement: " + str(e))
+        raise
+    except IOError as e:
+        print("I/O Error creating database " + name + ": " + str(e))
+        raise
+
+"""
+Inserts a row into the database.
+
+:param db: Connection to the database.
+:param table: Name of the table to insert into.
+:param values: List of values to insert.
+:raises IntegrityError: The insert query was unsuccesfull.
+"""
+def insert(db, table, values):
+    cursor = db.cursor()
+    placeholders = ''
+    for x in range(0, len(values)):
+        placeholders = placeholders + '?, '
+    placeholders = placeholders[:len(placeholders) - 2]
+    query = 'INSERT INTO ' + table + ' VALUES (' + placeholders + ')'
+    try:
+        cursor.execute(query, values)
+    except sqlite3.IntegrityError as e:
+        print('Error Inserting into ' + table + ' with values (' + str(values) + '): ' + str(e))
+        raise
+    cursor.close;
+
+"""
+Parses run data (tables run, run_party, run_drop_rune, run_drop_runecraft,
+run_drop_item, run_drop_unit, run_drop_sd, run_drop_unit_pieces,
+run_drop_shapeshifting).
+
+:param db: Sqlite database connection.
+:param data_start: JSON data of the run start.
+:param data_result: JSON data of the run result.
+"""
+def parseRun(db, data_request, data_response):
+    print("Parsing run...")
+    cursor = db.cursor()
+
+    # Read basic data
+    uid = data_request["wizard_id"]
+    dtime = data_response["tvaluelocal"]
+
+    # Check if run has already been inserted.
+    cursor.execute("SELECT count(id) AS c FROM run WHERE uid = ? AND dtime = ?;", (uid, dtime))
+    if (cursor.fetchone()[0] > 0):
+        print("    Run already in database. Stopping....")
+        return 409;
+
+    # We are inserting, get aditional info
+    area_type = 2 # Cairos Dungeon
+    area = data_request["dungeon_id"]
+    stage = data_request["stage_id"]
+    difficulty = None
+    win = data_response["win_lose"]
+    time = data_response["clear_time"]["current_time"]
+    if ("mana" in data_response["reward"]):
+        mana = data_response["reward"]["mana"]
+    else:
+        mana = 0
+    if ("energy" in data_response["reward"]):
+        energy = mana = data_response["reward"]["energy"]
+    else:
+        energy = 0
+    if ("crystal" in data_response["reward"]):
+        crystal = mana = data_response["reward"]["crystal"]
+    else:
+        crystal = 0
+    # TODO Read helper. Do a test with SWBD-debug
+    helper = 0
+
+    # GEt the new ID and insert
+    cursor.execute("SELECT max(id) + 1 AS id FROM run;")
+    id = cursor.fetchone()[0]
+    if id == None:
+        id = 1
+    insert(db, "run", (uid, id, dtime, area_type, area, stage, difficulty, win, time, mana, energy, crystal, helper))
+
+    # Parse various types of reward
+    crate = data_response["reward"]["crate"]
+    if "costume_point" in crate and crate["costume_point"] > 0:
+        insert(db, "run_drop_shapeshifting", (id, crate["costume_point"]))
+    if "instance_info" in data_response:
+        insert(db, "run_drop_sd", (id, data_response["instance_info"]))
+    if "unit_info" in crate:
+        insert(db, "run_drop_unit", (id, crate["unit_info"]["unit_master_id"]))
+    if "summon_pieces" in crate:
+        insert(db, "run_drop_unit_pieces", (id, crate["summon_pieces"]["item_master_id"], crate["summon_pieces"]["item_quantity"]))
+    if "item" in crate:
+        for item in crate["item"]:
+            insert(db, "run_drop_item", (id, item["item_id"], item["item_quantity"]))
+
+    # Parse rune reward
+    if "rune" in crate:
+        rune = crate["rune"]
+        rune_id = rune["rune_id"]
+        rune_type = rune["set_id"]
+        slot = rune["slot_no"]
+        stars = rune["class"]
+        ancient = 0
+        quality = rune["rank"]
+        value = rune["sell_value"]
+        efficiency, max_efficiency = MAPPING.calculate_efficiency(rune)
+        main_stat = rune["pri_eff"][0]
+        main_stat_value = rune["pri_eff"][1]
+        if "prefix_eff" in rune:
+            innate_stat = rune["prefix_eff"][0]
+            innate_stat_value = rune["prefix_eff"][1]
+        else:
+            innate_stat = 0
+            innate_stat_value = 0
+        substat_1 = 0
+        substat_1_value = 0
+        substat_2 = 0
+        substat_2_value = 0
+        substat_3 = 0
+        substat_3_value = 0
+        substat_4 = 0
+        substat_4_value = 0
+        if "0" in rune["sec_eff"]:
+            substat_1 = rune["sec_eff"]["0"]["0"]
+            substat_1_value = rune["sec_eff"]["0"]["1"]
+        if "1" in rune["sec_eff"]:
+            substat_1 = rune["sec_eff"]["1"]["0"]
+            substat_1_value = rune["sec_eff"]["1"]["1"]
+        if "2" in rune["sec_eff"]:
+            substat_1 = rune["sec_eff"]["2"]["0"]
+            substat_1_value = rune["sec_eff"]["2"]["1"]
+        if "3" in rune["sec_eff"]:
+            substat_1 = rune["sec_eff"]["3"]["0"]
+            substat_1_value = rune["sec_eff"]["3"]["1"]
+        insert(db, "run_drop_rune", (id, rune_id, rune_type, slot, stars, ancient, quality, value, efficiency, max_efficiency, main_stat, main_stat_value, innate_stat, innate_stat_value, substat_1, substat_1_value, substat_2, substat_2_value, substat_3, substat_3_value, substat_4, substat_4_value))
+
+    # Parse units
+    #for attribute, value in data_request["unit_id_list"]:
+    for unit in data_request["unit_id_list"]:
+        unit_id = unit["unit_id"]
+        unit_master_id = None
+        for k_unit in data_response["unit_list"]:
+            if k_unit["unit_id"] == unit["unit_id"]:
+                unit_master_id = k_unit["unit_master_id"];
+                break;
+        #unit_master_id = data_response["unit_list"][attribute]["unit_master_id"]
+        leader = unit["is_leader"]
+        front = 0
+        insert(db, "run_party", (id, unit_master_id, unit_id, leader, front))
+    db.commit()
+
+
+"""
+Begin script
+"""
+data_request = readData(2)
+data_response = readData(3)
+key = readKey()
+db = openDatabase()
+if verifyKey(db, data_response, key) == False:
+    print("Invalid API KEY...")
+    sys.exit(401)
+else:
+    #try:
+        parseRun(db, data_request, data_response)
+    #except Exception as e:
+    #    print("Error parsing dungeon run: " + str(e))
+    #    sys.exit(400)
+sys.exit(201)
+
+

+ 207 - 0
application/API/v2/bin/upload_run_scenario.py

@@ -0,0 +1,207 @@
+#!/usr/bin/python3
+
+import sqlite3
+import json
+import sys
+import os
+
+
+"""
+Reads the API KEY, that must be passed as first command line argument.
+
+:returns: Recovered API KEY.
+:raises Exception: Th KEY couldn't be red.
+"""
+def readKey():
+    try:
+        #print(sys.argv[0])
+        key = sys.argv[1]
+        return key
+    except Exception as e:
+        print("Error parsing API KEY: " + str(e))
+        raise
+
+"""
+Reads the JSON data, that must be passed as second command line argument.
+
+:returns: Recovered data, in JSON format.
+:raises Exception: The data couldn't be red or converted to JSON.
+"""
+def readData():
+    try:
+        data = json.loads(sys.argv[2])
+        return data
+    except Exception as e:
+        print("Error parsing data: " + str(e))
+        raise
+
+"""
+Verifies that the API key matches the player data and that it exists in th DB.
+
+:param db: Connection to the database.
+:returns: Connection to the database.
+:param data: Data in json format.
+:param key: API KEY.
+:returns: True if key and player match, False otherwise.
+:raises IntegrityError: The queryes couldn't bre executed.
+"""
+def verifyKey(db, data, key):
+    print('Verifying KEY...')
+    status = False
+    try:
+        uid = data["wizard_info"]["wizard_id"]
+        cursor = db.cursor()
+        cursor.execute('SELECT count(uid) AS c FROM player WHERE uid = ? AND api_key = ?;', (uid, key))
+        if cursor.fetchone()[0] == 1:
+            status = True
+        cursor.close()
+    except sqlite3.IntegrityError as e:
+        print("Error executing statement: " + str(e))
+        raise
+    return status
+
+"""
+Opens the database file and deletes from the user tables
+
+:param name: The path to the sqlite database.
+:returns: Connection to the database.
+:raises IntegrityError: The queryes couldn't bre executed.
+:raises IOError: The sqlite file couldn't be created.
+"""
+def openDatabase():
+    kdb = os.path.dirname(os.path.realpath(sys.argv[0])) + '/../../../sw.sqlite'
+    udb = os.path.dirname(os.path.realpath(sys.argv[0])) + '/../../../../data/sw.sqlite'
+    print('Configuring database...')
+    try:
+        db = sqlite3.connect(kdb)
+        cursor = db.cursor()
+        cursor.execute('attach "' + udb + '" as data;')
+        cursor.close()
+        return db
+    except sqlite3.IntegrityError as e:
+        print("Error executing statement: " + str(e))
+        raise
+    except IOError as e:
+        print("I/O Error creating database " + name + ": " + str(e))
+        raise
+
+"""
+Inserts a row into the database.
+
+:param db: Connection to the database.
+:param table: Name of the table to insert into.
+:param values: List of values to insert.
+:raises IntegrityError: The insert query was unsuccesfull.
+"""
+def insert(db, table, values):
+    cursor = db.cursor()
+    placeholders = ''
+    for x in range(0, len(values)):
+        placeholders = placeholders + '?, '
+    placeholders = placeholders[:len(placeholders) - 2]
+    query = 'INSERT INTO ' + table + ' VALUES (' + placeholders + ')'
+    try:
+        cursor.execute(query, values)
+    except sqlite3.IntegrityError as e:
+        print('Error Inserting into ' + table + ' with values (' + str(values) + '): ' + str(e))
+        raise
+    cursor.close;
+
+"""
+Parses run data (tables run, run_party, run_drop_rune, run_drop_runecraft,
+run_drop_item, run_drop_unit, run_drop_sd, run_drop_unit_pieces,
+run_drop_shapeshifting).
+
+:param db: Sqlite database connection.
+:param data: JSON data.
+"""
+def parseRun(db, data):
+    print("Parsing run...")
+    cursor = db.cursor()
+    uid = data["wizard_info"]["wizard_id"]
+    dtime = data["tvaluelocal"]
+    # Check if run has already been inserted.
+    cursor.execute("SELECT count(id) AS c FROM run WHERE uid = ? AND dtime = ?;", (uid, dtime))
+    if (cursor.fetchone()[0] > 0):
+        print("    Run already in database. Stopping....")
+        return;
+    area_type = 1 #scenario 
+    area = data["scenario_info"]["region_id"]
+    stage = data["scenario_info"]["stage_no"]
+    difficulty = data["scenario_info"]["difficulty"]
+    win = data["win_lose"]
+    time = data["clear_time"]["current_time"]
+    if ("mana" in data["reward"]):
+        mana = data["reward"]["mana"]
+    else:
+        mana = 0
+    if ("energy" in data["reward"]):
+        energy = mana = data["reward"]["energy"]
+    else:
+        energy = 0
+    if ("crystal" in data["reward"]):
+        crystal = mana = data["reward"]["crystal"]
+    else:
+        crystal = 0
+    helper = data["helper"]
+    cursor.execute("SELECT max(id) + 1 AS id FROM run;")
+    id = cursor.fetchone()[0]
+    if id == None:
+        id = 1
+    insert(db, "run", (uid, id, dtime, area, stage, difficulty, win, time, mana, energy, crystal, helper))
+    if data["shapeshifting"] > 0:
+        insert(db, "run_drop_shapeshifting", (id, data["shapeshifting"]))
+    if data["sd"] > 0:
+        insert(db, "run_drop_sd", (id, data["sd"]))
+    if data["sd"] > 0:
+        insert(db, "run_drop_unit", (id, data["unit"]))
+    for rune in data["rune"]:
+        rune_id = rune["id"]
+        rune_type = rune["type"]
+        slot = rune["slot"]
+        stars = rune["stars"]
+        ancient = rune["ancient"]
+        quality = rune["quality"]
+        value = rune["value"]
+        efficiency = rune["efficiency"]
+        main_stat = rune["main_stat"]
+        main_stat_value = rune["main_stat_value"]
+        innate_stat = rune["innate_stat"]
+        innate_stat_value = rune["innate_stat_value"]
+        substat_1 = rune["substat_1"]
+        substat_1_value = rune["substat_1_value"]
+        substat_2 = rune["substat_2"]
+        substat_2_value = rune["substat_2_value"]
+        substat_3 = rune["substat_3"]
+        substat_3_value = rune["substat_3_value"]
+        substat_4 = rune["substat_4"]
+        substat_4_value = rune["substat_4_value"]
+        insert(db, "run_drop_rune", (id, rune_id, rune_type, slot, stars, ancient, quality, value, efficiency, main_stat, main_stat_value, innate_stat, innate_stat_value, substat_1, substat_1_value, substat_2, substat_2_value, substat_3, substat_3_value, substat_4, substat_4_value))
+    for item in data["item"]:
+        item_id = item["id"]
+        quantity = item["quantity"]
+        insert(db, "run_drop_item", (id, item_id, quantity))
+    for pieces in data["unit_pieces"]:
+        pieces_id = pieces["id"]
+        quantity = pieces["quantity"]
+        insert(db, "run_drop_unit_pieces", (id, pieces_id, quantity))
+    for party in data["party"]:
+        unit_id = party["unit_id"]
+        unit_master_id = party["unit_master_id"]
+        insert(db, "run_party", (id, unit_master_id, unit_id))
+    db.commit()
+
+"""
+Begin script
+"""
+data = readData()
+key = readKey()
+db = openDatabase()
+if verifyKey(db, data, key) == False:
+    print("Invalid API KEY...")
+    sys.exit(-1)
+else:
+    parseRun(db, data)
+sys.exit(0)
+
+

+ 226 - 0
application/API/v2/bin/upload_run_toa.py

@@ -0,0 +1,226 @@
+#!/usr/bin/python3
+
+import sqlite3
+import json
+import sys
+import os
+import math
+
+"""
+Reads the API KEY, that must be passed as first command line argument.
+
+:returns: Recovered API KEY.
+:raises Exception: Th KEY couldn't be red.
+"""
+def readKey():
+    try:
+        key = sys.argv[1]
+        return key
+    except Exception as e:
+        print("Error parsing API KEY: " + str(e))
+        raise
+
+"""
+Reads the JSON data, that must be passed as second command line argument.
+
+:param: index 2 for start data, 3 for result data
+:returns: Recovered data, in JSON format.
+:raises Exception: The data couldn't be red or converted to JSON.
+"""
+def readData(index):
+    try:
+        data = json.loads(sys.argv[index])
+        return data
+    except Exception as e:
+        print("Error parsing data: " + str(e))
+        raise
+
+"""
+Verifies that the API key matches the player data and that it exists in th DB.
+
+:param db: Connection to the database.
+:returns: Connection to the database.
+:param data: Data in json format.
+:param key: API KEY.
+:returns: True if key and player match, False otherwise.
+:raises IntegrityError: The queryes couldn't bre executed.
+"""
+def verifyKey(db, data, key):
+    print('Verifying KEY...')
+    status = False
+    try:
+        uid = data["wizard_info"]["wizard_id"]
+        cursor = db.cursor()
+        cursor.execute('SELECT count(uid) AS c FROM player WHERE uid = ? AND api_key = ?;', (uid, key))
+        if cursor.fetchone()[0] == 1:
+            status = True
+        cursor.close()
+    except sqlite3.IntegrityError as e:
+        print("Error executing statement: " + str(e))
+        raise
+    return status
+
+"""
+Opens the database file and deletes from the user tables
+
+:param name: The path to the sqlite database.
+:returns: Connection to the database.
+:raises IntegrityError: The queryes couldn't bre executed.
+:raises IOError: The sqlite file couldn't be created.
+"""
+def openDatabase():
+    kdb = os.path.dirname(os.path.realpath(sys.argv[0])) + '/../../../sw.sqlite'
+    udb = os.path.dirname(os.path.realpath(sys.argv[0])) + '/../../../../data/sw.sqlite'
+    print('Configuring database...')
+    try:
+        db = sqlite3.connect(kdb)
+        cursor = db.cursor()
+        cursor.execute('attach "' + udb + '" as data;')
+        cursor.close()
+        return db
+    except sqlite3.IntegrityError as e:
+        print("Error executing statement: " + str(e))
+        raise
+    except IOError as e:
+        print("I/O Error opening database " + name + ": " + str(e))
+        raise
+
+"""
+Inserts a row into the database.
+
+:param db: Connection to the database.
+:param table: Name of the table to insert into.
+:param values: List of values to insert.
+:raises IntegrityError: The insert query was unsuccesfull.
+"""
+def insert(db, table, values):
+    cursor = db.cursor()
+    placeholders = ''
+    for x in range(0, len(values)):
+        placeholders = placeholders + '?, '
+    placeholders = placeholders[:len(placeholders) - 2]
+    query = 'INSERT INTO ' + table + ' VALUES (' + placeholders + ')'
+    try:
+        cursor.execute(query, values)
+    except sqlite3.IntegrityError as e:
+        print('Error Inserting into ' + table + ' with values (' + str(values) + '): ' + str(e))
+        raise
+    cursor.close;
+
+"""
+Parses run data (tables run, run_party, run_drop_rune, run_drop_runecraft,
+run_drop_item, run_drop_unit, run_drop_sd, run_drop_unit_pieces,
+run_drop_shapeshifting).
+
+:param db: Sqlite database connection.
+:param data_start: JSON data of the run start.
+:param data_result: JSON data of the run result.
+"""
+def parseRun(db, data_request, data_response):
+    print("Parsing run...")
+    cursor = db.cursor()
+
+    # Read basic data
+    uid = data_request["wizard_id"]
+    dtime = data_response["tvaluelocal"]
+
+    # Check if run has already been inserted.
+    cursor.execute("SELECT count(id) AS c FROM run WHERE uid = ? AND dtime = ?;", (uid, dtime))
+    if (cursor.fetchone()[0] > 0):
+        print("    Run already in database. Stopping....")
+        return 409;
+
+    # We are inserting, get aditional info
+    area_type = 10 # TOA
+    area = 10 # TOA
+    stage = data_request["floor_id"]
+    difficulty = data_request["difficulty"]
+    win = data_response["win_lose"]
+    time = data_request["clear_time"]
+    helper = 0
+
+    # Get the new ID and insert
+    cursor.execute("SELECT max(id) + 1 AS id FROM run;")
+    id = cursor.fetchone()[0]
+    if id == None:
+        id = 1
+
+    # Calculate rewards
+    mana = 0
+    energy = 0
+    crystal = 0
+    if stage == 10:
+        energy = 50
+    elif stage == 20:
+        # Rainbowmon 3* MAX
+        insert(db, "run_drop_item", (id, 143140325, 1))
+    elif stage == 30:
+        # Mystical Scroll
+        insert(db, "run_drop_item", (id, 3, 1))
+    elif stage == 40:
+        crystal = 100
+    elif stage == 50:
+        # Mystical Scroll
+        insert(db, "run_drop_item", (id, 3, 2))
+    elif stage == 60:
+        # Rainbowmon 4* MAX
+        insert(db, "run_drop_item", (id, 143140430, 1))
+    elif stage == 70:
+        # Devilmon
+        insert(db, "run_drop_item", (id, 151050101, 1))
+    elif stage == 80:
+        crystal = 300
+    elif stage == 90:
+        # Light and Dark Scroll
+        insert(db, "run_drop_item", (id, 11, 1))
+    elif stage == 100:
+        # Legendary Scroll
+        insert(db, "run_drop_item", (id, 10, 1))
+    elif stage / 5 == 0:
+        crystal = 10 * int(math.ceil((stage / 2) / 10) + 1)
+    else:
+        # Summon stone
+        insert(db, "run_drop_item", (id, 8, 1))
+
+    # Insert
+    insert(db, "run", (uid, id, dtime, area_type, area, stage, difficulty, win, time, mana, energy, crystal, helper))
+
+    # Parse units
+    i = 0
+    for unit in data_request["unit_id_list"]:
+        unit_id = unit["unit_id"]
+        cursor.execute("SELECT unit FROM unit WHERE uid = ? AND id = ?;", (uid, unit_id))
+        unit_master_id = cursor.fetchone()[0]
+        for k_unit in data_response["unit_list"]:
+            if k_unit["unit_id"] == unit["unit_id"]:
+                unit_master_id = k_unit["unit_master_id"];
+                break;
+        if i == 0:
+            leader = 1 # TODO UNKNOWABLE? Check event start
+        else:
+            leader = 0
+        front = 0
+        insert(db, "run_party", (id, unit_master_id, unit_id, leader, front))
+        i += 1
+    db.commit()
+
+
+"""
+Begin script
+"""
+data_request = readData(2)
+data_response = readData(3)
+key = readKey()
+db = openDatabase()
+if verifyKey(db, data_response, key) == False:
+    print("Invalid API KEY...")
+    sys.exit(401)
+else:
+    try:
+        parseRun(db, data_request, data_response)
+    except Exception as e:
+        print("Error parsing TOA run: " + str(e))
+        sys.exit(400)
+sys.exit(201)
+
+

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 125 - 0
application/API/v2/help/api.php


+ 512 - 0
application/API/v2/help/index.php

@@ -0,0 +1,512 @@
+<?php
+    /**
+     * API Help view.
+     *
+     * Shows API documentation.
+     *
+     * @category View
+     */
+?>
+<?php
+    include __DIR__ . "/api.php";
+?>
+<!DOCTYPE html>
+<html lang='en'>
+    <head>
+        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
+        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
+        <title>SWDB APIv2</title>
+        <link rel='shortcut icon' href='<?=URL::IMG["LOGO"]?>sw.png'/>
+        <!-- CSS -->
+        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
+        <style>
+
+            section#navbar{
+                text-align: left;
+                position: fixed;
+                top: 0;
+                left: 0;
+                width: 15%;
+                height: 100%;
+                border-radius: 0;
+                margin: 0 1em 0 0;
+                padding: 0em 0.4em 0.1em 0.4em;
+                border-left: 0;
+                border-top: 0;
+                border-bottom: 0;
+            }
+
+            section#navbar hr{
+                height: 0em;
+                padding: 0;
+                margin: 0 1em;
+                border: 0.01em solid #3105;
+                border-radius: 1em;
+            }
+
+            section#navbar h1{
+                text-align: center;
+                margin-top: 0;
+            }
+
+            section#navbar h1 img{
+                max-width: 100%;
+                display: block;
+                margin: auto auto -1em auto;
+            }
+
+            section#navbar h1 div{
+                text-shadow: 0 0 0.5em #000000, 0 0 0.5em #000000, 0 0 0.2em #000000, 0 0 0.2em #000000;
+                font-family: monospace, monospace;
+            }
+
+            section#navbar h1 div#swdb{
+                font-weight: bold;
+                font-size: 160%;
+                margin-bottom: -0.2em;
+            }
+
+            section#navbar h1 div#api{
+                font-style: italic;
+                font-size: 110%;
+            }
+
+            section#navbar h3 {
+                margin: 0.4em 0 0.2em 1em;
+                border-left: 0.5em solid #3105;
+                border-radius: 0.2em;
+                padding-left: 0.3em;
+                background-color: #00000000;
+                transition: all .3s ease-in-out;
+            }
+
+            section#navbar h3:hover{
+                background-color: #3105;
+            }
+
+            section#navbar h3 a{
+                color: #cccccc
+            }
+
+            section#navbar h3:hover a{
+                color: #ffffff
+            }
+
+            section#navbar ul {
+                margin: 0.2em 0;
+                list-style: none;
+            }
+
+            section#navbar ul li{
+                border-left: 0.5em solid #3105;
+                border-radius: 0.2em;
+                padding-left: 0.3em;
+                margin-bottom: 0.2em;
+                background-color: #00000000;
+                transition: all .3s ease-in-out;
+            }
+
+            section#navbar ul li:hover{
+                background-color: #3105;
+            }
+
+            section#navbar ul li a{
+                color: #cccccc
+            }
+
+            section#navbar ul li:hover a{
+                color: #ffffff
+            }
+
+            section#navbar div#copyright{
+                text-align: center;
+                font-size: 70%;
+                position: absolute;
+                bottom: 1em;
+            }
+
+            section.category{
+                margin: 2em 1em 1em 18%;
+            }
+
+            section.category h2{
+                padding: 0.5em 3em;
+            }
+
+            section.category article{
+                margin: 1.5em;
+                padding-left: 1em;
+                padding-right: 1em;
+                text-align: left;
+            }
+
+            section.category article h3{
+                vertical-align: bottom;
+            }
+
+            section.category article h3 span.description{
+                font-style: italic;
+                font-size: 90%;
+            }
+
+            section.category article:not(:last-child){
+                padding-bottom: 1em;
+                margin-bottom: 1em;
+                border-bottom: 0.1em solid #ffffff66;
+            }
+
+            section.category article div.command span{
+                display: inline-block;
+                border-radius: 0.6em;
+                padding: 0.3em 0.5em;
+                border-width: 0.15em;
+                border-style: solid;
+                color: #ffffff;
+                font-family: monospace, monospace;
+                font-weight: bold;
+            }
+
+            section.category article div.command span.url{
+                background-color: #292b36;
+                border-color: #4b4d57;
+            }
+
+            section.category article div.command span.type{
+                text-transform: uppercase;
+            }
+
+            section.category article div.command span.type_GET {
+                background-color: #008000;
+                border-color: #22a222;
+            }
+
+            section.category article div.command span.type_PUT {
+                background-color: #e5c500;
+                border-color: #f7e722;
+            }
+
+            section.category article div.command span.type_POST {
+                background-color: #4070ec;
+                border-color: #6292fe;
+            }
+
+            section.category article div.command span.type_DELETE {
+                background-color: #ed0039;
+                border-color: #ff225b;
+            }
+
+            section.category article table{
+                border-collapse: collapse;
+                width: 80%;
+                border-radius: 0.5em;
+                margin: 0.5em auto 1em auto;
+            }
+
+            section.category article table th {
+                background-color: #331100;
+                text-align: left;
+                font-weight: bold;
+                padding: 0.5em 1em;
+                border: 0.1px solid #e0e0e0;
+            }
+
+            section.category article table td {
+                background-color: #352413;
+                vertical-align: top;
+                padding: 0.2em 0.4em;
+                border: #e0e0e0 1px solid;
+            }
+
+            section.category article table td.name {
+                font-family: monospace, monospace;
+                font-weight: bold;
+            }
+
+            section.category article table td.type {
+                font-family: monospace, monospace;
+                font-weight: bold;
+            }
+
+            section.category article table td.status {
+                font-family: monospace, monospace;
+                font-weight: bold;
+            }
+
+            section.category article table td.description span.optional {
+                font-size: 90%;
+                color: #bbbbbb;
+                font-style: italic;
+            }
+
+            section#example{
+                position: fixed;
+                display: none;
+                top: 15%;
+                left: 20%;
+                width: 60%;
+                max-width: 90em;
+                height: 70%;
+                max-height: 30em;
+                text-align: right;
+            }
+
+            section#example article{
+                text-align: right;
+            }
+
+            section#example article pre#example_content{
+                text-align: left;
+                background-color: #292b36;
+                border: 0.3em solid #4b4d57;
+                border-radius: 0.8em;
+                padding: 1em;
+                max-height: 20em;
+                overflow: scroll;
+            }
+
+        </style>
+        <!-- Javascript -->
+        <script>
+            function showExample(command, status, example){
+                document.getElementById('example_title').innerHTML = command + " HTTP status " + status + " response example";
+                document.getElementById('example_content').innerHTML = JSON.stringify(JSON.parse(example), null, 4);
+                document.getElementById('example').style.display = 'block';
+            }
+            function closeExample(command, status, example){
+                document.getElementById('example').style.display = 'none';
+            }
+        </script>
+        <!-- Meta tags -->
+        <link rel='canonical' href='<?=URL::BASE?>API/v2/help/'/>
+        <link rel='author' href='SWDB'/>
+        <link rel='publisher' href='SWDB'/>
+        <meta name='description' content='SWDB API v2 Documentation'/>
+        <meta property='og:title' content='SWDB APIv2'/>
+        <meta property='og:url' content='<?=URL::BASE?>API/v2/help/'/>
+        <meta property='og:description' content='SWDB API v2 Documentation'/>
+        <meta property='og:image' content='<?=URL::IMG["LOGO"]?>sw.png'/>
+        <meta property='og:site_name' content='SWDB'/>
+        <meta property='og:type' content='website'/>
+        <meta property='og:locale' content='en'/>
+        <meta name='twitter:card' content='summary'/>
+        <meta name='twitter:title' content='SWDB APIv2'/>
+        <meta name='twitter:description' content='SWDB API v2 Documentation'/>
+        <meta name='twitter:image' content='<?=URL::IMG["LOGO"]?>sw.png'/>
+        <meta name='twitter:url' content='<?=URL::BASE?>API/v2/help/'/>
+        <meta name='robots' content='index follow'/>
+    </head>
+    <body>
+        <section id='navbar' class='content'>
+            <h1>
+                <div id='swdb'>
+                    <img alt=' ' src='<?=URL::IMG["LOGO"]?>sw.png'/>
+                    SWDB
+                </div>
+                <div id='api'>
+                    API v2
+                </div>
+            </h1>
+            <hr/>
+<?php
+            foreach ($API["category"] as $category){
+?>
+                <h3>
+                    <a href='#category_<?=$category["name"]?>'>
+                        <?=$category["name"]?>
+                    </a>
+                </h3>
+                <ul>
+<?php
+                    foreach ($category["command"] as $command){
+?>
+                        <li>
+                            <a href='#command_<?=$command["name"]?>' alt='<?=$command["description"]?>'>
+                                <?=$command["name"]?>
+                            </a>
+                        </li>
+<?php
+                    }
+?>
+                </ul>
+                <hr/>
+<?php
+            }
+?>
+            <div id='copyright'>
+                Version 2.1. Developed by <a href="https://inigovalentin.com">Iñigo Valentin</a>.
+                <br/>
+                <br/>
+                Source code available on <a href="https://github.com">Github</a> under the GPLv3.
+                <br/>
+                <br/>
+                Neither I nor this tool are affiliated with or endorsed by <a href="http://com2us.com/">Com2uS</a> in any way. The Summoners War logo and the monster images are property of their respective owners (Com2US, probably). Monsters images and info are parsed from <a href="https://swarfarm.com">https://swarfarm.com</a>.
+            </div>
+        </section>
+<?php
+        foreach ($API["category"] as $category){
+?>
+            <section id='category_<?=$category["name"]?>' class='category content'>
+                <h2>
+                    <?=$category["name"]?>
+                </h2>
+<?php
+                foreach ($category["command"] as $command){
+?>
+                    <article id='command_<?=$command["name"]?>'>
+                        <h3>
+                            <?=$command["name"]?>
+                            <span class='description'>
+                                - <?=$command["description"]?>
+                            </span>
+                        </h3>
+                        <div class='command'>
+                            <span class='type type_<?=$command["type"]?>'>
+                                <?=$command["type"]?>
+                            </span>
+                            <span class='url'>
+                                <?=htmlspecialchars($command["url"])?>
+                            </span>
+                        </div>
+                        <table class='params'>
+                            <thead>
+                                <tr>
+                                    <th class='header' colspan='3'>
+                                        Parameters
+                                    </th>
+                                </tr>
+                                <tr>
+                                    <th class='name'>
+                                        Parameter
+                                    </th>
+                                    <th class='type'>
+                                        Type
+                                    </th>
+                                    <th class='description'>
+                                        Description
+                                    </th>
+                                </tr>
+                            </thead>
+                            <tbody>
+<?php
+                                foreach ($command["parameter"] as $param){
+?>
+                                    <tr>
+                                        <td class='name'>
+                                            <?=$param["key"]?>
+                                        </td>
+                                        <td class='type'>
+                                            <?=$param["type"]?>
+                                        </td>
+                                        <td class='description'>
+                                            <?=($param["optional"] ? "<span class='optional'>[Optional] </span>" : "")?>
+                                            <?=$param["description"]?>
+                                        </td>
+                                    </tr>
+<?php
+                                }
+?>
+                            </tbody>
+                        </table>
+                        <table class='success'>
+                            <thead>
+                                <tr>
+                                    <th class='header' colspan='2'>
+                                        Sucessfull Responses
+                                    </th>
+                                </tr>
+                                <tr>
+                                    <th class='status'>
+                                        Status
+                                    </th>
+                                    <th class='description'>
+                                        Description
+                                    </th>
+                                </tr>
+                            </thead>
+                            <tbody>
+<?php
+                                foreach ($command["success"] as $status){
+?>
+                                    <tr>
+                                        <td class='status'>
+                                            HTTP/1.1 <?=$status["status"]?>
+                                        </td>
+                                        <td class='description'>
+                                            <?=$status["description"]?>
+<?php
+                                            if ($status["example"] != null){
+?>
+                                                <span class='fake_a' onclick="showExample('<?=$command["name"]?>', <?=$status["status"]?>, '<?=htmlspecialchars(json_decode($status["example"]), ENT_QUOTES)?>')">
+                                                    View example.
+                                                </span>
+<?php
+                                            }
+?>
+                                        </td>
+                                    </tr>
+<?php
+                                }
+?>
+                            </tbody>
+                        </table>
+                        <table class='error'>
+                            <thead>
+                                <tr>
+                                    <th class='header' colspan='2'>
+                                        Error Responses
+                                    </th>
+                                </tr>
+                                <tr>
+                                    <th class='status'>
+                                        Status
+                                    </th>
+                                    <th class='description'>
+                                        Description
+                                    </th>
+                                </tr>
+                            </thead>
+                            <tbody>
+<?php
+                                foreach ($command["error"] as $status){
+?>
+                                    <tr>
+                                        <td class='status'>
+                                            HTTP/1.1 <?=$status["status"]?>
+                                        </td>
+                                        <td class='description'>
+                                            <?=$status["description"]?>
+<?php
+                                            if ($status["example"] != null){
+?>
+                                                <span class='fake_a' onclick="showExample('<?=$command["name"]?>', '<?=htmlspecialchars(json_decode($status["example"]), ENT_QUOTES)?>')">
+                                                    View example.
+                                                </span>
+<?php
+                                            }
+?>
+                                        </td>
+                                    </tr>
+<?php
+                                }
+?>
+                            </tbody>
+                        </table>
+                    </article>
+<?php
+                }
+?>
+            </section>
+            <section id='example' class='content'>
+                <h2 id='example_title'>
+                </h2>
+                <article>
+                    <pre id='example_content'>
+                    </pre>
+                    <input type='button' value='Close' onClick='closeExample();'>
+                </article>
+            </section>
+<?php
+        }
+?>
+    </body>
+</html>

+ 0 - 0
public/API/v1/log-profile/index.php → application/API/v2/log-profile/index.php


+ 0 - 0
public/API/v1/log-run/index.php → application/API/v2/log-run/index.php


+ 17 - 0
application/API/v2/main.php

@@ -0,0 +1,17 @@
+<?php
+    header("Content-Type:application/json");
+    $links = [
+        "Archetypes" => URL::BASE . "/API/v2/archetypes",
+        "Area types" => URL::BASE . "/API/v2/area_types",
+        "Areas types" => URL::BASE . "/API/v2/areas",
+        "Buildings" => URL::BASE . "/API/v2/buildings",
+        "Decorations" => URL::BASE . "/API/v2/decorations",
+        "Areas types" => URL::BASE . "/API/v2/areas",
+        "Units" => URL::BASE . "/API/v2/units",
+        "Skills" => URL::BASE . "/API/v2/skills",
+        "Skill effects" => URL::BASE . "/API/v2/skill_efects",
+    ];
+    header("HTTP/1.1 200");
+    $json_response = json_encode($links);
+    echo $json_response;
+?>

+ 108 - 0
application/API/v2/units.php

@@ -0,0 +1,108 @@
+<?php
+    /**
+     * Unit api.
+     *
+     * Exposes an API to get a list of units of a player, or a unit details.
+     * Used POST parameters are:
+     *  - key: User API key. If not, only public user's unit can be seen.
+     *
+     * @category API
+     * @magic $params URL parameters:
+     *  1: User ID
+     *  2: Unit ID (optional)
+     */
+
+    global $db;
+
+    try{
+
+        if (count($params) <= 1){
+            http_response_code(400);
+            return 400;
+        }
+        $uid = $params[1];
+        $id = null;
+
+        if (count($params) > 2){
+            $id = $params[2];
+        }
+
+        // Check API key or public profile.
+        if (isset($_POST['key'])){
+            $key = filter_input(INPUT_POST, 'key');
+        }
+        else{
+            $key = "";
+        }
+        $s = "SELECT COUNT(uid) AS c FROM player WHERE uid = $uid AND (public = 1 OR api_key = '$key');";
+        if (1 != $db->query($s)->fetchArray(SQLITE3_ASSOC)["c"]){
+            http_response_code(401);
+            return 401;
+        }
+
+        // No unit ID specified, show a list.
+        if ($id == null){
+            $units = [];
+            $s = "SELECT id, '" . URL::BASE . "API/v2/units/$uid/' || id AS uri FROM unit WHERE uid = '$uid'";
+            $q = $db->query($s);
+            $content = false;
+            while ($r = $q->fetchArray(SQLITE3_ASSOC)){
+                array_push($units, $r);
+                $content = true;
+            }
+            if ($content == true){
+                header('Content-Type: application/json');
+                echo json_encode($units);
+                http_response_code(200);
+                return 200;
+            }
+            else{
+                http_response_code(204);
+                return 204;
+            }
+        }
+
+        // Specific unit, show details.
+        else{
+            $s = "SELECT * FROM unit WHERE uid = '$uid' AND id = '$id'";
+            $q = $db->query($s);
+            if($r = $q->fetchArray(SQLITE3_ASSOC)){
+                $unit = $r;
+
+                // Runes
+                $unit["runes"] = [];
+                $s = "SELECT * FROM rune WHERE assigned_to = '" . $unit["id"] . "' ORDER BY slot;";
+                $q = $db->query($s);
+                while ($r = $q->fetchArray(SQLITE3_ASSOC)){
+                    $rune = $r;
+                    unset($rune["uid"]); # Not needed
+                    unset($rune["assigned_to"]); # Not needed
+                    array_push($unit["runes"], $rune);
+                }
+
+                // Skills
+                $unit["skills"] = [];
+                $s = "SELECT skill, level FROM unit_skill WHERE unit = '" . $unit["id"] . "';";
+                $q = $db->query($s);
+                while ($r = $q->fetchArray(SQLITE3_ASSOC)){
+                    array_push($unit["skills"], $r);
+                }
+
+                header('Content-Type: application/json');
+                echo json_encode($unit);
+                http_response_code(200);
+                return 200;
+            }
+            else{
+                http_response_code(404);
+                return 404;
+            }
+        }
+
+    }
+    catch(Exception $e) {
+        error_log("Unknown error fetching units: " . $e->getMessage());
+        http_response_code(500);
+        return 500;
+    }
+?>

+ 94 - 0
application/API/v2/upload_profile.php

@@ -0,0 +1,94 @@
+<?php
+    /**
+     * Profile uploader script.
+     *
+     * Exposes an API to save a run to the database.
+     * Reads post data and calls the upload_profile.py script.
+     * It also saves the data to a JSON file in the data directory.
+     * Mandatory POST parameters are:
+     *  - data: Received JSON file.
+     *  - key: User API key.
+     *
+     * @category API
+     */
+
+    global $db;
+
+    try{
+        // Check data
+        $data = filter_input(INPUT_POST, 'data');
+        if ($data == null || $data == false){
+            http_response_code(400);
+            return 400;
+        }
+
+        // Check API key.
+        $key = filter_input(INPUT_POST, 'key');
+        if ($key == null || $key == false){
+            http_response_code(401);
+            return 401;
+        }
+
+        // Check data format.
+        $json = json_decode($data);
+        if ($json === null){
+            http_response_code(400);
+            return 400;
+        }
+
+        // Authenticate
+        $uid = $json->{"wizard_id"};
+        $uname = $json->{"wizard_info"}->{"wizard_name"};
+        $s = "SELECT COUNT(uid) AS c FROM player WHERE uid = $uid AND api_key = '$key';";
+        if (1 != $db->query($s)->fetchArray(SQLITE3_ASSOC)["c"]){
+            http_response_code(401);
+            return 401;
+        }
+
+        // Write data file
+        $dtime = (new DateTime())->format('Y-m-dTH:i:s');
+        $fname = ($_SERVER["DOCUMENT_ROOT"] . "/../data/profile_" . $uid . "_" . $uname . "_" . $dtime . ".json");
+        try{
+            file_put_contents($fname, $data);
+        }
+        catch(Exception $e) {
+            error_log("Unable to write profile data to '$fname': " . $e->getMessage());
+            http_response_code(500);
+            return 500;
+        }
+
+        // Run profile parser script
+        $cmd = __DIR__ . "/bin/upload_profile.py " . $key . " " . $fname;
+        $out = [];
+        $ret = 0;
+        try{
+            exec($cmd, $out, $ret);
+        }
+        catch(Exception $e) {
+            error_log("Error running profile script '$cmd': " . $e->getMessage());
+            http_response_code(500);
+            return 500;
+        }
+
+        if ($ret != 204){
+            try{
+                http_response_code($ret);
+                return $ret;
+            }
+            catch(Exception $e) {
+                error_log("Profile script '$cmd' returned an unexpected value $ret: " . $e->getMessage());
+                http_response_code(500);
+                return 500;
+            }
+        }
+
+        // At this point, status code should be 200
+        http_response_code($ret);
+        return $ret;
+    }
+    catch(Exception $e) {
+        error_log("Unknown error parsing profile: " . $e->getMessage());
+        http_response_code(500);
+        return 500;
+    }
+?>

+ 83 - 0
application/API/v2/upload_run_dimension.php

@@ -0,0 +1,83 @@
+<?php
+    /**
+     * Dimension Hole Dungeon run logger script.
+     *
+     * Exposes an API to save a run to the database.
+     * Reads post data and calls the upload_run_dimension.py script.
+     * Mandatory POST parameters are:
+     *  - request: Intercepted game request.
+     *  - response: Intercepted game response.
+     *  - key: User API key.
+     *
+     * @category API
+     */
+
+    global $db;
+
+    try{
+        // Check data
+        $data_request = filter_input(INPUT_POST, 'request');
+        $data_response= filter_input(INPUT_POST, 'response');
+        if ($data_request == null || $data_request == false || $data_response == null || $data_response == false){
+            http_response_code(400);
+            return 400;
+        }
+
+        // Check API key.
+        $key = filter_input(INPUT_POST, 'key');
+        if ($key == null || $key == false){
+            http_response_code(401);
+            return 401;
+        }
+
+        // Check data format.
+        $json_request = json_decode($data_request);
+        $json_response = json_decode($data_response);
+        if ($json_response === null | $json_request === null){
+            http_response_code(400);
+            return 400;
+        }
+
+        // Authenticate
+        $uid = $json_request->{"wizard_id"};
+        $uname = $json_response->{"wizard_info"}->{"wizard_name"};
+        $s = "SELECT COUNT(uid) AS c FROM player WHERE uid = $uid AND api_key = '$key';";
+        if (1 != $db->query($s)->fetchArray(SQLITE3_ASSOC)["c"]){
+            http_response_code(401);
+            return 401;
+        }
+
+        // Run TOA run parser script
+        $cmd = __DIR__ . "/bin/upload_run_dimension.py " . $key . " " . escapeshellarg($data_request) . " " . escapeshellarg($data_response);
+        $out = [];
+        $ret = 0;
+        try{
+            exec($cmd, $out, $ret);
+        }
+        catch(Exception $e) {
+            error_log("Error running Dimension Hole Dungeon run script: " . $e->getMessage());
+            http_response_code(500);
+            return 500;
+        }
+
+        if ($ret != 201){
+            try{
+                http_response_code($ret);
+                return $ret;
+            }
+            catch(Exception $e) {
+                error_log("Dimension Hole Dungeon run script returned an unexpected value $ret: " . $e->getMessage());
+                http_response_code(500);
+                return 500;
+            }
+        }
+        // At this point, status code should be 200
+        http_response_code($ret);
+        return $ret;
+    }
+    catch(Exception $e) {
+        error_log("Unknown error parsing Dimension Hole Dungeon run: " . $e->getMessage());
+        http_response_code(500);
+        return 500;
+    }
+?>

+ 93 - 0
application/API/v2/upload_run_dungeon.php

@@ -0,0 +1,93 @@
+<?php
+    /**
+     * Dungeon run logger script.
+     *
+     * Exposes an API to save a run to the database.
+     * Reads post data and calls the upload_dungeon_run.py script.
+     * Mandatory POST parameters are:
+     *  - request: Intercepted game request.
+     *  - response: Intercepted game response.
+     *  - key: User API key.
+     *
+     * @category API
+     */
+
+    global $db;
+
+//foreach($_POST as $k => $v) {
+//    error_log(" POST $k: $v");
+//}
+
+
+    try{
+        // Check data
+        $data_request = filter_input(INPUT_POST, 'request');
+        $data_response= filter_input(INPUT_POST, 'response');
+        if ($data_request == null || $data_request == false || $data_response == null || $data_response == false){
+error_log("error1");
+            http_response_code(400);
+            return 400;
+        }
+
+        // Check API key.
+        $key = filter_input(INPUT_POST, 'key');
+        if ($key == null || $key == false){
+            http_response_code(401);
+            return 401;
+        }
+
+        // Check data format.
+        $json_request = json_decode($data_request);
+        $json_response = json_decode($data_response);
+        if ($json_response === null | $json_request === null){
+error_log("error2");
+            http_response_code(400);
+            return 400;
+        }
+
+        // Authenticate
+        $uid = $json_request->{"wizard_id"};
+        $uname = $json_response->{"wizard_info"}->{"wizard_name"};
+        $s = "SELECT COUNT(uid) AS c FROM player WHERE uid = $uid AND api_key = '$key';";
+        if (1 != $db->query($s)->fetchArray(SQLITE3_ASSOC)["c"]){
+            http_response_code(401);
+            return 401;
+        }
+
+        // Run dungeon run parser script
+        $cmd = __DIR__ . "/bin/upload_run_dungeon.py " . $key . " " . escapeshellarg($data_request) . " " . escapeshellarg($data_response);
+        $out = [];
+        $ret = 0;
+//error_log($cmd);
+        try{
+            exec($cmd, $out, $ret);
+        }
+        catch(Exception $e) {
+            error_log("Error running dungeon run script: " . $e->getMessage());
+            http_response_code(500);
+            return 500;
+        }
+
+        if ($ret != 201){
+            try{
+                http_response_code($ret);
+error_log("NO 201: $ret");
+                return $ret;
+            }
+            catch(Exception $e) {
+                error_log("Dungeon run script returned an unexpected value $ret: " . $e->getMessage());
+                http_response_code(500);
+                return 500;
+            }
+        }
+error_log("ACTUAL RET: " . $ret);
+        // At this point, status code should be 200
+        http_response_code($ret);
+        return $ret;
+    }
+    catch(Exception $e) {
+        error_log("Unknown error parsing dungeon run: " . $e->getMessage());
+        http_response_code(500);
+        return 500;
+    }
+?>

+ 81 - 0
application/API/v2/upload_run_scenario.php

@@ -0,0 +1,81 @@
+<?php
+    /**
+     * Scenario run logger script.
+     *
+     * Exposes an API to save a run to the database.
+     * Reads post data and calls the upload_scenario_run.py script.
+     * Mandatory POST parameters are:
+     *  - data: Received JSON file after a run.
+     *  - key: User API key.
+     *
+     * @category API
+     */
+
+    global $db;
+
+    try{
+        // Check data
+        $data = filter_input(INPUT_POST, 'data');
+        if ($data == null || $data == false){
+            http_response_code(400);
+            return 400;
+        }
+
+        // Check API key.
+        $key = filter_input(INPUT_POST, 'key');
+        if ($key == null || $key == false){
+            http_response_code(401);
+            return 401;
+        }
+
+        // Check data format.
+        $json = json_decode($data);
+        if ($json === null){
+            http_response_code(400);
+            return 400;
+        }
+
+        // Authenticate
+        $uid = $json->{"wizard_id"};
+        $uname = $json->{"wizard_info"}->{"wizard_name"};
+        $s = "SELECT COUNT(uid) AS c FROM player WHERE uid = $uid AND api_key = '$key';";
+        if (1 != $db->query($s)->fetchArray(SQLITE3_ASSOC)["c"]){
+            http_response_code(401);
+            return 401;
+        }
+
+        // Run scenario run parser script
+        $cmd = __DIR__ . "/bin/upload_run_scenario.py " . $key . " " . escapeshellarg($data);;
+        $out = [];
+        $ret = 0;
+        try{
+            exec($cmd, $out, $ret);
+        }
+        catch(Exception $e) {
+            error_log("Error running scenario run script '$cmd': " . $e->getMessage());
+            http_response_code(500);
+            return 500;
+        }
+
+        if ($ret != 200){
+            try{
+                http_response_code($ret);
+                return $ret;
+            }
+            catch(Exception $e) {
+                error_log("Dungeon run script '$cmd' returned an unexpected value $ret: " . $e->getMessage());
+                http_response_code(500);
+                return 500;
+            }
+        }
+
+        // At this point, status code should be 200
+        http_response_code($ret);
+        return $ret;
+    }
+    catch(Exception $e) {
+        error_log("Unknown error parsing scenario run: " . $e->getMessage());
+        http_response_code(500);
+        return 500;
+    }
+?>

+ 83 - 0
application/API/v2/upload_run_toa.php

@@ -0,0 +1,83 @@
+<?php
+    /**
+     * Dungeon run logger script.
+     *
+     * Exposes an API to save a run to the database.
+     * Reads post data and calls the upload_dungeon_run.py script.
+     * Mandatory POST parameters are:
+     *  - request: Intercepted game request.
+     *  - response: Intercepted game response.
+     *  - key: User API key.
+     *
+     * @category API
+     */
+
+    global $db;
+
+    try{
+        // Check data
+        $data_request = filter_input(INPUT_POST, 'request');
+        $data_response= filter_input(INPUT_POST, 'response');
+        if ($data_request == null || $data_request == false || $data_response == null || $data_response == false){
+            http_response_code(400);
+            return 400;
+        }
+
+        // Check API key.
+        $key = filter_input(INPUT_POST, 'key');
+        if ($key == null || $key == false){
+            http_response_code(401);
+            return 401;
+        }
+
+        // Check data format.
+        $json_request = json_decode($data_request);
+        $json_response = json_decode($data_response);
+        if ($json_response === null | $json_request === null){
+            http_response_code(400);
+            return 400;
+        }
+
+        // Authenticate
+        $uid = $json_request->{"wizard_id"};
+        $uname = $json_response->{"wizard_info"}->{"wizard_name"};
+        $s = "SELECT COUNT(uid) AS c FROM player WHERE uid = $uid AND api_key = '$key';";
+        if (1 != $db->query($s)->fetchArray(SQLITE3_ASSOC)["c"]){
+            http_response_code(401);
+            return 401;
+        }
+
+        // Run TOA run parser script
+        $cmd = __DIR__ . "/bin/upload_run_toa.py " . $key . " " . escapeshellarg($data_request) . " " . escapeshellarg($data_response);
+        $out = [];
+        $ret = 0;
+        try{
+            exec($cmd, $out, $ret);
+        }
+        catch(Exception $e) {
+            error_log("Error running TOA run script: " . $e->getMessage());
+            http_response_code(500);
+            return 500;
+        }
+
+        if ($ret != 201){
+            try{
+                http_response_code($ret);
+                return $ret;
+            }
+            catch(Exception $e) {
+                error_log("TOA run script returned an unexpected value $ret: " . $e->getMessage());
+                http_response_code(500);
+                return 500;
+            }
+        }
+        // At this point, status code should be 200
+        http_response_code($ret);
+        return $ret;
+    }
+    catch(Exception $e) {
+        error_log("Unknown error parsing TOA run: " . $e->getMessage());
+        http_response_code(500);
+        return 500;
+    }
+?>

+ 25 - 2
application/Controller.php

@@ -23,7 +23,7 @@
      * @global int $UID.
      * @global int $UID.
      */
      */
     $UID = 0;
     $UID = 0;
-    
+
     /**
     /**
      * @global resource $DB.
      * @global resource $DB.
      */
      */
@@ -68,9 +68,32 @@
                 }
                 }
             }
             }
 
 
+            // API call: Use API controller
+            if (count($pars) > 0 && strtoupper($pars[0]) == "API"){
+                if (count($pars) == 1){
+                    // TODO: API MAIN PAGE
+                }
+                else{
+                    $version = strtolower($pars[1]);
+                    $api_params = $pars;
+                    array_shift($api_params);
+                    array_shift($api_params);
+                    switch ($version){
+                        case "v1":
+                            require_once(__DIR__ . "/API/v1/API_Controller.php");
+                            new API_Controller($params);
+                            break;
+                        case "v2":
+                            require_once(__DIR__ . "/API/v2/API_Controller.php");
+                            new API_Controller($params);
+                            break;
+                    }
+                }
+                return;
+            }
             // Special case: Actions.
             // Special case: Actions.
             // Not redirecting to page, just execute a function
             // Not redirecting to page, just execute a function
-            if (count($pars) > 1 && strtoupper($pars[0]) == "ACTION"){
+            elseif (count($pars) > 1 && strtoupper($pars[0]) == "ACTION"){
                 switch (strtoupper($pars[1])){
                 switch (strtoupper($pars[1])){
                     case "LOGIN":
                     case "LOGIN":
                         require_once(PATH::ACTION . "login.php");
                         require_once(PATH::ACTION . "login.php");

+ 6 - 1
install_data/install_data.sql

@@ -29,7 +29,8 @@ CREATE TABLE player(
     honor_mark INT NOT NULL CHECK (honor_mark >= 0),
     honor_mark INT NOT NULL CHECK (honor_mark >= 0),
     event_coin INT NOT NULL CHECK (event_coin >= 0),
     event_coin INT NOT NULL CHECK (event_coin >= 0),
     storage_slots INT NOT NULL CHECK (storage_slots >= 0),
     storage_slots INT NOT NULL CHECK (storage_slots >= 0),
-    island INT NOT NULL CHECK (island >= 0)
+    island INT NOT NULL CHECK (island >= 0),
+    public INT NOT NULL DEFAULT 0 CHECK(public IN (0, 1))
 );
 );
 CREATE TABLE scenario(
 CREATE TABLE scenario(
     uid INT NOT NULL REFERENCES player(id),
     uid INT NOT NULL REFERENCES player(id),
@@ -158,6 +159,8 @@ CREATE TABLE run_party(
     run INT NOT NULL REFERENCES run(id),
     run INT NOT NULL REFERENCES run(id),
     unit INT NOT NULL REFERENCES unit(id),
     unit INT NOT NULL REFERENCES unit(id),
     k_unit INT NOT NULL REFERENCES k_unit(id),
     k_unit INT NOT NULL REFERENCES k_unit(id),
+    leader INT NOT NULL DEFAULT 0,
+    front INT NOT NULL DEFAULT 0,
     PRIMARY KEY (run, unit)
     PRIMARY KEY (run, unit)
 );
 );
 CREATE TABLE run_drop_rune_craft(
 CREATE TABLE run_drop_rune_craft(
@@ -200,6 +203,7 @@ CREATE TABLE run(
     uid INT NOT NULL REFERENCES player(id),
     uid INT NOT NULL REFERENCES player(id),
     id INT NOT NULL PRIMARY KEY,
     id INT NOT NULL PRIMARY KEY,
     dtime TIMESTAMP NOT NULL,
     dtime TIMESTAMP NOT NULL,
+    area_type INT NOT NULL REFERENCES k_area_type,
     area INT NOT NULL REFERENCES k_area,
     area INT NOT NULL REFERENCES k_area,
     stage INT CHECK(stage > 0),
     stage INT CHECK(stage > 0),
     difficulty INT REFERENCES k_difficulty(id),
     difficulty INT REFERENCES k_difficulty(id),
@@ -220,6 +224,7 @@ CREATE TABLE run_drop_rune(
     quality INT NOT NULL REFERENCES k_quality(id),
     quality INT NOT NULL REFERENCES k_quality(id),
     value INT NOT NULL CHECK(value > 0),
     value INT NOT NULL CHECK(value > 0),
     efficiency FLOAT NOT NULL CHECK(efficiency BETWEEN 0 AND 100),
     efficiency FLOAT NOT NULL CHECK(efficiency BETWEEN 0 AND 100),
+    max_efficiency FLOAT NOT NULL CHECK(max_efficiency BETWEEN 0 AND 100),
     main_stat INT NOT NULL REFERENCES k_rune_stat(id),
     main_stat INT NOT NULL REFERENCES k_rune_stat(id),
     main_stat_value INT NOT NULL CHECK(value > 0),
     main_stat_value INT NOT NULL CHECK(value > 0),
     innate_stat INT REFERENCES k_rune_stat(id),
     innate_stat INT REFERENCES k_rune_stat(id),

+ 0 - 35
phpdoc.xml

@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<phpdoc>
-    <title>SWDB</title>
-    <parser>
-        <target>doc/.tmp</target>
-        <encoding>utf8</encoding>
-        <markers>
-            <item>TODO</item>
-            <item>FIXME</item>
-        </markers>
-        <extensions>
-            <extension>php</extension>
-            <extension>php3</extension>
-            <extension>phtml</extension>
-        </extensions>
-        <visibility></visibility>
-    </parser>
-    <transformer>
-        <target>doc</target>
-    </transformer>
-    <!--<logging>
-        <level>warn</level>
-        <paths>
-            <default>{APP_ROOT}/data/log/{DATE}.log</default>
-            <errors>{APP_ROOT}/data/log/{DATE}.errors.log</errors>
-        </paths>
-    </logging>-->
-    <transformations>
-        <!--<template name="responsive" />-->
-    </transformations>
-    <files>
-        <directory>.</directory>
-        <ignore>util/</ignore>
-    </files>
-</phpdoc>

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 291 - 687
swex-plugin/swdb.js


Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott