Skip to content

Instantly share code, notes, and snippets.

@kj54321
Created August 13, 2020 13:23
Show Gist options
  • Save kj54321/cccdd9a2c35df4701ab017d86f44e7d7 to your computer and use it in GitHub Desktop.
Save kj54321/cccdd9a2c35df4701ab017d86f44e7d7 to your computer and use it in GitHub Desktop.
DDO game launcher for Mac/Linux wine64
#!/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 <[email protected]>
# (C) 2020 Dong <[email protected]>
#
# 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/\&amp\;/\&/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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment