Beispiel #1
0
        /// <summary>
        /// Construct a ModDirectoryEntry from the stored KV info.
        /// Thankfully, this one at least doesn't need crypto signing...I think
        /// </summary>
        /// <param name="des">The keyvalue object describing the ModDirectoryEntry.</param>
        public ModDirectoryEntry(KV.KeyValue des)
        {
            name = des.Key;
            foreach (KV.KeyValue kv in des.Children)
            {
                switch (kv.Key)
                {
                case "version":
                    version = new Version(kv.GetString());
                    break;

                case "downloadurl":
                    downloadurl = kv.GetString();
                    break;

                case "dependency":
                    dependencies.Add(kv.GetString());
                    break;

                case "requiresource2":
                    requiresource2 = kv.GetBool();
                    break;

                case "core":
                    core = kv.GetBool();
                    break;

                default:
                    // no default rule here
                    break;
                }
            }
        }
Beispiel #2
0
        /// <summary>
        /// Construct a ModDirectoryList from a file listing installed ModDirectories.
        /// </summary>
        /// <param name="descriptor"></param>
        public ModDirectoryList(KV.KeyValue descriptor)
        {
            foreach (KV.KeyValue node in descriptor.Children)
            {
                try
                {
                    string name         = node.Key;
                    Uri    directoryuri = null;
                    Uri    versionuri   = null;
                    foreach (KV.KeyValue prop in node.Children)
                    {
                        switch (prop.Key)
                        {
                        case "version":
                            versionuri = new Uri(prop.GetString());
                            break;

                        case "directory":
                            directoryuri = new Uri(prop.GetString());
                            break;

                        default:
                            // ignore
                            break;
                        }
                    }
                    Uri uri = new Uri(node.GetString());
                    directories.Add(new Tuple <string, Uri, Uri>(name, directoryuri, versionuri));
                } catch (Exception)
                {
                    Console.WriteLine("Encountered a poor entry in the directory list, skipping...");
                }
            }
        }
Beispiel #3
0
        /// <summary>
        /// Checks gameinfo.txt (and later gameinfo.gi) to make sure that we are
        /// actually overriding the vpk, and our changes haven't been nuked by a
        /// "verify local game cache" use.
        /// </summary>
        public void CheckGameInfo()
        {
            KV.KeyValue gameinfo = null;
            if (File.Exists(dotapath + "/dota/gameinfo.txt"))
            {
                gameinfo = KV.KVParser.ParseKeyValueFile(dotapath + "/dota/gameinfo.txt");
            }
            Console.WriteLine(gameinfo.ToString());
            // Make sure our entry is there
            bool found = false;

            if (gameinfo != null && gameinfo["FileSystem"] != null && gameinfo["FileSystem"]["SearchPaths"] != null)
            {
                foreach (KV.KeyValue f in gameinfo["FileSystem"]["SearchPaths"].Children)
                {
                    if (f.Key == "Game" && f.GetString() == "moddota")
                    {
                        found = true;
                        break;
                    }
                }
            }
            if (!found)
            {
                gameinfo = GetFixedGameInfo();
                // Write to the file
                File.WriteAllText(dotapath + "/dota/gameinfo.txt", gameinfo.ToString());
            }
        }
Beispiel #4
0
 /// <summary>
 /// Checks the signature of a data file, and returns null if it fails, or the data if it succeeds.
 /// </summary>
 /// <param name="data">The file KV.</param>
 /// <returns>True if the signature check passes, false otherwise.</returns>
 public bool CheckSignature(KV.KeyValue data)
 {
     try
     {
         if (data["signatureinfo"] == null || data["body"] == null)
         {
             Console.WriteLine("Couldn't find the necessary signature information or body block of checked KV, failing signature check.");
             return(false);
         }
         if (data["signatureinfo"]["certificate"] == null)
         {
             Console.WriteLine("Couldn't find the certificate in the specified KV for signature check, failing signature check.");
             return(false);
         }
         if (data["signatureinfo"]["signature"] == null)
         {
             Console.WriteLine("Couldn't find the signature in the specifeid KV, failing signature check.");
             return(false);
         }
         X509Certificate2 certificate = new X509Certificate2(Convert.FromBase64String(data["signatureinfo"]["certificate"].Value));
         bool             isValid     = certchain.Build(certificate);
         if (!isValid)
         {
             Console.WriteLine("Unable to validate certificate chain, failing signature check.");
             return(false);
         }
         // Get the contents nice and ready to be checked.
         string contents     = data["body"].ToString();
         byte[] contentbytes = new byte[contents.Length * sizeof(char)];
         System.Buffer.BlockCopy(contents.ToCharArray(), 0, contentbytes, 0, contentbytes.Length);
         // Get the signature decoded.
         byte[] signaturebytes = Convert.FromBase64String(data["signatureinfo"]["signature"].Value);
         var    rsa            = certificate.PublicKey.Key as RSACryptoServiceProvider;
         // Check the signature!
         bool verified = rsa.VerifyData(contentbytes, "SHA256", signaturebytes);
         if (!verified)
         {
             Console.WriteLine("Signature check failed to match signature to signed data, failing signature check.");
             return(false);
         }
         // Well, it all checks out.
         return(true);
     } catch (Exception e)
     {
         Console.WriteLine("Unexpected exception in signature check, failing signature check.");
         Console.WriteLine("Exception was " + e.ToString());
         return(false);
     }
 }
Beispiel #5
0
        /// <summary>
        /// Load the specifications of all installed mods.
        /// </summary>
        /// <returns>A List of ModSpecifications, one per installed mod.</returns>
        List <ModSpecification> LoadModSpecifications()
        {
            // Mod specifications are kv files stored in /moddota/mods/
            // the specifications just list filename keys, containing a CRC kv each (and maybe a path?)
            // there's also a single "version" key, which lists the version of the mod (for comparison with md)
            string folderpath = dotapath + "/moddota/mods/";

            if (!Directory.Exists(folderpath))
            {
                Directory.CreateDirectory(folderpath);
            }
            IEnumerable <string>    mods     = Directory.EnumerateFiles(folderpath, "*.mod");
            List <ModSpecification> modspecs = new List <ModSpecification>();

            foreach (string modfilename in mods)
            {
                Console.WriteLine("Parsing data for mod at " + modfilename);
                KV.KeyValue thisone = null;
                try
                {
                    thisone = KV.KVParser.ParseKeyValueFile(modfilename);
                }
                catch (KV.KVParser.KeyValueParsingException)
                {
                    Console.WriteLine("Error while KV parsing " + modfilename);
                }
                if (thisone == null)
                {
                    // should queue-up re-download?
                    continue;
                }
                ModSpecification ms = new ModSpecification(thisone);
                ms.name    = thisone.Key;
                ms.version = new Version();
                ms.files   = new List <ModResource>();
                foreach (KV.KeyValue k in thisone.Children)
                {
                    if (k.Key == "version")
                    {
                        ms.version = new Version(k.GetString());
                    }
                    if (k.Key == "resource")
                    {
                        ms.files.Add(new ModResource(k));
                    }
                }
            }
            return(modspecs);
        }
        /// <summary>
        /// Construct a ModSpecification from the base data.
        /// </summary>
        /// <param name="sourcedata">The KeyValue from which this ModSpecification is to be constructed.</param>
        public ModSpecification(KV.KeyValue sourcedata)
        {
            if (!ModDotaHelper.modman.CCV.CheckSignature(sourcedata))
            {
                throw new CryptoChainValidator.SignatureException();
            }
            foreach (KV.KeyValue kv in sourcedata["body"].Children)
            {
                switch (kv.Key)
                {
                case "modinfo":
                    break;

                default:
                    files.Add(new ModResource(kv));
                    break;
                }
            }
        }
Beispiel #7
0
        /// <summary>
        /// Construct a ModDirectory from the stored KV info
        /// </summary>
        /// <param name="des">The KV object containing the keyvalue structure as well as the signature for the moddirectory.</param>
        /// <param name="host">The host from which the ModDirectory was fetched, used for validation.</param>
        /// <exception cref="ModDotaHelper.ModDirectory.ModDirectorySignatureException">If the directory's signature is bad.</exception>
        public ModDirectory(KV.KeyValue des, string host)
        {
            bool passedvalidation = ModDotaHelper.modman.CCV.CheckSignature(des);

            if (!passedvalidation)
            {
                throw new CryptoChainValidator.SignatureException();
            }
            foreach (KV.KeyValue kv in des["body"].Children)
            {
                switch (kv.Key)
                {
                case "version":
                    version = new Version(kv.GetString());
                    break;

                default:
                    entries.Add(new ModDirectoryEntry(kv));
                    break;
                }
            }
        }
Beispiel #8
0
        /// <summary>
        /// Construct from a kv node, used when parsing .mod files.
        /// </summary>
        /// <param name="k"></param>
        public ModResource(KV.KeyValue k)
        {
            foreach (KV.KeyValue v in k.Children)
            {
                switch (v.Key)
                {
                case "CRC":
                    CRC = UInt32.Parse(v.GetString());
                    break;

                case "internalpath":
                    internalpath = v.GetString();
                    break;

                case "downloadurl":
                    downloadurl = v.GetString();
                    break;

                case "signature":
                    signature = v.GetString();
                    break;
                }
            }
        }
Beispiel #9
0
        /// <summary>
        /// Get the mod list from the local cache.
        /// </summary>
        /// <param name="nodownload">If true, don't download new versions for missing/incorrect/outdated directories.</param>
        /// <param name="forcereacquire">If true, force a new download of all directories.</param>
        public ModDirectory GetModDirectories(bool nodownload = false, bool forcereacquire = false)
        {
            // First get the list of directories we need to look at
            if (!File.Exists(dotapath + "/moddota/directories.kv"))
            {
                return(null);
            }
            string contents = null;

            try
            {
                contents = File.ReadAllText(dotapath + "/moddota/directories.kv");
            }
            catch (Exception)
            {
                Console.WriteLine("Couldn't access directories file!");
            }
            KV.KeyValue kv = null;
            try
            {
                kv = KV.KVParser.ParseKeyValue(contents);
            }
            catch (KV.KVParser.KeyValueParsingException)
            {
                Console.WriteLine("poorly formatted kv file for directories, using default");
                kv = new KV.KeyValue("directories");
                KV.KeyValue defaultentry = new KV.KeyValue("moddota");

                KV.KeyValue defaultdirectory = new KV.KeyValue("directory");
                defaultdirectory.Set("https://moddota.com/mdc/directory.kv");
                defaultentry.AddChild(defaultdirectory);

                KV.KeyValue defaultversion = new KV.KeyValue("version");
                defaultversion.Set("https://moddota.com/mdc/directory.version");
                defaultentry.AddChild(defaultversion);

                kv.AddChild(defaultentry);
            }
            ModDirectoryList mdl    = new ModDirectoryList(kv);
            ModDirectory     basemd = new ModDirectory();

            foreach (Tuple <string, Uri, Uri> tpl in mdl.directories)
            {
                try
                {
                    string directoryname = dotapath + "/moddota/dirs/" + tpl.Item1 + ".dir";
                    bool   trieddownload = false;
                    // If we don't have that particular directory, force a re-acquire
                    if (!File.Exists(directoryname) || forcereacquire)
                    {
                        if (nodownload)
                        {
                            // don't have it and can't get it, just go to the next one.
                            continue;
                        }
                        else
                        {
                            TryDownloadModDirectory(tpl.Item1, tpl.Item2);
                            trieddownload = true;
                        }
                    }
                    ModDirectory md = null;
                    // Try to parse the file, since we have one
                    try
                    {
                        md = new ModDirectory(KV.KVParser.ParseKeyValueFile(directoryname), tpl.Item2.Host);
                    }
                    catch (CryptoChainValidator.SignatureException)
                    {
                        // The signature was wrong.
                        if (nodownload)
                        {
                            // Since we can't download a new version, we might as well just give up on this one
                            continue;
                        }
                        else
                        {
                            md            = TryDownloadModDirectory(tpl.Item1, tpl.Item2);
                            trieddownload = true;
                        }
                    }
                    catch (KV.KVParser.KeyValueParsingException)
                    {
                        // The format was wrong.
                        if (nodownload)
                        {
                            // Since we can't download a new version, we might as well just give up on this one
                            continue;
                        }
                        else
                        {
                            md            = TryDownloadModDirectory(tpl.Item1, tpl.Item2);
                            trieddownload = true;
                        }
                    }
                    // Version check - don't check the version if we aren't going to download a new one anyway, and don't check it if we already just downloaded it.
                    if (!nodownload && !trieddownload)
                    {
                        Version remoteversion;
                        try
                        {
                            using (WebClient client = new WebClient())
                            {
                                remoteversion = new Version(client.DownloadString(tpl.Item3));
                            }
                            if (remoteversion > md.version)
                            {
                                if (nodownload)
                                {
                                    // don't actually skip this time - we just let people play offline with the old version.
                                }
                                else
                                {
                                    ModDirectory newmd = TryDownloadModDirectory(tpl.Item1, tpl.Item2);
                                    if (newmd != null)
                                    {
                                        md = newmd;
                                    }
                                    trieddownload = true;
                                }
                            }
                        }
                        catch (Exception)
                        {
                            Console.WriteLine("Encountered exception while parsing files for " + tpl.Item1 + "'s directory version information");
                        }
                    }
                    basemd.add(md);
                }
                catch (Exception)
                {
                    Console.WriteLine("Failed to acquire or parse the ModDirectory from " + tpl.Item2.ToString());
                }
            }
            return(basemd);
        }
Beispiel #10
0
        /// <summary>
        /// Get a gameinfo with our override added.
        /// </summary>
        /// <returns>A KV structure describing the gameinfo.</returns>
        private static KV.KeyValue GetFixedGameInfo()
        {
            // This seemed like a good way to do it at the time... remind me to
            // add a more syntactically-compact way of doing this or just use a
            // string representation instead - this is 3x longer and 3000x more
            // complex/unreadable/error-prone.
            KV.KeyValue gameinfo = new KV.KeyValue("GameInfo");
            KV.KeyValue game     = new KV.KeyValue("game");
            game.Set("DOTA 2");
            gameinfo.AddChild(game);
            KV.KeyValue gamelogo = new KV.KeyValue("gamelogo");
            gamelogo.Set(1);
            gameinfo.AddChild(gamelogo);
            KV.KeyValue type = new KV.KeyValue("type");
            type.Set("multiplayer_only");
            gameinfo.AddChild(type);
            KV.KeyValue nomodels = new KV.KeyValue("nomodels");
            nomodels.Set(1);
            gameinfo.AddChild(nomodels);
            KV.KeyValue nohimodel = new KV.KeyValue("nohimodel");
            nohimodel.Set(1);
            gameinfo.AddChild(nohimodel);
            KV.KeyValue nocrosshair = new KV.KeyValue("nocrosshair");
            nocrosshair.Set(0);
            gameinfo.AddChild(nocrosshair);
            KV.KeyValue gamedata = new KV.KeyValue("GameData");
            gamedata.Set("dota.fgd");
            gameinfo.AddChild(gamedata);
            KV.KeyValue supportsdx8 = new KV.KeyValue("SupportsDX8");
            supportsdx8.Set(0);
            gameinfo.AddChild(supportsdx8);

            KV.KeyValue filesystem = new KV.KeyValue("FileSystem");
            KV.KeyValue SteamAppId = new KV.KeyValue("SteamAppId");
            SteamAppId.Set(816);
            filesystem.AddChild(SteamAppId);
            KV.KeyValue ToolsAppId = new KV.KeyValue("ToolsAppId");
            ToolsAppId.Set(211);
            filesystem.AddChild(ToolsAppId);

            KV.KeyValue SearchPaths = new KV.KeyValue("SearchPaths");
            KV.KeyValue game0       = new KV.KeyValue("Game");
            game0.Set("moddota");
            SearchPaths.AddChild(game0);
            KV.KeyValue game1 = new KV.KeyValue("Game");
            game1.Set("|gameinfo_path|.");
            SearchPaths.AddChild(game1);
            KV.KeyValue game2 = new KV.KeyValue("Game");
            game2.Set("platform");
            SearchPaths.AddChild(game2);

            filesystem.AddChild(SearchPaths);

            gameinfo.AddChild(filesystem);

            KV.KeyValue ToolsEnvironment = new KV.KeyValue("ToolsEnvironment");
            KV.KeyValue Engine           = new KV.KeyValue("Engine");
            Engine.Set("Souce");
            ToolsEnvironment.AddChild(Engine);
            KV.KeyValue UseVPlatform = new KV.KeyValue("UseVPlatform");
            UseVPlatform.Set(1);
            ToolsEnvironment.AddChild(UseVPlatform);
            KV.KeyValue PythonVersion = new KV.KeyValue("PythonVersion");
            PythonVersion.Set("2.7");
            ToolsEnvironment.AddChild(PythonVersion);
            KV.KeyValue PythonHomeDisable = new KV.KeyValue("PythonHomeDisable");
            PythonVersion.Set(1);
            ToolsEnvironment.AddChild(PythonVersion);

            gameinfo.AddChild(ToolsEnvironment);

            return(gameinfo);
        }
Beispiel #11
0
        /// <summary>
        /// Grab all of the keyvalues from a string.
        /// </summary>
        /// <param name="contents">The string containing keyvalues</param>
        /// <param name="allowunnamedkeys">Whether or not to allow unnamed blocks (used in bsp entity lump)</param>
        /// <returns>An array containing all root-level KeyValues in the string</returns>
        /// <exception cref="ModDotaHelper.KV.KVParser.KeyValueParsingException">Throws one of these if parsing fails</exception>
        public static KeyValue[] ParseAllKeyValues(string contents, bool allowunnamedkeys = false)
        {
            if (contents == null)
            {
                throw new KeyValueParsingException("Contents string was null!", new ArgumentNullException());
            }
            try
            {
                parseEnum parseState = parseEnum.lookingForKey;
                KeyValue  basekv     = new KeyValue("base"); // file contents are interpreted as children of this keyvalue
                KeyValue  curparent  = basekv;
                for (int i = 0; i < contents.Length; i++)
                {
                    // go until next symbol
                    if (contents[i] == ' ' || contents[i] == '\t' || contents[i] == '\n' || contents[i] == '\r')
                    {
                        continue;
                    }
                    switch (parseState)
                    {
                    case parseEnum.lookingForKey:
                        if (contents[i] == '{')
                        {
                            if (!allowunnamedkeys)
                            {
                                throw new KeyValueParsingException("Hit unnamed key while parsing without unnamed keys enabled.", null);
                            }
                            // This is a special case - some kv files, in particular bsp entity lumps, have unkeyed kvs
                            KeyValue cur = new KeyValue("UNNAMED");
                            curparent.AddChild(cur);
                            curparent  = cur;
                            parseState = parseEnum.lookingForValue;
                        }
                        else if (contents[i] == '"' || contents[i] == '\'')
                        {
                            //quoted key
                            int j = i + 1;
                            if (j >= contents.Length)
                            {
                                throw new KeyValueParsingException("Couldn't find terminating '" + contents[i].ToString() + "' for key started at position " + i.ToString(), null);
                            }
                            while (contents[j] != contents[i])
                            {
                                // handle escaped quotes
                                if (contents[j] == '\\')
                                {
                                    j++;
                                }
                                j++;
                                if (j >= contents.Length)
                                {
                                    throw new KeyValueParsingException("Couldn't find terminating '" + contents[i].ToString() + "' for key started at position " + i.ToString(), null);
                                }
                            }
                            //ok, now contents[i] and contents[j] are the same character, on either end of the key
                            KeyValue cur = new KeyValue(contents.Substring(i + 1, j - (i + 1)));
                            curparent.AddChild(cur);
                            curparent  = cur;
                            parseState = parseEnum.lookingForValue;
                            i          = j;
                        }
                        else if (Char.IsLetter(contents[i]))
                        {
                            //un-quoted key
                            int j = i;
                            while (contents[j] != ' ' && contents[j] != '\t' && contents[j] != '\n' && contents[j] != '\r')
                            {
                                j++;
                                if (j > contents.Length)
                                {
                                    throw new KeyValueParsingException("Couldn't find end of key started at position " + i.ToString(), null);
                                }
                            }
                            KeyValue cur = new KeyValue(contents.Substring(i, j - i));
                            curparent.AddChild(cur);
                            curparent  = cur;
                            parseState = parseEnum.lookingForValue;
                            i          = j;
                        }
                        else if (contents[i] == '}')
                        {
                            //drop one level
                            curparent = curparent.Parent;
                        }
                        else if (contents[i] == '/')
                        {
                            if (i + 1 < contents.Length && contents[i + 1] == '/')
                            {
                                // we're in a comment! throw stuff away until the next \n
                                while (i < contents.Length && contents[i] != '\n')
                                {
                                    i++;
                                }
                            }
                        }
                        else
                        {
                            throw new KeyValueParsingException("Unexpected '" + contents[i].ToString() + "' at position " + i.ToString(), null);
                        }
                        break;

                    case parseEnum.lookingForValue:
                        if (contents[i] == '{')
                        {
                            // it's a list of children
                            // thankfully, we don't actually need to handle this!
                            parseState = parseEnum.lookingForKey;
                        }
                        else if (contents[i] == '"' || contents[i] == '\'')
                        {
                            //quoted value
                            int j = i + 1;
                            while (contents[j] != contents[i])
                            {
                                // handle escaped quotes
                                if (contents[j] == '\\')
                                {
                                    j++;
                                }
                                j++;
                                if (j > contents.Length)
                                {
                                    throw new KeyValueParsingException("Couldn't find terminating '" + contents[i].ToString() + "' for key started at position " + i.ToString(), null);
                                }
                            }
                            //ok, now contents[i] and contents[j] are the same character, on either end of the value
                            curparent.Set(contents.Substring(i + 1, j - (i + 1)));
                            curparent  = curparent.Parent;
                            parseState = parseEnum.lookingForKey;
                            i          = j;
                        }
                        else if (contents[i] == '/')
                        {
                            if (i + 1 < contents.Length && contents[i + 1] == '/')
                            {
                                // we're in a comment! throw stuff away until the next \n
                                while (i < contents.Length && contents[i] != '\n')
                                {
                                    i++;
                                }
                            }
                        }
                        else if (!Char.IsWhiteSpace(contents[i]))
                        {
                            int j = i;
                            while (contents[j] != ' ' && contents[j] != '\t' && contents[j] != '\n' && contents[j] != '\r')
                            {
                                j++;
                                if (j > contents.Length)
                                {
                                    // a value ending the file counts as ending the value
                                    break;
                                }
                            }
                            curparent.Set(contents.Substring(i, j - i));
                            curparent  = curparent.Parent;
                            parseState = parseEnum.lookingForKey;
                            i          = j;
                        }
                        else
                        {
                            throw new KeyValueParsingException("Unexpected '" + contents[i].ToString() + "' at position " + i.ToString(), null);
                        }
                        break;
                    }
                }
                // At the end of the file, we should be looking for another key
                if (parseState != parseEnum.lookingForKey)
                {
                    throw new KeyNotFoundException("File ended while looking for value", null);
                }
                // At the end of the file, all block values should be closed
                if (curparent != basekv)
                {
                    throw new KeyNotFoundException("Unterminated child blocks", null);
                }
                KeyValue[] ret = basekv.Children.ToArray <KeyValue>();
                basekv.clearChildParents();
                return(ret);
            }
            catch (KeyValueParsingException e)
            {
                throw e;
            }
            catch (Exception e)
            {
                throw new KeyValueParsingException("Hit an exception while parsing kv data!", e);
            }
        }
Beispiel #12
0
        /// <summary>
        /// Read the configuration file. May need to be made a bit more fail-
        /// safe,  there's a few potential exceptions not handled.
        /// </summary>
        public static void ReadConfig()
        {
            string configpath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "/config.txt";

            if (!File.Exists(configpath))
            {
                // Uh oh! We don't have a config file!
                // Quick, before they realize, generate a new config file!
                // By doing this approach, we can get the elevated privs needed to peek at the registry for dota' location.
                ProcessStartInfo ps = new ProcessStartInfo();
                ps.Verb     = "runas";
                ps.FileName = "GenerateBaseConfiguration.exe";
                try
                {
                    Process generator = Process.Start(ps);
                    generator.WaitForExit();
                }
                catch (Win32Exception)
                {
                    //can't really do anything about it here
                }
                //ok, we've writen it, it's all good
            }
            // The file may still not exist if the generator failed
            if (File.Exists(configpath))
            {
                string contents;
                try
                {
                    using (StreamReader readfrom = new StreamReader(configpath))
                    {
                        contents = readfrom.ReadToEnd();
                    }
                }
                catch (Exception)
                {
                    // There's a lot of reasons why it might fail, so just handle it with defaults for now
                    Console.WriteLine("Failed to read config file, using default values...");
                    goto parsefailed;
                }
                KV.KeyValue   confignode = null;
                KV.KeyValue[] confignodes;
                try
                {
                    confignodes = KV.KVParser.ParseAllKeyValues(contents);
                }
                catch (KV.KVParser.KeyValueParsingException)
                {
                    Console.WriteLine("Failed to parse config file, using default values...");
                    goto parsefailed;
                }
                foreach (KV.KeyValue kv in confignodes)
                {
                    if (kv.Key == "config")
                    {
                        confignode = kv;
                    }
                }
                if (confignode == null)
                {
                    Console.WriteLine("Couldn't find config node in configuration, using default values...");
                    goto parsefailed;
                }
                foreach (KV.KeyValue child in confignode.Children)
                {
                    switch (child.Key)
                    {
                    case "dotaDir":
                        DotaPath = child.GetString();
                        break;

                    default:
                        // We haven't defined anything else yet.
                        continue;
                    }
                }
                // Check that required values are set
                if (DotaPath == null)
                {
                    Console.WriteLine("Couldn't find required values in config file, using default values...");
                    goto parsefailed;
                }
            }
            else
            {
                goto parsefailed;
            }
            return;

            // PARSE FAILURE HANDLING
parsefailed:
            // can't read the configuration, and can't generate a new one. Oh well, use defaults.
            // Default dota install dir
            DotaPath = "C:/Program Files (x86)/Steam/steamapps/common/dota 2 beta";
            return;
        }