#!/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. # # URL-encoding is necessary, so get urlencode.sh from the net, # eg. from http://www.shelldorado.de/scripts/cmds/urlencode.txt # # (C) 2007-2011 SNy # ########### SETUP # options for wget etc. and for starting the game, add everything you need here wgetOptions="--no-check-certificate -q" export WINEDEBUG="fixme-all" export WINEPREFIX="$HOME/.wine" # fullscreen 1680x1050 on pseudo-desktop with alt-tab ability #wineOptions="explorer /desktop=LotRO-Europe,1680x1050" # glc - OpenGL/ALSA capturing #preWineCmds="glc-capture --fps=15 --resize=0.5 --out=/media/LOTRO/lotro-capture-%year%-%month%-%day%-%capture%.glc" # extra paths needed for glc #export PATH="${PATH}:/usr/local/bin" #export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/usr/local/lib64:/usr/local/lib32" ########### START echo -e "\nWelcome to the CLI launcher for LOTRO v1.0rc2.\n\t(C) 2007-2011 by SNy\n" # official launcher config file configFile="TurbineLauncher.exe.config" # make this script be callable from elsewhere (desktop shortcuts etc) # change directory to where the launcher resides oldDir=`pwd` gameDir=`dirname "$0"` 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 "${game}" \ "${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 section which breaks things echo -e "" >> .launcher/GLSDataCenter.config.${game} cat .launcher/GLSDataCenter.config | sed -n -e "/^.*${game}<\/Name>/,/<\/Datacenter>.*$/ p" >> .launcher/GLSDataCenter.config.${game} # get dynamic launcher settings patchServer_ADR=`grep -h "" .launcher/GLSDataCenter.config.${game} | grep -v '' | sed -e "s/^.*//;s/<\/PatchServer>.*$//"` launcherCfg_URL=`grep -h "" .launcher/GLSDataCenter.config.${game} | grep -v '' | sed -e "s/^.*//;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.Filename" .launcher/launcher.config | grep -v '' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"` gameClient_ARGTMPL=`grep -h "GameClient.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 "" and output 4 lines afterwards (which contain the server config) # then grep for "" and strip off any tags # just the name is saved here, other info will be retrieved later on worlds=`grep -h -A 4 "" .launcher/GLSDataCenter.config.${game} | grep "" | grep -v '' | sed -e "s/^.*//;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 "" .launcher/GLSDataCenter.config.${game} | grep -v '' | sed -e "s/^.*//;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 (by Sinistral) # get available languages IFS=$'\n' i=0 for name in $(ls -x1 *.dat | grep -h "client_local_" | sed -e "s/^.*client_local_//;s/.dat$//"); do # results are all upper case (EN, EN_GB, DE etc.) # for proper patching, the first two letters need to be lower case (en, en_GB, de etc.) # else the splash screen image will not get updated (Windows-dev's case-insensitivity sillyness, probably) languages[$i]=`echo ${name:0:2} | tr A-Z a-z`${name:2} i=$(($i + 1)) done languages[$i]="end-of-list" unset IFS # is there even more than one language file? (mod by SNy) if [[ $i > 1 ]] ; then # select the language echo -e "\nPlease choose your language now." i=0 while [[ "${languages[$i]}" != "end-of-list" ]] ; do echo -e "\t$i:\t${languages[$i]}" i=$(($i + 1)) done echo -n "Your choice (enter the number on the left)? " read selectedLanguage else selectedLanguage=0 fi ########### PATCHING # default is NO patching echo -en "\nDo you want to check for updates (y/N)? " read checkForUpdates if [[ $checkForUpdates == "y" || $checkForUpdates == "Y" ]] ; then echo -e "Checking for updates..." # this is as simple as that, thanks to Turbine, who have made the Patch()-function in the new PatchClient.dll very self explanatory # too bad finding this wasn't as obvious as using it (thanks again Robert Getter for pointing it out) # # edit: regarding simplicity: OK, the patching itself takes place in a number of steps, download/decrypt file, replace file # some files require a "SelfPatch" others don't # this is yet to be understood properly, will see how the sequence below works out wine rundll32.exe PatchClient,Patch $patchServer_ADR --highres --filesonly --language ${languages[$selectedLanguage]} --productcode $game wine rundll32.exe PatchClient,Patch $patchServer_ADR --highres --dataonly --language ${languages[$selectedLanguage]} --productcode $game fi ########### AUTHENTICATION function GLSAuth() { # account name echo -e "\nPlease enter your account details now." echo -n "account: " read account # password, read silently (no echo) echo -n "password: " read -s pass echo "" # "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 "${account}${pass}" \ "${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 # check for multiple game subscriptions for the authenticated account # insert whitespace after tags here (to have linebreaks as separators for accounts) sed -e "s#\(\)#\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>" .launcher/GLSAuthServer.config | grep "" | grep -v '' | sed -e "s/^.*//;s/<\/Name>.*$//"` subDescs=`grep -h "${game}<\/Game>" .launcher/GLSAuthServer.config | grep "" | grep -v '' | sed -e "s/^.*//;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/^.*//;s/<\/Ticket>.*$// p" .launcher/GLSAuthServer.config` 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 echo -n "Your choice (enter the number on the left)? " read selectedServer # 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 "" .launcher/GLSDataCenter.config.${game} | grep -F -A 3 "${serverNames[$selectedServer]}" | grep -h "" | grep -v '' | sed -e "s/^.*//;s/<\/ChatServerUrl>.*$//"` serverStatus_URL=`grep -h -A 4 "" .launcher/GLSDataCenter.config.${game} | grep -F -A 3 "${serverNames[$selectedServer]}" | grep -h "" | grep -v '' | sed -e "s/^.*//;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 "" .launcher/server.config | grep -v '' | sed -e "s/^.*//;s/<\/loginservers>.*$//"` queueUrls=`grep -h "" .launcher/server.config | grep -v '' | sed -e "s/^.*//;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}" | sh urlencode.sh -- -` loginQueueURLencoded=`echo "${serverQueue}" | sh urlencode.sh -- -` # 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"` ########### 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 "" .launcher/WorldQueue.config | grep -v '' | sed -e "s/^.*//;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 "" .launcher/WorldQueue.config | grep -v '' | sed -e "s/^.*//;s/<\/QueueNumber>.*$//"` queueSrvd=`grep -h "" .launcher/WorldQueue.config | grep -v '' | sed -e "s/^.*//;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 # 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]}/"` # all ready, now fire up the client echo "Ready. Now starting the client..." ${preWineCmds} \ wine ${wineOptions} ${gameClient_FILE} ${gameClient_ARGS} $@ # get back to where the caller was cd "$oldDir" ########### THAT'S IT # permission granted to use and modify, as long as original copyleft notice is retained # (C) 2007 SNy # # the initial version of this script has been based on # . dumping command line arguments from the original .NET launcher # . the turbine "GLS auth server" description # and inspired by # . the need to circumvent the stupid .NET launcher # . a loose couple of thoughts from ubuntuforum members # # it has since been rewritten to extract all of its formerly static settings from # the official resources given in a number of xml-files starting with # TurbineLauncher.exe.config # ########### # # History/ChangeLog # # v0.9 2007-05-05 initial version # # v0.9.1 2007-05-24 "parsing" TurbineLauncher.exe.config # # v0.9.2 2007-05-25 added check for installed languages and chooser # contributor: Sinistral # # v0.9.3 2007-05-27 fixed extractions for XML value="$VAL" # contributor: ajackson (problem identified) # # v0.9.4 2007-05-29 changed to SOAP for GetDatacenters due to non-EU LOTRO # datacenter (US/AU/others) not accepting HTTP GET # contributor: Fitzy_oz (SOAP request body) # # v0.9.5 2007-05-30 changed to SOAP for LoginAccount as well (same reason as 0.9.4) # SOAP snippet taken from lotroeugls.com service description # also switched off wine debugging msgs to increase performance # contributor: kegie (suggested WINEDEBUG=fixme-all) # # v0.9.6 2007-08-07 changed accound id extraction to look for the correct game, too # so that it works for subscribers of other turbine games (DDO) # contributor: thealb # # v0.9.7 2007-11-20 can now be called from elsewhere, will change to the game dir # (still needs to reside there, of course) # # v0.9.9 2008-01-02 MAJOR breakthrough with the patching # earlier attempts at linking against the dll functions stalled # due to missing function prototypes # blatantly simple call using the rundll interface does the trick # contributor: Robert Getter ("rundll32.exe PatchClient,Patch") # # v0.9.9b 2008-01-28 fixed an issue with the GLSDataCenter config file downloaded # from Turbine containing more than one section # # v0.9.9c 2008-04-10 added choice for patching (default: start without patching) # changed language check to now return the proper language # code so that the patching works properly now wrt. # the splash screen (ie: DE->de EN->en EN_GB->en_GB) # added choice for subscription to use when more than one # sections exist in GLSAuthServer config # contributors: ct_traveller and JediMastyre (multiple subscriptions) # # v0.9.9d 2008-10-27 added check for disabled world login queue # # v1.0rc1 2010-11-25 updates for F2P # added queue looping (thanks to steelsnake) # minor cleanups # # v1.0rc2 2011-08-09 small update for global service (grep -F for server names) # ###########