#!/bin/bash # Launch LOTRO client from CLI. # # To be used # with cygwin on windows if you just can't stand .NET # or with wine/cedega under GNU/Linux or *BSD # to play The Lord of the Rings Online: Shadows of Angmar. # # # (C) 2007-2011 SNy <SNy@bmx-chemnitz.de> # (C) 2020 Dong <dong115@uwindsor.ca> # # Modded to take command line args and windoze use and by AtomicMew 6/3/12 # Port & adapt to dnd64client.exe by Dong 8/10/20 ########### SETUP # options for wget etc. and for starting the game, add everything you need here wgetOptions="--no-check-certificate -q" ########### START myaccount="youraccount" # change as needed mypass="yourpass" # change as needed myserver="7" # change as needed characterName="yourname" # change as needed account=${1:-$myaccount} pass=${2:-$mypass} selectedServer=${3:-$myserver} echo -e "\nWelcome to the CLI launcher for LOTRO/DDO modded v 0.0000001\n" # official launcher config file configFile="ddo.launcherconfig" # make this script be callable from elsewhere (desktop shortcuts etc) # change directory to where the launcher resides oldDir=`pwd` gameDir="~/Game/DDO" # change as needed cd "$gameDir" # cleanup temp directory for configuration files downloaded from the official servers rm -rf .launcher mkdir .launcher # launcher config file starts it all, get Game and DataCenter settings if ! [ -r $configFile ] ; then echo -e "\nError: $configFile cannot be read.\n" cd "$oldDir" read dummy exit fi echo "Reading launcher configuration..." glsDataCenter_URL=`grep -h "Launcher.DataCenterService.GLS" $configFile | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"` game=`grep -h "DataCenter.GameName" $configFile | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"` # get configuration info (xml-file containing auth, patch and game servers with their corresponding settings) # NOTE: while a normal HTTP GET to /GLS.DataCenterServer/Service.asmx/GetDatacenters?game=LOTROEU # works fine for the european datacenter, it does not work for the US/AU/... LOTRO one # instead, we need to send a SOAP request there, ending up with a SOAP answer (no whitespace whatsoever) # now, to have at least some whitespace we can deal with, sed is used to insert a newline after each closing xml tag below wget $wgetOptions \ --header 'Content-Type: text/xml; charset=utf-8' \ --header 'SOAPAction: "http://www.turbine.com/SE/GLS/GetDatacenters"' \ --post-data "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><soap:Body><GetDatacenters xmlns=\"http://www.turbine.com/SE/GLS\"><game>${game}</game></GetDatacenters></soap:Body></soap:Envelope>" \ "${glsDataCenter_URL}" -O .launcher/GLSDataCenter.config if ! [ -r .launcher/GLSDataCenter.config ] ; then echo -e "\nError: Could not fetch GLS data center configuration.\n" cd "$oldDir" read dummy exit fi # insert the whitespace after closing xml tags here sed -e "s#\(</[^>]*>\)#\1\n#g" -i .launcher/GLSDataCenter.config # skip all datacenters but the one we are adressing # precaution as the DDO datacenter config contains more than one <Datacenter> section which breaks things echo -e "<!--\n NOTE\n This file is NOT a valid XML file!\n-->" >> .launcher/GLSDataCenter.config.${game} cat .launcher/GLSDataCenter.config | sed -n -e "/^.*<Datacenter><Name>${game}<\/Name>/,/<\/Datacenter>.*$/ p" >> .launcher/GLSDataCenter.config.${game} # get dynamic launcher settings patchServer_ADR=`grep -h "<PatchServer>" .launcher/GLSDataCenter.config.${game} | grep -v '<!--.*-->' | sed -e "s/^.*<PatchServer>//;s/<\/PatchServer>.*$//"` launcherCfg_URL=`grep -h "<LauncherConfigurationServer>" .launcher/GLSDataCenter.config.${game} | grep -v '<!--.*-->' | sed -e "s/^.*<LauncherConfigurationServer>//;s/<\/LauncherConfigurationServer>.*$//"` wget $wgetOptions "${launcherCfg_URL}" -O .launcher/launcher.config if ! [ -r .launcher/launcher.config ] ; then echo -e "\nError: Could not fetch dynamic launcher configuration.\n" cd "$oldDir" read dummy exit fi # extract game settings from launcher settings worldQueue_URL=`grep -h "WorldQueue.LoginQueue.URL" .launcher/launcher.config | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"` worldQueue_ARGTMPL=`grep -h "WorldQueue.TakeANumber.Parameters" .launcher/launcher.config | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"` gameClient_FILE=`grep -h "GameClient.WIN64.Filename" .launcher/launcher.config | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"` gameClient_ARGTMPL=`grep -h "GameClient.OSX.ArgTemplate" .launcher/launcher.config | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"` # extract list of game servers and settings # this is a PITA without a proper parser # grep for "<World>" and output 4 lines afterwards (which contain the server config) # then grep for "<Name>" and strip off any tags # just the name is saved here, other info will be retrieved later on worlds=`grep -h -A 4 "<World>" .launcher/GLSDataCenter.config.${game} | grep "<Name>" | grep -v '<!--.*-->' | sed -e "s/^.*<Name>//;s/<\/Name>.*$//"` # now we have a list of servernames, separated by new-line-characters, split them there into an array, ignore all other whitespace IFS=$'\n' i=0 for name in $worlds ; do serverNames[$i]=$name i=$(($i + 1)) done serverNames[$i]="end-of-list" unset IFS # extract the auth server URL from the configuration file authServer_URL=`grep -h "<AuthServer>" .launcher/GLSDataCenter.config.${game} | grep -v '<!--.*-->' | sed -e "s/^.*<AuthServer>//;s/<\/AuthServer>.*$//"` if [ -z "${authServer_URL}" ] ; then echo -e "\nError: Could not extract authentication server URL from launcher configuration.\n" cd "$oldDir" read dummy exit fi ########### CHOOSE LANGUAGE languages[0]=english selectedLanguage=0 ########### PATCHING #no patching ########### AUTHENTICATION function GLSAuth() { # "submit" the login form via POST, will download a file called "LoginAccount" # NOTE: the same thing as with the DataCenterServer applies here # the lotroeugls server even has a service description and test form online # well, at least they provide the SOAP request body as well... echo "Requesting GLS authentication ticket..." wget $wgetOptions \ --header 'Content-Type: text/xml; charset=utf-8' \ --header 'SOAPAction: "http://www.turbine.com/SE/GLS/LoginAccount"' \ --post-data "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><LoginAccount xmlns=\"http://www.turbine.com/SE/GLS\"><username>${account}</username><password>${pass}</password><additionalInfo></additionalInfo></LoginAccount></soap:Body></soap:Envelope>" \ "${authServer_URL}" -O .launcher/GLSAuthServer.config if ! [ -s .launcher/GLSAuthServer.config ] ; then echo -e "\nError: GLS auth server request failed. Wrong account/password?\n" echo -e "\nRetry (Y/n)? " read retry if [[ $retry == "y" || $retry == "Y" || $retry == "" ]] ; then GLSAuth else cd "$oldDir" read dummy exit fi fi } GLSAuth ########### Multiple subscription check? Must be a LOTRO thing # check for multiple game subscriptions for the authenticated account # insert whitespace after </GameSubscription> tags here (to have linebreaks as separators for accounts) sed -e "s#\(</GameSubscription>\)#\1\n#g" -i .launcher/GLSAuthServer.config # extract each of the ids as wells as descriptions for subscriptions to the current game subNames=`grep -h "<Game>${game}<\/Game>" .launcher/GLSAuthServer.config | grep "<Name>" | grep -v '<!--.*-->' | sed -e "s/^.*<Name>//;s/<\/Name>.*$//"` subDescs=`grep -h "<Game>${game}<\/Game>" .launcher/GLSAuthServer.config | grep "<Description>" | grep -v '<!--.*-->' | sed -e "s/^.*<Description>//;s/<\/Description>.*$//"` IFS=$'\n' # list of subscription ids and descriptions i=0 for sub in $subNames ; do subs[$i]=$sub i=$(($i + 1)) done subs[$i]="end-of-list" i=0 for desc in $subDescs ; do descs[$i]=$desc i=$(($i + 1)) done descs[$i]="end-of-list" unset IFS # check for at least one active subscription if [[ $i == 0 ]] ; then echo -e "\nError: There appears to be no subscription for $game.\n" cd "$oldDir" read dummy exit fi # if there are multiple subscriptions to the current game available for the account, ask which one to use if [[ $i > 1 ]] ; then i=0 echo "You have the following subscriptions for $game:" while [[ "${subs[$i]}" != "end-of-list" ]] ; do echo -e "\t$i:\t${subs[$i]}\t'${descs[$i]}'" i=$(($i + 1)) done echo -n "Please select the one you wish to use (enter the number on the left): " read selectedSub else selectedSub=0 fi # extract the ticket from the GLS auth file, check for failure # NOTE: only the id (not the ticket) is subscription-specific glsTicket=`sed -n -e "s/^.*<Ticket>//;s/<\/Ticket>.*$// p" .launcher/GLSAuthServer.config` glsTicketlifetime=21600 if [[ -z "${glsTicket}" ]] ; then echo -e "\nError: Could not extract auth result from GLS auth server response.\n" cd "$oldDir" read dummy exit fi echo -e "Logged in." ########### REALM SELECTION echo -e "\nThe following servers are available:" i=0 while [[ "${serverNames[$i]}" != "end-of-list" ]] ; do echo -e "\t$i:\t${serverNames[$i]}" i=$(($i + 1)) done # with the given index (and therefore server name), look up the server info in the configuration file # chat server adress is given directly, other stuff needs another file (cache_$REALMNAME.xml) from the server serverChat=`grep -h -A 4 "<World>" .launcher/GLSDataCenter.config.${game} | grep -F -A 3 "${serverNames[$selectedServer]}" | grep -h "<ChatServerUrl>" | grep -v '<!--.*-->' | sed -e "s/^.*<ChatServerUrl>//;s/<\/ChatServerUrl>.*$//"` serverStatus_URL=`grep -h -A 4 "<World>" .launcher/GLSDataCenter.config.${game} | grep -F -A 3 "${serverNames[$selectedServer]}" | grep -h "<StatusServerUrl>" | grep -v '<!--.*-->' | sed -e "s/^.*<StatusServerUrl>//;s/<\/StatusServerUrl>.*$//"` # now we know where the chat server resides and we have the adress of the server xml status file # download the status file and get server adress and login queue adress to establish the connection # ToDo: this file also contains current availability information, use it wget $wgetOptions "$serverStatus_URL" -O .launcher/server.config # extract the list of loginServers and queue URLs (two at this time, it seems) loginServers=`grep -h "<loginservers>" .launcher/server.config | grep -v '<!--.*-->' | sed -e "s/^.*<loginservers>//;s/<\/loginservers>.*$//"` queueUrls=`grep -h "<queueurls>" .launcher/server.config | grep -v '<!--.*-->' | sed -e "s/^.*<queueurls>//;s/<\/queueurls>.*$//"` if [ -z "${loginServers}" ] || [ -z "${queueUrls}" ] ; then echo -e "\nError: Could not extract server information for realm ${serverNames[$selectedServer]}.\n" cd "$oldDir" read dummy exit fi IFS=";" i=0 for adr in $loginServers ; do serverAdresses[$i]=$adr i=$(($i + 1)) done i=0 for adr in $queueUrls ; do serverQueues[$i]=$adr i=$(($i + 1)) done unset IFS # just use the respective first one given serverAdress="${serverAdresses[0]}" serverQueue="${serverQueues[0]}" # the ticket can contain special characters and needs to be URL encoded before POSTing it (same for queue_url) # launcher.config also includes a parameter template for the world queue request, used the same way as the client args below glsTicketURLencoded=$(echo ${glsTicket} | sed -e 's/%/%25/g' -e 's/ /%20/g' -e 's/!/%21/g' -e 's/"/%22/g' -e 's/#/%23/g' -e 's/\$/%24/g' -e 's/\&/%26/g' -e 's/'\''/%27/g' -e 's/(/%28/g' -e 's/)/%29/g' -e 's/\*/%2a/g' -e 's/+/%2b/g' -e 's/,/%2c/g' -e 's/-/%2d/g' -e 's/\./%2e/g' -e 's/\//%2f/g' -e 's/:/%3a/g' -e 's/;/%3b/g' -e 's//%3e/g' -e 's/?/%3f/g' -e 's/@/%40/g' -e 's/\[/%5b/g' -e 's/\\/%5c/g' -e 's/\]/%5d/g' -e 's/\^/%5e/g' -e 's/_/%5f/g' -e 's/`/%60/g' -e 's/{/%7b/g' -e 's/|/%7c/g' -e 's/}/%7d/g' -e 's/~/%7e/g') loginQueueURLencoded=$(echo ${serverQueue} | sed -e 's/%/%25/g' -e 's/ /%20/g' -e 's/!/%21/g' -e 's/"/%22/g' -e 's/#/%23/g' -e 's/\$/%24/g' -e 's/\&/%26/g' -e 's/'\''/%27/g' -e 's/(/%28/g' -e 's/)/%29/g' -e 's/\*/%2a/g' -e 's/+/%2b/g' -e 's/,/%2c/g' -e 's/-/%2d/g' -e 's/\./%2e/g' -e 's/\//%2f/g' -e 's/:/%3a/g' -e 's/;/%3b/g' -e 's//%3e/g' -e 's/?/%3f/g' -e 's/@/%40/g' -e 's/\[/%5b/g' -e 's/\\/%5c/g' -e 's/\]/%5d/g' -e 's/\^/%5e/g' -e 's/_/%5f/g' -e 's/`/%60/g' -e 's/{/%7b/g' -e 's/|/%7c/g' -e 's/}/%7d/g' -e 's/~/%7e/g') # the launcher.config also includes a parameter template for the world queue request worldQueue_ARGS=`echo "${worldQueue_ARGTMPL}" | sed -e "s/[{]0[}]/${subs[$selectedSub]}/;s/[{]1[}]/${glsTicketURLencoded}/;s/[{]2[}]/${loginQueueURLencoded}/;s/\&\;/\&/g"` echo $worldQueue_ARGS ########### LOGIN QUEUE / CLIENT START function JoinQueue() { # now get a queue number from the world login queue so that the client can enqueue and authenticate echo -e "\nConnecting to world login queue for realm ${serverNames[$selectedServer]}..."; inQueue=1 while [ "$inQueue" == "1" ]; do # loop when necessary wget $wgetOptions --post-data="${worldQueue_ARGS}" "${worldQueue_URL}" -O .launcher/WorldQueue.config if ! [ -r .launcher/WorldQueue.config ] ; then echo -e "\nError: World login queue request failed.\n" cd "$oldDir" read dummy exit fi # check the result, should be HRESULT 0x00000000, indicating success (Windows API madness) hresult=`grep -h "<HResult>" .launcher/WorldQueue.config | grep -v '<!--.*-->' | sed -e "s/^.*<HResult>//;s/<\/HResult>.*$//"` if [ "$hresult" != "0x00000000" ] ; then echo -e "\nError: World login queue response indicates failure." cd "$oldDir" read dummy exit else # lets see what position we are at queuePos=`grep -h "<QueueNumber>" .launcher/WorldQueue.config | grep -v '<!--.*-->' | sed -e "s/^.*<QueueNumber>//;s/<\/QueueNumber>.*$//"` queueSrvd=`grep -h "<NowServingNumber>" .launcher/WorldQueue.config | grep -v '<!--.*-->' | sed -e "s/^.*<NowServingNumber>//;s/<\/NowServingNumber>.*$//"` queueAhead=$(( ${queuePos} - ${queueSrvd} )) if [ ${queueAhead} -gt 0 ]; then # d'oh, need to wait echo -e "\n${queueAhead} ahead in queue, retrying in 5s..." sleep 5 else # hah, ready to go inQueue=0 fi fi done } # new: world queue is unused on (some?) EU servers, just like with DDO, skip if no queue URL if [ -n "${serverQueue}" ] ; then JoinQueue else echo -e "\nWorld login queue seems to be disabled, skipping..." fi supportURL="https://tss.ddo.com/TSSTrowser/trowser.aspx" bugURL="http://ddobugs.turbine.com?task=ticket" supportServiceURL="https://tss.ddo.com/TSSTrowser/SubmitTicket.asmx" # OK, so we have a template for the client arguments and we have the arguments # note that for replacing the glsTicket, I use s### instead of s/// due to the ticket containing slashes # hopefully, it doesn't contain any sharp characters :) #gameClient_ARGS=`echo "${gameClient_ARGTMPL}" | sed -e "s/[{]0[}]/${subs[$selectedSub]}/;s/[{]1[}]/${serverAdress}/;s#[{]2[}]#${glsTicket}#;s/[{]3[}]/${serverChat}/;s/[{]4[}]/${languages[$selectedLanguage]}/;s/[{]5[}]/${authServer_URL}/;s/[{]6[}]/${glsTicketlifetime}/;s/[{]7[}]/${supportURL}/;s/[{]8[}]/${bugURL}/;s/[{]9[}]/${supportServiceURL}/"` #serverAdress="198.252.160.45:9003" gameClient_ARGS="-a ${subs[$selectedSub]} -h ${serverAdress} --glsticketdirect ${glsTicket} --chatserver ${serverChat} --rodat on --language ${languages[$selectedLanguage]} --gametype DDO --authserverurl ${authServer_URL} --glsticketlifetime ${glsTicketlifetime} --supporturl ${supportURL} --bugurl ${bugURL} --supportserviceurl ${supportServiceURL} -r ${characterName}" # all ready, now fire up the client echo "Ready. Now starting the client..." echo $gameClient_ARGS wine64 x64/dndclient64.exe $gameClient_ARGS & # get back to where the caller was cd "$oldDir" exit