void CMissionManager::LoadModListFromXml(const XmlDocumentPtr& doc)
{
	assert(doc != NULL);

	/* Example XML Snippet
	
	<mission id="11" title="Living Expenses" releaseDate="2010-01-02" size="5.9" author="Sonosuke">
		<downloadLocation language="English" url="http://www.bloodgate.com/mirrors/tdm/pub/pk4/fms/living_expenses.pk4"/>
		<downloadLocation language="German" url="http://www.bloodgate.com/mirrors/tdm/pub/pk4/fms/living_expenses_de.pk4"/>
	</mission>
	
	*/

	pugi::xpath_node_set nodes = doc->select_nodes("//tdm/availableMissions//mission");

	const char* fs_currentfm = cvarSystem->GetCVarString("fs_currentfm");

	// Tels: #3419 - After game start the sequence is always the same, so set a random seed
	time_t seconds = time(NULL);
	gameLocal.random.SetSeed( static_cast<int>(seconds) );

	for (pugi::xpath_node_set::const_iterator i = nodes.begin(); i != nodes.end(); ++i)	
	{
		pugi::xml_node node = i->node();

		DownloadableMod mission;

		mission.title = node.attribute("title").value();

		mission.id = node.attribute("id").as_int();
		mission.sizeMB = node.attribute("size").as_float();
		mission.author = node.attribute("author").value();
		mission.releaseDate = node.attribute("releaseDate").value();
		mission.type = idStr::Icmp(node.attribute("type").value(), "multi") == 0 ? DownloadableMod::Multi : DownloadableMod::Single;

		mission.modName = node.attribute("internalName").value();
		// Tels #3294: We need to clean the server-side modName of things like uppercase letters, trailing ".pk4" etc.
		//	       Otherwise we get duplicated entries in the MissionDB like "broads.pk4" and "broads" or "VFAT1" and "vfat1":
		mission.modName.ToLower();
		mission.modName.StripTrailingOnce(".pk4");

		// Clean modName string from any weird characters
		int modNameLen = mission.modName.Length();
		for (int i = 0; i < modNameLen; ++i)
		{
			if (idStr::CharIsAlpha(mission.modName[i]) || idStr::CharIsNumeric(mission.modName[i])) continue;
			mission.modName[i] = '_'; // replace non-ASCII keys with underscores
		}

		mission.version = node.attribute("version").as_int();
		mission.isUpdate = false;
        mission.needsL10NpackDownload = false; // gnartsch

		if (idStr::Cmp(mission.modName.c_str(), fs_currentfm) == 0)
		{
			DM_LOG(LC_MAINMENU, LT_DEBUG)LOGSTRING("Removing currently installed mission %s from the list of downloadable missions.\r", fs_currentfm);
			continue;
		}

		bool missionExists = false;

		// Check if this mission is already downloaded
		for (int j = 0; j < _availableMods.Num(); ++j)
		{
			if (idStr::Icmp(_availableMods[j], mission.modName) == 0)
			{
				missionExists = true;
				break;
			}
		}

		if (missionExists)
		{
			// Check mod version, there might be an update available
			if (_missionDB->ModInfoExists(mission.modName))
			{
				CModInfoPtr missionInfo = _missionDB->GetModInfo(mission.modName);

				idStr versionStr = missionInfo->GetKeyValue("downloaded_version", "1");
				int existingVersion = atoi(versionStr.c_str());

				if (existingVersion >= mission.version)
				{
					// gnartsch : Skip to next mission only in case the localization pack 
                    //            for the current mission had been downloaded already as well
					if (missionInfo->isL10NpackInstalled) 
                    {
						continue; // Our version is up to date
					}
				}
				else
				{
					mission.isUpdate = true;
				}
			}
		}

		// gnartsch : Process mission download locations only if the mission itself is not 
        //            present or not up to date, otherwise skip to the localization pack
		if (!missionExists || mission.isUpdate) 
		{
            // Mission download links
		    pugi::xpath_node_set downloadLocations = node.select_nodes("downloadLocation");

		    for (pugi::xpath_node_set::const_iterator loc = downloadLocations.begin(); loc != downloadLocations.end(); ++loc)	
		    {
			    pugi::xml_node locNode = loc->node();

			    // Only accept English downloadlinks
			    if (idStr::Icmp(locNode.attribute("language").value(), "english") != 0) continue;

			    // Tels: #3419: Randomize the order of download URLs by inserting at a random place (+2 to avoid the first URL always being placed last)
			    mission.missionUrls.Insert(locNode.attribute("url").value(), gameLocal.random.RandomInt( mission.missionUrls.Num() + 2 ) );
		    }
        }

		// Localisation packs
        // gnartsch: Process only if mission is either present locally or at least a download link for the mission is available.
        if (missionExists || mission.missionUrls.Num() > 0)
		{
		    pugi::xpath_node_set l10PackNodes = node.select_nodes("localisationPack");

		    for (pugi::xpath_node_set::const_iterator loc = l10PackNodes.begin(); loc != l10PackNodes.end(); ++loc)	
		    {
			    pugi::xml_node locNode = loc->node();

			    // Tels: #3419: Randomize the order of l10n URLs by inserting at a random place (+2 to avoid the first URL always being placed last)
			    mission.l10nPackUrls.Insert(locNode.attribute("url").value(), gameLocal.random.RandomInt( mission.l10nPackUrls.Num() + 2 ) );

                // gnartsch: Found a localization pack url for download
				mission.needsL10NpackDownload = true;
		    }
        }

		// Only add missions with valid locations
		// gnartsch: add the mission in case localization pack needs to be downloaded
		if (mission.missionUrls.Num() > 0 || mission.l10nPackUrls.Num() > 0)
		{
			// Copy-construct the local mission struct into the heap-allocated one
			_downloadableMods.Append(new DownloadableMod(mission));
		}
	}

	SortDownloadableMods();
}