Kaytis
02-19-2015, 12:15 AM
10/2/2020 Version 1.5
Added Lamannia and 64-bit Client support
This script can be run from the Terminal utility and allows you to enter the game directly on the toon you wish to play, bypassing the loading screen downloads, the launcher, and the character selection screen. You can alternatively go to the character selection screen first. You can multi-box by providing a toon name from a different account on the command line.
Instructions for installing and running the script are embedded in the text.
The script supports command line arguments for username, password, world, character, and 64-bits. This allows it to be used without modification.
Note that the script is not able to patch the game. The real "DDO" application must be run one time after every update to the game.
Enable the 64-bit client by adding -x64 to the command line. The 64-bit Client will only work seamlessly on a macOS 10.14 machine or earlier. MacOS 10.15 support is possible but requires an older machine to update the software whenever the game changes. This is because there is no 64-bit version of the Launcher. Until SSG gets around to that, the only way this will work in macOS 10.15 is to copy an up to date version of "/Applications/DDO" and ~"/Library/Application Support/com.standingstonegames.ddo" from an old machine to the macOS 10.15 machine. Additionally you will need to open System Preferences > Security & Privacy > Privacy" and allow Developer Tools > Terminal to run unverified software locally. This only needs to be done once.
Use Lamannia by adding "-w Lamannia" to the command line. Note that you will need the base Lamannia client installed in /Applications
Some observations about the Wine client:
It doesn't work in macOS 10.15 -don't upgrade to 10.15 if you want to keep playing DDO on the Mac. SSG needs to finish converting the Launcher to 64-bits before it will work well enough to use.
When installing wine, there are two confusing dialogs that come up asking if you want to install some extra pieces. It actually doesn't matter how you answer these two questions. The best answer is to say "No" -the DDO installer already comes with versions of the components needed. There is no need to load your machine down with software that won't be used.
There are some oddities with the command key. For some reason it registers as the alt key. Because I need the command key to tab between multiple clients, I had to clear "Rune Arm Use" from it. I did not remap it to the alt key, because the game would not recognize it. That is going to be a problem for people who care about the rune arm.
Saving and using ui layout files has become very complicated. The good news is that it can be done. If you have a layout file you like, copy it to:
"/[home]/Documents/Dungeons and Dragons Online/ui/layouts/myfavoritelayout.layout"
You will need to create the "ui" folder and the "layouts" folder. Make sure that the layout file has the ".layout" extension on it. To load it in the game go to the chat window and type:
/ui layout load myfavoritelayout
Easy!
If anyone runs into any trouble with the script, please let me know.
#!/bin/bash
################################################## ###########################
# General Information
#
# Command Line Interface launcher for Dungeons & Dragons online.
#
# To use this script, follow the instructions in the sections:
# 1) Create the script
# 2) Play the game
# 3) Optional: Personalize the script
#
# Note that this script cannot update the game. If it fails to work, run the
# original DDO Launcher to allow it to perform any pending updates first.
#
# (C) 2007-2011 SNy <SNy@bmx-chemnitz.de>
#
# AtomicMew 6/3/12
# -modded to take command line args and windoze use
#
# Kaytis 5/31/13 v 1.1
# -modded to work on Mac OS X
#
# Kaytis 2/18/15 v 1.2
# -updated to match new login protocols
#
# Kaytis 11/30/15 v 1.2.1
# -disabled peer verification to bypass invalid certificates
#
# Kaytis 4/4/19 v 1.3
# -simplified instructions
#
# Kaytis 10/19/19 v 1.4
# -added support for wine client
# -added support for command line arguments:
# -u username
# -p password
# -n subscription
# -w world
# -c character
#
# Kaytis 10/2/20 v 1.5
# -added support for Lamannia world
# -added support for 64-bit client
# -x64
################################################## ###########################
# Create the script
#
# 1) Open TextEdit, create a new document, and paste all of this text into it.
# 2) Select menu item "Format" > "Make Plain Text" (if you don't see this option, ignore this step).
# 3) Save the file as /Applications/DNDLauncher.sh
# 4) Open /Applications/Utilities/Terminal and type:
# chmod 755 /Applications/DNDLauncher.sh
# 5) Hit the return key to execute the command.
################################################## ###########################
# Play the game
#
# 1) Open /Applications/Utilities/Terminal
# 2) To start from the character selection screen, type:
# /Applications/DNDLauncher.sh -u username -p password -w world
# To start in world with a specific character type:
# /Applications/DNDLauncher.sh -u username -p password -w world -c character
# 3) Hit the return key to execute the command. The game will start.
#
# Handy tip: the up arrow will restore the last command you typed in the Terminal.
# Handy tip: you do not need to leave this file open after creating it.
#
# Note in certain circumstances, a subscription number is required. You can
# specify it using the -n parameter. If one is required, and it is not provided,
# the script will help you figure out what the number is so that it can be
# provided next time.
################################################## ###########################
# Parse the command line arguments.
#
# NOTE: If you don't personalize the script, you MUST provide at least the
# username, password and world on the command line.
# Version 1.4 and higher of this script can be customized in EXACTLY
# the same way as earlier versions, including personalizing it per character and
# invoking the script with just the character name (the "-c" is optional).
# If you want to personalize the script please skip to the
# "Personalize the script" section below.
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-u|--username)
ARG_USERNAME="$2"
shift # past argument
shift # past value
;;
-p|--password)
ARG_PASSWORD="$2"
shift # past argument
shift # past value
;;
-n|--subscription)
ARG_SUBSCRIPTION="$2"
shift # past argument
shift # past value
;;
-w|--world)
ARG_WORLD="$2"
shift # past argument
shift # past value
;;
-c|--character)
ARG_CHARACTER="$2"
shift # past argument
shift # past value
;;
-x64|--x64)
X64=YES
shift # past argument
;;
-h|--help)
HELP=YES
shift # past argument
;;
*) # unknown option -assume character name
ARG_CHARACTER="$1"
shift # past argument
;;
esac
done
################################################## ###########################
# Personalize the script
#
# Providing these values will allow you to go to the character select screen on
# a specific world without providing any arguments to the script. If you add a
# character name as an argument to the script, you will be taken straight to
# that character on the specified world.
#
# Optionally provide your username, password and primary world here. Note that
# if you choose to enter this information, you should NOT share this file with
# anyone. Note: arguments provided on the command line will override these
# values.
#
# 1) Fill in the following, by replacing,
# account_username_here,
# account_password_here, and
# world_name_here
# 2) When you are done typing the values, save the file. Don't share the
# file with anyone. Your password is in it.
username=account_username_here # Main account username. No spaces anywhere.
password=account_password_here # Main account password. No spaces anywhere.
subscription=0 # Main account subscription. Usually 0.
world=world_name_here # Main world name e.g. Orien. Capitalize the first letter!
################################################## ###########################
# Advanced instructions
#
# Providing values for:
# alt_account_character_name_here
# alt_account_username_here
# alt_account_password_here
# alt_world_name_here
# will allow you to go to a specific character on any account on any world by
# providing a character name as an argument to the script. The character name is
# used as a key to select the right account and world. You will typically just
# provide the character name and nothing else when multiboxing. Note: arguments
# provided on the command line will override these values.
#
# To play a character on an alternate account:
# 1) Open /Applications/Utilities/Terminal
# 2) Type:
# /Applications/DNDLauncher.sh alt_account_character_name
# 3) Hit the return key -a new copy of the game will start.
#
# Handy tip: you can have as many copies of the game open as you have alternate
# accounts. You can add more character names by duplicating an "elif" block.
if [ "${ARG_CHARACTER}" == "alt_account_character_name_here" ] ; then
username=alt_account_username_here # Alt account username
password=alt_account_password_here # Alt account password
subscription=0 # Alt account subscription. Usually 0
world=alt_world_name_here # Alt server name e.g. Orien
elif [ "${ARG_CHARACTER}" == "alt_account_character_name_here" ] ; then
username=alt_account_username_here # Alt account username
password=alt_account_password_here # Alt account password
subscription=0 # Alt account subscription. Usually 0
world=alt_world_name_here # Alt server name e.g. Orien
elif [ "${ARG_CHARACTER}" == "alt_account_character_name_here" ] ; then
username=alt_account_username_here # Alt account username
password=alt_account_password_here # Alt account password
subscription=0 # Alt account subscription. Usually 0
world=alt_world_name_here # Alt server name e.g. Orien
fi
################################################## ###########################
# Launcher script
if [ -n "${HELP}" ] ; then
echo ""
echo "A macOS command line launcher for DDO. Use this command for fast game launching"
echo "and multiboxing."
echo ""
echo "usage: DNDLauncher [-u username] [-p password] [-n subscription] [-w world] [-c character]"
echo ""
echo "-u username : account username"
echo "-p password : account password"
echo "-n subscription : account subscription. Usually 0. Omit if unknown."
echo "-w world : world e.g. Orien. Capitalize only the first letter"
echo "-c character : character to log in. '-c' is optional"
echo "-x64 : use 64-bit client"
echo ""
echo "Example:"
echo ""
echo " /Applications/DNDLauncher.sh -u my_account_name -p my_account_password -w Orien -c Kaytis"
echo ""
echo "or with appropriate script customization (see script for details):"
echo ""
echo " /Applications/DNDLauncher.sh -c Kaytis"
echo ""
exit 0
fi
# Snag the character name, if any, and stash it in the "character" variable.
character="${ARG_CHARACTER}"
# Apply overides
if [ -n "${ARG_USERNAME}" ] ; then
username="${ARG_USERNAME}"
fi
if [ -n "${ARG_PASSWORD}" ] ; then
password="${ARG_PASSWORD}"
fi
if [ -n "${ARG_SUBSCRIPTION}" ] ; then
subscription="${ARG_SUBSCRIPTION}"
fi
if [ -n "${ARG_WORLD}" ] ; then
world="${ARG_WORLD}"
fi
echo "-----------------------------------------------------------------------"
echo -e "Welcome to the CLI launcher for DDO for Mac OS X version 1.5"
echo "-----------------------------------------------------------------------"
exe() { echo "> $@" ; "$@" ; }
# Some common directories
gameCommon_DIR="$HOME/Library/Application Support/com.standingstonegames.ddo/common"
gameWineBin_DIR="$gameCommon_DIR/usr/bin"
gameWinePrefix_DIR="$gameCommon_DIR/wineprefix"
gameClient_DIR="$gameWinePrefix_DIR/drive_c/Program Files (x86)/StandingStoneGames/Dungeons & Dragons Online"
gameClient_EXE="C:/Program Files (x86)/StandingStoneGames/Dungeons & Dragons Online/dndclient.exe"
if [ -n "${X64}" ] ; then
gameClient_EXE="C:/Program Files (x86)/StandingStoneGames/Dungeons & Dragons Online/x64/dndclient64.exe"
fi
if [ "${world}" == "Lamannia" ] ; then
gameClient_DIR="$gameWinePrefix_DIR/drive_c/Program Files (x86)/StandingStoneGames/Dungeons & Dragons Online (Preview)"
gameClient_EXE="C:/Program Files (x86)/StandingStoneGames/Dungeons & Dragons Online (Preview)/dndclient.exe"
if [ -n "${X64}" ] ; then
gameClient_EXE="C:/Program Files (x86)/StandingStoneGames/Dungeons & Dragons Online (Preview)/x64/dndclient64.exe"
fi
fi
# make this script be callable from elsewhere (desktop shortcuts etc)
oldDir=`pwd`
# change directory to where the launcher resources reside
cd "${gameClient_DIR}"
# cleanup temp directory for configuration files downloaded from the official servers
rm -rf .launcher
mkdir .launcher
echo "Reading game configuration..."
# Get Game and DataCenter settings from launcher config file.
configFile="ddo.launcherconfig"
if ! [ -r "${configFile}" ] ; then
echo -e "\nError: ${configFile} cannot be read.\n"
cd "${oldDir}"
exit
fi
game=`grep -h "DataCenter.GameName" "${configFile}" | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"`
glsDataCenter_URL=`grep -h "Launcher.DataCenterService.GLS" "${configFile}" | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"`
echo "Game Name: ${game}"
echo "Game Login Server: ${glsDataCenter_URL}"
echo "-----------------------------------------------------------------------"
# 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
echo "Querying Game Login Server..."
# wget \
# --no-check-certificate -q \
# --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
curl --silent \
--insecure \
--header 'Content-Type: text/xml; charset=utf-8' \
--header 'SOAPAction: "http://www.turbine.com/SE/GLS/GetDatacenters"' \
--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}" \
--output .launcher/GLSDataCenter.config
if ! [ -r .launcher/GLSDataCenter.config ] ; then
echo -e "\nError: Could not fetch GLS data center configuration.\n"
cd "${oldDir}"
exit
fi
# Format the response by adding new lines after the closing xml tags.
sed -e "s#\(</[^>]*>\)#\1_make_newline_#g" -i "" .launcher/GLSDataCenter.config
awk '{ gsub(/_make_newline_/,"\n",$0); print }' .launcher/GLSDataCenter.config >> .launcher/GLSDataCenter.config.tmp
rm -f .launcher/GLSDataCenter.config
mv .launcher/GLSDataCenter.config.tmp .launcher/GLSDataCenter.config
# Grab only the first DataCenter.
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}"
# Extract global server URLs
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>.*$//"`
authServer_URL=`grep -h "<AuthServer>" .launcher/GLSDataCenter.config.${game} | grep -v '<!--.*-->' | sed -e "s/^.*<AuthServer>//;s/<\/AuthServer>.*$//"`
# Extract world specific server URLs
serverChat_URL=`grep -h -A 4 "<World>" .launcher/GLSDataCenter.config.${game} | grep -F -A 3 "${world}" | 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 "${world}" | grep -h "<StatusServerUrl>" | grep -v '<!--.*-->' | sed -e "s/^.*<StatusServerUrl>//;s/<\/StatusServerUrl>.*$//"`
# Look up the server info in the configuration file
# The chat server address is given directly, other stuff needs another file (cache_$REALMNAME.xml) from the server
if [ -z "${launcherCfg_URL}" ] || [ -z "${authServer_URL}" ] ; then
echo -e "\nError: Could not extract one or more server URLs from the Game Login Server response.\n"
cd "${oldDir}"
exit
fi
# Extract list of game servers into an array
worlds=`grep -h -A 4 "<World>" .launcher/GLSDataCenter.config.${game} | grep "<Name>" | grep -v '<!--.*-->' | sed -e "s/^.*<Name>//;s/<\/Name>.*$//"`
IFS=$'\n'
i=0
for name in ${worlds} ; do
serverNames[$i]=${name}
i=$(($i + 1))
done
serverNames[$i]="end-of-list"
unset IFS
echo "Launch Configuration Server: ${launcherCfg_URL}"
echo "Patch Server: ${patchServer_ADR}"
echo "Authorization Server: ${authServer_URL}"
if [[ "${serverNames[0]}" != "end-of-list" ]] ; then
echo "Available Worlds: ${serverNames[0]}"
i=1
while [[ "${serverNames[$i]}" != "end-of-list" ]] ; do
echo -e " ${serverNames[$i]}"
i=$(($i + 1))
done
else
echo "Available Worlds: None"
cd "${oldDir}"
exit
fi
echo "${world} Chat Server: ${serverChat_URL}"
echo "${world} Status Server: ${serverStatus_URL}"
echo "-----------------------------------------------------------------------"
echo "Querying Launch Configuration Server..."
# wget --no-check-certificate \
# -q "${launcherCfg_URL}" \
# -O .launcher/launcher.config
curl --silent \
--insecure \
"${launcherCfg_URL}" \
--output .launcher/launcher.config
if ! [ -r .launcher/launcher.config ] ; then
echo -e "\nError: Could not fetch dynamic launcher configuration.\n"
cd "${oldDir}"
exit
fi
# Extract game settings from launcher configuration.
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/"`
glsTicketLifetime=`grep -h "GameClient.Arg.glsticketlifetime" .launcher/launcher.config | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"`
support_URL=`grep -h "GameClient.Arg.supporturl" .launcher/launcher.config | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"`
bug_URL=`grep -h "GameClient.Arg.bugurl" .launcher/launcher.config | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"`
supportService_URL=`grep -h "GameClient.Arg.supportserviceurl" .launcher/launcher.config | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"`
gameClient_FILE=`grep -h "GameClient.OSX.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/"`
echo "World Queue Server: ${worldQueue_URL}"
echo "World Queue Args: ${worldQueue_ARGTMPL}"
echo "GLS Ticket Lifetime: ${glsTicketLifetime}"
echo "Support URL: ${support_URL}"
echo "Bug URL: ${bug_URL}"
echo "Support Service URL: ${supportService_URL}"
echo "Game Client File: ${gameClient_FILE}"
echo "Game Client Args: ${gameClient_ARGTMPL}"
echo "-----------------------------------------------------------------------"
########### CHOOSE LANGUAGE
languages[0]=english
selectedLanguage=0
########### PATCHING
# Note: This script cannot patch the game. Use the Launcher to do that.
########### 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...
# wget \
# --no-check-certificate -q \
# --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>${username}</username><password>${password}</password><additionalInfo></additionalInfo></LoginAccount></soap:Body></soap:Envelope>" \
# "${authServer_URL}" -O .launcher/GLSAuthServer.config
curl --silent \
--insecure \
--header 'Content-Type: text/xml; charset=utf-8' \
--header 'SOAPAction: "http://www.turbine.com/SE/GLS/LoginAccount"' \
--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>${username}</username><password>${password}</password><additionalInfo></additionalInfo></LoginAccount></soap:Body></soap:Envelope>" \
"${authServer_URL}" \
--output .launcher/GLSAuthServer.config
if ! [ -s .launcher/GLSAuthServer.config ] || grep -qs faultcode .launcher/GLSAuthServer.config ; then
echo -e "\nError: GLS auth server request failed. Wrong username / password?\n"
cd "${oldDir}"
exit
fi
}
echo "Requesting account details and authentication token from the Authorization Server..."
GLSAuth
# 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_make_newline_#g" -i "" .launcher/GLSAuthServer.config
awk '{ gsub(/_make_newline_/,"\n",$0); print }' .launcher/GLSAuthServer.config >> .launcher/GLSAuthServer.config.tmp
rm -f .launcher/GLSAuthServer.config
mv .launcher/GLSAuthServer.config.tmp .launcher/GLSAuthServer.config
# Extract each of the ids as well 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'
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}"
exit
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`
if [[ -z "${glsTicket}" ]] ; then
echo -e "\nError: Could not extract authetication token from the Authorization Server response.\n"
cd "${oldDir}"
exit
fi
if [[ "${subs[0]}" != "end-of-list" ]] ; then
echo "Subscription Names: ${subs[0]}"
i=1
while [[ "${subs[$i]}" != "end-of-list" ]] ; do
echo -e " ${subs[$i]}"
i=$(($i + 1))
done
else
echo "Subscription Names: None"
cd "${oldDir}"
exit
fi
if [[ "${descs[0]}" != "end-of-list" ]] ; then
echo "Subscription Descriptions: ${descs[0]}"
i=1
while [[ "${descs[$i]}" != "end-of-list" ]] ; do
echo -e " ${descs[$i]}"
i=$(($i + 1))
done
else
echo "Subscription Descriptions: None"
cd "${oldDir}"
exit
fi
echo "Authentication Token: ${glsTicket}"
echo "-----------------------------------------------------------------------"
# If there are multiple subscriptions to the current game available for the account,
# and we haven't set the subscription choice, ask which one to use.
if [[ "${subscription}" == "" ]] ; then
if [[ $i > 1 && "${subscription}" == "" ]] ; then
i=0
echo "You have the following subscriptions for $game:"
while [[ "${subs[$i]}" != "end-of-list" ]] ; do
echo -e " $i: ${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 subscription
else
subscription=0
fi
fi
echo "Requesting world information for ${world} from the Status Server..."
# Download the status file and get the server adress and login queue adress to
# establish the connection.
# TODO: this file also contains current availability information, use it
# wget --no-check-certificate \
# -q "$serverStatus_URL" \
# -O .launcher/server.config
curl --silent \
--insecure \
"$serverStatus_URL" \
--output .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 world information for ${world}.\n"
cd "${oldDir}"
exit
fi
# Create array of servers
IFS=";"
i=0
for adr in ${loginServers} ; do
serverAddresses[$i]="${adr}"
i=$(($i + 1))
done
serverAddresses[$i]="end-of-list"
i=0
for adr in ${queueUrls} ; do
serverQueues[$i]="${adr}"
i=$(($i + 1))
done
serverQueues[$i]="end-of-list"
unset IFS
# Create the world queue login request for the requested world.
# The ticket can contain special characters and needs to be URL encoded before
# POSTing it (same for queue_url). launcher.config included a parameter template
# for the world queue request, used the same way as the client args below
serverAddress="${serverAddresses[0]}"
serverQueue="${serverQueues[0]}"
rawurlencode() {
local string="${1}"
local strlen=${#string}
local encoded=""
for (( pos=0 ; pos<strlen ; pos++ )); do
c=${string:$pos:1}
case "$c" in
[-_.~a-zA-Z0-9] ) o="${c}" ;;
* ) printf -v o '%%%02x' "'$c"
esac
encoded+="${o}"
done
echo "${encoded}"
}
glsTicketURLencoded=$(rawurlencode "${glsTicket}")
loginQueueURLencoded=$(rawurlencode "${serverQueue}")
worldQueue_ARGS=`echo "${worldQueue_ARGTMPL}" | sed -e "s/[{]0[}]/${subs[$subscription]}/;s/[{]1[}]/${glsTicketURLencoded}/;s/[{]2[}]/${loginQueueURLencoded}/;s/\&\;/\&/g"`
if [[ "${serverAddresses[0]}" != "end-of-list" ]] ; then
echo "${world} Login Servers: ${serverAddresses[0]}"
i=1
while [[ "${serverAddresses[$i]}" != "end-of-list" ]] ; do
echo -e " ${serverAddresses[$i]}"
i=$(($i + 1))
done
else
echo "${world} Login Servers: None"
cd "${oldDir}"
exit
fi
if [[ "${serverQueues[0]}" != "end-of-list" ]] ; then
echo "${world} Login Servers: ${serverQueues[0]}"
i=1
while [[ "${serverQueues[$i]}" != "end-of-list" ]] ; do
echo -e " ${serverQueues[$i]}"
i=$(($i + 1))
done
else
echo "${world} Login Servers: None"
cd "${oldDir}"
exit
fi
echo "Selecting queue: ${serverQueue}"
echo "Queue Arguments: ${worldQueue_ARGS}"
echo "-----------------------------------------------------------------------"
########### LOGIN QUEUE / CLIENT START
function JoinQueue() {
# Now get a queue number from the world login queue so that the client can enqueue and authenticate
inQueue=1
while [ "${inQueue}" == "1" ]; do
# loop when necessary
# wget --no-check-certificate \
# -q --post-data="${worldQueue_ARGS}" \
# "${worldQueue_URL}" \
# -O .launcher/WorldQueue.config
curl --silent \
--insecure \
--data "${worldQueue_ARGS}" \
"${worldQueue_URL}" \
--output .launcher/WorldQueue.config
if ! [ -r .launcher/WorldQueue.config ] ; then
echo -e "\nError: World login queue request failed.\n"
cd "${oldDir}"
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}"
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
}
echo "Joining ${world} Login Server queue..."
# 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
echo "Login to ${world} successful."
# 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/[{]SUBSCRIPTION[}]/${subs[$subscription]}/;
s/[{]LOGIN[}]/${serverAddress}/;
s#[{]GLS[}]#${glsTicket}#;
s/[{]CHAT[}]/${serverChat_URL}/;
s/[{]LANG[}]/${languages[$selectedLanguage]}/;
s#[{]AUTHSERVERURL[}]#${authServer_URL}#;
s/[{]GLSTICKETLIFETIME[}]/${glsTicketLifetime}/;
s#[{]SUPPORTURL[}]#${support_URL}#;
s#[{]BUGURL[}]#${bug_URL}#;
s#[{]SUPPORTSERVICEURL[}]#${supportService_URL}#;
"`
if [ -n "${character}" ] ; then
gameClient_ARGS="$gameClient_ARGS -u $character"
fi
BP='\033[0;34m'
NC='\033[0m'
echo "Launching client:"
if [ -n "${X64}" ] ; then
echo -e "${BP}WINEPREFIX=$gameWinePrefix_DIR $gameWineBin_DIR/wine64 $gameClient_EXE ${gameClient_ARGS} &>/dev/null &${NC}"
WINEPREFIX="$gameWinePrefix_DIR" "$gameWineBin_DIR"/wine64 "$gameClient_EXE" ${gameClient_ARGS} &>/dev/null &
else
echo -e "${BP}WINEPREFIX=$gameWinePrefix_DIR $gameWineBin_DIR/wine $gameClient_EXE ${gameClient_ARGS} &>/dev/null &${NC}"
WINEPREFIX="$gameWinePrefix_DIR" "$gameWineBin_DIR"/wine "$gameClient_EXE" ${gameClient_ARGS} &>/dev/null &
fi
disown
echo "-----------------------------------------------------------------------"
echo "Done. Please allow the DDO client a few seconds to launch."
echo "-----------------------------------------------------------------------"
# Get back to where the caller was
cd "${oldDir}"
exit
Added Lamannia and 64-bit Client support
This script can be run from the Terminal utility and allows you to enter the game directly on the toon you wish to play, bypassing the loading screen downloads, the launcher, and the character selection screen. You can alternatively go to the character selection screen first. You can multi-box by providing a toon name from a different account on the command line.
Instructions for installing and running the script are embedded in the text.
The script supports command line arguments for username, password, world, character, and 64-bits. This allows it to be used without modification.
Note that the script is not able to patch the game. The real "DDO" application must be run one time after every update to the game.
Enable the 64-bit client by adding -x64 to the command line. The 64-bit Client will only work seamlessly on a macOS 10.14 machine or earlier. MacOS 10.15 support is possible but requires an older machine to update the software whenever the game changes. This is because there is no 64-bit version of the Launcher. Until SSG gets around to that, the only way this will work in macOS 10.15 is to copy an up to date version of "/Applications/DDO" and ~"/Library/Application Support/com.standingstonegames.ddo" from an old machine to the macOS 10.15 machine. Additionally you will need to open System Preferences > Security & Privacy > Privacy" and allow Developer Tools > Terminal to run unverified software locally. This only needs to be done once.
Use Lamannia by adding "-w Lamannia" to the command line. Note that you will need the base Lamannia client installed in /Applications
Some observations about the Wine client:
It doesn't work in macOS 10.15 -don't upgrade to 10.15 if you want to keep playing DDO on the Mac. SSG needs to finish converting the Launcher to 64-bits before it will work well enough to use.
When installing wine, there are two confusing dialogs that come up asking if you want to install some extra pieces. It actually doesn't matter how you answer these two questions. The best answer is to say "No" -the DDO installer already comes with versions of the components needed. There is no need to load your machine down with software that won't be used.
There are some oddities with the command key. For some reason it registers as the alt key. Because I need the command key to tab between multiple clients, I had to clear "Rune Arm Use" from it. I did not remap it to the alt key, because the game would not recognize it. That is going to be a problem for people who care about the rune arm.
Saving and using ui layout files has become very complicated. The good news is that it can be done. If you have a layout file you like, copy it to:
"/[home]/Documents/Dungeons and Dragons Online/ui/layouts/myfavoritelayout.layout"
You will need to create the "ui" folder and the "layouts" folder. Make sure that the layout file has the ".layout" extension on it. To load it in the game go to the chat window and type:
/ui layout load myfavoritelayout
Easy!
If anyone runs into any trouble with the script, please let me know.
#!/bin/bash
################################################## ###########################
# General Information
#
# Command Line Interface launcher for Dungeons & Dragons online.
#
# To use this script, follow the instructions in the sections:
# 1) Create the script
# 2) Play the game
# 3) Optional: Personalize the script
#
# Note that this script cannot update the game. If it fails to work, run the
# original DDO Launcher to allow it to perform any pending updates first.
#
# (C) 2007-2011 SNy <SNy@bmx-chemnitz.de>
#
# AtomicMew 6/3/12
# -modded to take command line args and windoze use
#
# Kaytis 5/31/13 v 1.1
# -modded to work on Mac OS X
#
# Kaytis 2/18/15 v 1.2
# -updated to match new login protocols
#
# Kaytis 11/30/15 v 1.2.1
# -disabled peer verification to bypass invalid certificates
#
# Kaytis 4/4/19 v 1.3
# -simplified instructions
#
# Kaytis 10/19/19 v 1.4
# -added support for wine client
# -added support for command line arguments:
# -u username
# -p password
# -n subscription
# -w world
# -c character
#
# Kaytis 10/2/20 v 1.5
# -added support for Lamannia world
# -added support for 64-bit client
# -x64
################################################## ###########################
# Create the script
#
# 1) Open TextEdit, create a new document, and paste all of this text into it.
# 2) Select menu item "Format" > "Make Plain Text" (if you don't see this option, ignore this step).
# 3) Save the file as /Applications/DNDLauncher.sh
# 4) Open /Applications/Utilities/Terminal and type:
# chmod 755 /Applications/DNDLauncher.sh
# 5) Hit the return key to execute the command.
################################################## ###########################
# Play the game
#
# 1) Open /Applications/Utilities/Terminal
# 2) To start from the character selection screen, type:
# /Applications/DNDLauncher.sh -u username -p password -w world
# To start in world with a specific character type:
# /Applications/DNDLauncher.sh -u username -p password -w world -c character
# 3) Hit the return key to execute the command. The game will start.
#
# Handy tip: the up arrow will restore the last command you typed in the Terminal.
# Handy tip: you do not need to leave this file open after creating it.
#
# Note in certain circumstances, a subscription number is required. You can
# specify it using the -n parameter. If one is required, and it is not provided,
# the script will help you figure out what the number is so that it can be
# provided next time.
################################################## ###########################
# Parse the command line arguments.
#
# NOTE: If you don't personalize the script, you MUST provide at least the
# username, password and world on the command line.
# Version 1.4 and higher of this script can be customized in EXACTLY
# the same way as earlier versions, including personalizing it per character and
# invoking the script with just the character name (the "-c" is optional).
# If you want to personalize the script please skip to the
# "Personalize the script" section below.
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-u|--username)
ARG_USERNAME="$2"
shift # past argument
shift # past value
;;
-p|--password)
ARG_PASSWORD="$2"
shift # past argument
shift # past value
;;
-n|--subscription)
ARG_SUBSCRIPTION="$2"
shift # past argument
shift # past value
;;
-w|--world)
ARG_WORLD="$2"
shift # past argument
shift # past value
;;
-c|--character)
ARG_CHARACTER="$2"
shift # past argument
shift # past value
;;
-x64|--x64)
X64=YES
shift # past argument
;;
-h|--help)
HELP=YES
shift # past argument
;;
*) # unknown option -assume character name
ARG_CHARACTER="$1"
shift # past argument
;;
esac
done
################################################## ###########################
# Personalize the script
#
# Providing these values will allow you to go to the character select screen on
# a specific world without providing any arguments to the script. If you add a
# character name as an argument to the script, you will be taken straight to
# that character on the specified world.
#
# Optionally provide your username, password and primary world here. Note that
# if you choose to enter this information, you should NOT share this file with
# anyone. Note: arguments provided on the command line will override these
# values.
#
# 1) Fill in the following, by replacing,
# account_username_here,
# account_password_here, and
# world_name_here
# 2) When you are done typing the values, save the file. Don't share the
# file with anyone. Your password is in it.
username=account_username_here # Main account username. No spaces anywhere.
password=account_password_here # Main account password. No spaces anywhere.
subscription=0 # Main account subscription. Usually 0.
world=world_name_here # Main world name e.g. Orien. Capitalize the first letter!
################################################## ###########################
# Advanced instructions
#
# Providing values for:
# alt_account_character_name_here
# alt_account_username_here
# alt_account_password_here
# alt_world_name_here
# will allow you to go to a specific character on any account on any world by
# providing a character name as an argument to the script. The character name is
# used as a key to select the right account and world. You will typically just
# provide the character name and nothing else when multiboxing. Note: arguments
# provided on the command line will override these values.
#
# To play a character on an alternate account:
# 1) Open /Applications/Utilities/Terminal
# 2) Type:
# /Applications/DNDLauncher.sh alt_account_character_name
# 3) Hit the return key -a new copy of the game will start.
#
# Handy tip: you can have as many copies of the game open as you have alternate
# accounts. You can add more character names by duplicating an "elif" block.
if [ "${ARG_CHARACTER}" == "alt_account_character_name_here" ] ; then
username=alt_account_username_here # Alt account username
password=alt_account_password_here # Alt account password
subscription=0 # Alt account subscription. Usually 0
world=alt_world_name_here # Alt server name e.g. Orien
elif [ "${ARG_CHARACTER}" == "alt_account_character_name_here" ] ; then
username=alt_account_username_here # Alt account username
password=alt_account_password_here # Alt account password
subscription=0 # Alt account subscription. Usually 0
world=alt_world_name_here # Alt server name e.g. Orien
elif [ "${ARG_CHARACTER}" == "alt_account_character_name_here" ] ; then
username=alt_account_username_here # Alt account username
password=alt_account_password_here # Alt account password
subscription=0 # Alt account subscription. Usually 0
world=alt_world_name_here # Alt server name e.g. Orien
fi
################################################## ###########################
# Launcher script
if [ -n "${HELP}" ] ; then
echo ""
echo "A macOS command line launcher for DDO. Use this command for fast game launching"
echo "and multiboxing."
echo ""
echo "usage: DNDLauncher [-u username] [-p password] [-n subscription] [-w world] [-c character]"
echo ""
echo "-u username : account username"
echo "-p password : account password"
echo "-n subscription : account subscription. Usually 0. Omit if unknown."
echo "-w world : world e.g. Orien. Capitalize only the first letter"
echo "-c character : character to log in. '-c' is optional"
echo "-x64 : use 64-bit client"
echo ""
echo "Example:"
echo ""
echo " /Applications/DNDLauncher.sh -u my_account_name -p my_account_password -w Orien -c Kaytis"
echo ""
echo "or with appropriate script customization (see script for details):"
echo ""
echo " /Applications/DNDLauncher.sh -c Kaytis"
echo ""
exit 0
fi
# Snag the character name, if any, and stash it in the "character" variable.
character="${ARG_CHARACTER}"
# Apply overides
if [ -n "${ARG_USERNAME}" ] ; then
username="${ARG_USERNAME}"
fi
if [ -n "${ARG_PASSWORD}" ] ; then
password="${ARG_PASSWORD}"
fi
if [ -n "${ARG_SUBSCRIPTION}" ] ; then
subscription="${ARG_SUBSCRIPTION}"
fi
if [ -n "${ARG_WORLD}" ] ; then
world="${ARG_WORLD}"
fi
echo "-----------------------------------------------------------------------"
echo -e "Welcome to the CLI launcher for DDO for Mac OS X version 1.5"
echo "-----------------------------------------------------------------------"
exe() { echo "> $@" ; "$@" ; }
# Some common directories
gameCommon_DIR="$HOME/Library/Application Support/com.standingstonegames.ddo/common"
gameWineBin_DIR="$gameCommon_DIR/usr/bin"
gameWinePrefix_DIR="$gameCommon_DIR/wineprefix"
gameClient_DIR="$gameWinePrefix_DIR/drive_c/Program Files (x86)/StandingStoneGames/Dungeons & Dragons Online"
gameClient_EXE="C:/Program Files (x86)/StandingStoneGames/Dungeons & Dragons Online/dndclient.exe"
if [ -n "${X64}" ] ; then
gameClient_EXE="C:/Program Files (x86)/StandingStoneGames/Dungeons & Dragons Online/x64/dndclient64.exe"
fi
if [ "${world}" == "Lamannia" ] ; then
gameClient_DIR="$gameWinePrefix_DIR/drive_c/Program Files (x86)/StandingStoneGames/Dungeons & Dragons Online (Preview)"
gameClient_EXE="C:/Program Files (x86)/StandingStoneGames/Dungeons & Dragons Online (Preview)/dndclient.exe"
if [ -n "${X64}" ] ; then
gameClient_EXE="C:/Program Files (x86)/StandingStoneGames/Dungeons & Dragons Online (Preview)/x64/dndclient64.exe"
fi
fi
# make this script be callable from elsewhere (desktop shortcuts etc)
oldDir=`pwd`
# change directory to where the launcher resources reside
cd "${gameClient_DIR}"
# cleanup temp directory for configuration files downloaded from the official servers
rm -rf .launcher
mkdir .launcher
echo "Reading game configuration..."
# Get Game and DataCenter settings from launcher config file.
configFile="ddo.launcherconfig"
if ! [ -r "${configFile}" ] ; then
echo -e "\nError: ${configFile} cannot be read.\n"
cd "${oldDir}"
exit
fi
game=`grep -h "DataCenter.GameName" "${configFile}" | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"`
glsDataCenter_URL=`grep -h "Launcher.DataCenterService.GLS" "${configFile}" | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"`
echo "Game Name: ${game}"
echo "Game Login Server: ${glsDataCenter_URL}"
echo "-----------------------------------------------------------------------"
# 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
echo "Querying Game Login Server..."
# wget \
# --no-check-certificate -q \
# --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
curl --silent \
--insecure \
--header 'Content-Type: text/xml; charset=utf-8' \
--header 'SOAPAction: "http://www.turbine.com/SE/GLS/GetDatacenters"' \
--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}" \
--output .launcher/GLSDataCenter.config
if ! [ -r .launcher/GLSDataCenter.config ] ; then
echo -e "\nError: Could not fetch GLS data center configuration.\n"
cd "${oldDir}"
exit
fi
# Format the response by adding new lines after the closing xml tags.
sed -e "s#\(</[^>]*>\)#\1_make_newline_#g" -i "" .launcher/GLSDataCenter.config
awk '{ gsub(/_make_newline_/,"\n",$0); print }' .launcher/GLSDataCenter.config >> .launcher/GLSDataCenter.config.tmp
rm -f .launcher/GLSDataCenter.config
mv .launcher/GLSDataCenter.config.tmp .launcher/GLSDataCenter.config
# Grab only the first DataCenter.
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}"
# Extract global server URLs
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>.*$//"`
authServer_URL=`grep -h "<AuthServer>" .launcher/GLSDataCenter.config.${game} | grep -v '<!--.*-->' | sed -e "s/^.*<AuthServer>//;s/<\/AuthServer>.*$//"`
# Extract world specific server URLs
serverChat_URL=`grep -h -A 4 "<World>" .launcher/GLSDataCenter.config.${game} | grep -F -A 3 "${world}" | 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 "${world}" | grep -h "<StatusServerUrl>" | grep -v '<!--.*-->' | sed -e "s/^.*<StatusServerUrl>//;s/<\/StatusServerUrl>.*$//"`
# Look up the server info in the configuration file
# The chat server address is given directly, other stuff needs another file (cache_$REALMNAME.xml) from the server
if [ -z "${launcherCfg_URL}" ] || [ -z "${authServer_URL}" ] ; then
echo -e "\nError: Could not extract one or more server URLs from the Game Login Server response.\n"
cd "${oldDir}"
exit
fi
# Extract list of game servers into an array
worlds=`grep -h -A 4 "<World>" .launcher/GLSDataCenter.config.${game} | grep "<Name>" | grep -v '<!--.*-->' | sed -e "s/^.*<Name>//;s/<\/Name>.*$//"`
IFS=$'\n'
i=0
for name in ${worlds} ; do
serverNames[$i]=${name}
i=$(($i + 1))
done
serverNames[$i]="end-of-list"
unset IFS
echo "Launch Configuration Server: ${launcherCfg_URL}"
echo "Patch Server: ${patchServer_ADR}"
echo "Authorization Server: ${authServer_URL}"
if [[ "${serverNames[0]}" != "end-of-list" ]] ; then
echo "Available Worlds: ${serverNames[0]}"
i=1
while [[ "${serverNames[$i]}" != "end-of-list" ]] ; do
echo -e " ${serverNames[$i]}"
i=$(($i + 1))
done
else
echo "Available Worlds: None"
cd "${oldDir}"
exit
fi
echo "${world} Chat Server: ${serverChat_URL}"
echo "${world} Status Server: ${serverStatus_URL}"
echo "-----------------------------------------------------------------------"
echo "Querying Launch Configuration Server..."
# wget --no-check-certificate \
# -q "${launcherCfg_URL}" \
# -O .launcher/launcher.config
curl --silent \
--insecure \
"${launcherCfg_URL}" \
--output .launcher/launcher.config
if ! [ -r .launcher/launcher.config ] ; then
echo -e "\nError: Could not fetch dynamic launcher configuration.\n"
cd "${oldDir}"
exit
fi
# Extract game settings from launcher configuration.
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/"`
glsTicketLifetime=`grep -h "GameClient.Arg.glsticketlifetime" .launcher/launcher.config | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"`
support_URL=`grep -h "GameClient.Arg.supporturl" .launcher/launcher.config | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"`
bug_URL=`grep -h "GameClient.Arg.bugurl" .launcher/launcher.config | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"`
supportService_URL=`grep -h "GameClient.Arg.supportserviceurl" .launcher/launcher.config | grep -v '<!--.*-->' | sed -e "s/^.*value=\"\([^\"]*\)\".*$/\1/"`
gameClient_FILE=`grep -h "GameClient.OSX.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/"`
echo "World Queue Server: ${worldQueue_URL}"
echo "World Queue Args: ${worldQueue_ARGTMPL}"
echo "GLS Ticket Lifetime: ${glsTicketLifetime}"
echo "Support URL: ${support_URL}"
echo "Bug URL: ${bug_URL}"
echo "Support Service URL: ${supportService_URL}"
echo "Game Client File: ${gameClient_FILE}"
echo "Game Client Args: ${gameClient_ARGTMPL}"
echo "-----------------------------------------------------------------------"
########### CHOOSE LANGUAGE
languages[0]=english
selectedLanguage=0
########### PATCHING
# Note: This script cannot patch the game. Use the Launcher to do that.
########### 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...
# wget \
# --no-check-certificate -q \
# --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>${username}</username><password>${password}</password><additionalInfo></additionalInfo></LoginAccount></soap:Body></soap:Envelope>" \
# "${authServer_URL}" -O .launcher/GLSAuthServer.config
curl --silent \
--insecure \
--header 'Content-Type: text/xml; charset=utf-8' \
--header 'SOAPAction: "http://www.turbine.com/SE/GLS/LoginAccount"' \
--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>${username}</username><password>${password}</password><additionalInfo></additionalInfo></LoginAccount></soap:Body></soap:Envelope>" \
"${authServer_URL}" \
--output .launcher/GLSAuthServer.config
if ! [ -s .launcher/GLSAuthServer.config ] || grep -qs faultcode .launcher/GLSAuthServer.config ; then
echo -e "\nError: GLS auth server request failed. Wrong username / password?\n"
cd "${oldDir}"
exit
fi
}
echo "Requesting account details and authentication token from the Authorization Server..."
GLSAuth
# 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_make_newline_#g" -i "" .launcher/GLSAuthServer.config
awk '{ gsub(/_make_newline_/,"\n",$0); print }' .launcher/GLSAuthServer.config >> .launcher/GLSAuthServer.config.tmp
rm -f .launcher/GLSAuthServer.config
mv .launcher/GLSAuthServer.config.tmp .launcher/GLSAuthServer.config
# Extract each of the ids as well 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'
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}"
exit
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`
if [[ -z "${glsTicket}" ]] ; then
echo -e "\nError: Could not extract authetication token from the Authorization Server response.\n"
cd "${oldDir}"
exit
fi
if [[ "${subs[0]}" != "end-of-list" ]] ; then
echo "Subscription Names: ${subs[0]}"
i=1
while [[ "${subs[$i]}" != "end-of-list" ]] ; do
echo -e " ${subs[$i]}"
i=$(($i + 1))
done
else
echo "Subscription Names: None"
cd "${oldDir}"
exit
fi
if [[ "${descs[0]}" != "end-of-list" ]] ; then
echo "Subscription Descriptions: ${descs[0]}"
i=1
while [[ "${descs[$i]}" != "end-of-list" ]] ; do
echo -e " ${descs[$i]}"
i=$(($i + 1))
done
else
echo "Subscription Descriptions: None"
cd "${oldDir}"
exit
fi
echo "Authentication Token: ${glsTicket}"
echo "-----------------------------------------------------------------------"
# If there are multiple subscriptions to the current game available for the account,
# and we haven't set the subscription choice, ask which one to use.
if [[ "${subscription}" == "" ]] ; then
if [[ $i > 1 && "${subscription}" == "" ]] ; then
i=0
echo "You have the following subscriptions for $game:"
while [[ "${subs[$i]}" != "end-of-list" ]] ; do
echo -e " $i: ${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 subscription
else
subscription=0
fi
fi
echo "Requesting world information for ${world} from the Status Server..."
# Download the status file and get the server adress and login queue adress to
# establish the connection.
# TODO: this file also contains current availability information, use it
# wget --no-check-certificate \
# -q "$serverStatus_URL" \
# -O .launcher/server.config
curl --silent \
--insecure \
"$serverStatus_URL" \
--output .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 world information for ${world}.\n"
cd "${oldDir}"
exit
fi
# Create array of servers
IFS=";"
i=0
for adr in ${loginServers} ; do
serverAddresses[$i]="${adr}"
i=$(($i + 1))
done
serverAddresses[$i]="end-of-list"
i=0
for adr in ${queueUrls} ; do
serverQueues[$i]="${adr}"
i=$(($i + 1))
done
serverQueues[$i]="end-of-list"
unset IFS
# Create the world queue login request for the requested world.
# The ticket can contain special characters and needs to be URL encoded before
# POSTing it (same for queue_url). launcher.config included a parameter template
# for the world queue request, used the same way as the client args below
serverAddress="${serverAddresses[0]}"
serverQueue="${serverQueues[0]}"
rawurlencode() {
local string="${1}"
local strlen=${#string}
local encoded=""
for (( pos=0 ; pos<strlen ; pos++ )); do
c=${string:$pos:1}
case "$c" in
[-_.~a-zA-Z0-9] ) o="${c}" ;;
* ) printf -v o '%%%02x' "'$c"
esac
encoded+="${o}"
done
echo "${encoded}"
}
glsTicketURLencoded=$(rawurlencode "${glsTicket}")
loginQueueURLencoded=$(rawurlencode "${serverQueue}")
worldQueue_ARGS=`echo "${worldQueue_ARGTMPL}" | sed -e "s/[{]0[}]/${subs[$subscription]}/;s/[{]1[}]/${glsTicketURLencoded}/;s/[{]2[}]/${loginQueueURLencoded}/;s/\&\;/\&/g"`
if [[ "${serverAddresses[0]}" != "end-of-list" ]] ; then
echo "${world} Login Servers: ${serverAddresses[0]}"
i=1
while [[ "${serverAddresses[$i]}" != "end-of-list" ]] ; do
echo -e " ${serverAddresses[$i]}"
i=$(($i + 1))
done
else
echo "${world} Login Servers: None"
cd "${oldDir}"
exit
fi
if [[ "${serverQueues[0]}" != "end-of-list" ]] ; then
echo "${world} Login Servers: ${serverQueues[0]}"
i=1
while [[ "${serverQueues[$i]}" != "end-of-list" ]] ; do
echo -e " ${serverQueues[$i]}"
i=$(($i + 1))
done
else
echo "${world} Login Servers: None"
cd "${oldDir}"
exit
fi
echo "Selecting queue: ${serverQueue}"
echo "Queue Arguments: ${worldQueue_ARGS}"
echo "-----------------------------------------------------------------------"
########### LOGIN QUEUE / CLIENT START
function JoinQueue() {
# Now get a queue number from the world login queue so that the client can enqueue and authenticate
inQueue=1
while [ "${inQueue}" == "1" ]; do
# loop when necessary
# wget --no-check-certificate \
# -q --post-data="${worldQueue_ARGS}" \
# "${worldQueue_URL}" \
# -O .launcher/WorldQueue.config
curl --silent \
--insecure \
--data "${worldQueue_ARGS}" \
"${worldQueue_URL}" \
--output .launcher/WorldQueue.config
if ! [ -r .launcher/WorldQueue.config ] ; then
echo -e "\nError: World login queue request failed.\n"
cd "${oldDir}"
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}"
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
}
echo "Joining ${world} Login Server queue..."
# 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
echo "Login to ${world} successful."
# 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/[{]SUBSCRIPTION[}]/${subs[$subscription]}/;
s/[{]LOGIN[}]/${serverAddress}/;
s#[{]GLS[}]#${glsTicket}#;
s/[{]CHAT[}]/${serverChat_URL}/;
s/[{]LANG[}]/${languages[$selectedLanguage]}/;
s#[{]AUTHSERVERURL[}]#${authServer_URL}#;
s/[{]GLSTICKETLIFETIME[}]/${glsTicketLifetime}/;
s#[{]SUPPORTURL[}]#${support_URL}#;
s#[{]BUGURL[}]#${bug_URL}#;
s#[{]SUPPORTSERVICEURL[}]#${supportService_URL}#;
"`
if [ -n "${character}" ] ; then
gameClient_ARGS="$gameClient_ARGS -u $character"
fi
BP='\033[0;34m'
NC='\033[0m'
echo "Launching client:"
if [ -n "${X64}" ] ; then
echo -e "${BP}WINEPREFIX=$gameWinePrefix_DIR $gameWineBin_DIR/wine64 $gameClient_EXE ${gameClient_ARGS} &>/dev/null &${NC}"
WINEPREFIX="$gameWinePrefix_DIR" "$gameWineBin_DIR"/wine64 "$gameClient_EXE" ${gameClient_ARGS} &>/dev/null &
else
echo -e "${BP}WINEPREFIX=$gameWinePrefix_DIR $gameWineBin_DIR/wine $gameClient_EXE ${gameClient_ARGS} &>/dev/null &${NC}"
WINEPREFIX="$gameWinePrefix_DIR" "$gameWineBin_DIR"/wine "$gameClient_EXE" ${gameClient_ARGS} &>/dev/null &
fi
disown
echo "-----------------------------------------------------------------------"
echo "Done. Please allow the DDO client a few seconds to launch."
echo "-----------------------------------------------------------------------"
# Get back to where the caller was
cd "${oldDir}"
exit