Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

How to play AoE2DE multiplayer on linux without steam

Overview

I recently purchased the remaining DLCs for Age of Empires II: Definitive Edition (steam) and I wanted to make sure that my ability to play this game survives Steam, my relationship with them, and even the world wide web.

After a lot of research and the benefit of many Free Software tools, I can play multiplayer AoE2DE with my mods without Steam, on Linux! Here is my method for assembling a package for distributing to my systems without Internet. I have tested only for a few minutes between two machines in the same room. I don't know how this would scale to a vpn or wireless clients.

This does not use system Wine, which crashes immediately with some opengl32 problem.

Building the package

This first asset is the proper Steam directory with all my DLCs installed.

tar -zcf aoe2de-alldlcs-raw.2026-06-28.tgz "${HOME}/.steam/steam/steamapps/common/AoE2DE"
  1. Explode my asset aoe2de-alldlcs-raw.2026-06-28.tgz to ~/aoe2de.

    tar -zx -C ~ -f aoe2de-alldlcs-raw.2026-06-28.tgz
    
  2. Download dependency files to ~/aoe2de/prepare, which is a temporary spot.

    wget https://github.com/GloriousEggroll/proton-ge-custom/releases/download/GE-Proton11-1/GE-Proton11-1.tar.gz https://github.com/Open-Wine-Components/umu-launcher/releases/download/1.4.0/python3-umu-launcher_1.4.0-1_amd64_debian-13.deb https://github.com/Open-Wine-Components/umu-launcher/releases/download/1.4.0/umu-launcher_1.4.0-1_all_debian-13.deb https://github.com/luskaner/ageLANServerLauncherCompanion/releases/download/v1.3.0.0/ageLANServerLauncherCompanion_Age2FakeOnline_1.0.0.0.zip https://github.com/Detanup01/gbe_fork/releases/download/release-2026_05_30/emu-win-release.7z https://github.com/luskaner/ageLANServer/releases/download/v1.14.1/ageLANServer_full_1.14.1_linux_x86-64.tar.gz
    
  3. Move debs to ~/aoe2de/debs. They will need to be installed on any system that uses this package.

  4. Explode GE-Proton11-1 to ~/aoe2de.

    tar -zx -C ~/aoe2de -f ~/aoe2de/prepare/GE-Proton11-1.tar.gz
    
  5. Prepare files from Goldberg Emulator fork.

    cd ~/aoe2de/prepare
    7za x emu-win-release.7z
    cd ~/aoe2de/
    cp -pi prepare/release/steamclient_experimental/{ColdClientLoader.ini,steamclient*.dll,steamclient_loader_x64.exe} .
    

    Prepare the ColdClientLoader.ini. Here is the non-comment values I used.

    [SteamClient]
    Exe=AoE2DE\AoE2DE_s.exe
    ExeRunDir=
    ExeCommandLine=NOSTARTUP SKIPINTRO
    AppId=813780
    SteamClientDll=steamclient.dll
    SteamClient64Dll=steamclient64.dll
    [Injection]
    ForceInjectSteamClient=0
    ForceInjectGameOverlayRenderer=0
    DllsToInjectFolder=dlls
    IgnoreInjectionError=1
    IgnoreLoaderArchDifference=0
    [Persistence]
    Mode=0
    [Debug]
    ResumeByDebugger=0
    
  6. Prepare configs for steam emulator

    cd ~/aoe2de
    mkdir -p dlls steam_settings saves
    
  7. Load dlls.

    cd ~/aoe2de/dlls
    7za x ~/aoe2de/prepare/ageLANServerLauncherCompanion_Age2FakeOnline_1.0.0.0.zip
    cp -pi ../prepare/release/steamclient_experimental/GameOverlayRenderer64.dll ../prepare/release/steamclient_experimental/extra_dlls/steamclient_extra_x64.dll .
    
  8. Prepare steam_settings.

    Prepare these based on Reference 5.

    File achievements.json is lengthy and not shown here.

    The DLCs that I own are these, for file configs.app.ini.

    [app::dlcs]
    unlock_all=0
    # Expansions
    2141580=Age of Empires II: Definitive Edition - Return of Rome
    2555420=Age of Empires II: Definitive Edition - The Mountain Royals
    #2805510=Age of Empires II: Definitive Edition - Victors and Vanquished
    2805520=Age of Empires II: DE - Chronicles: Battle for Greece
    3080080=Age of Empires II: DE - The Three Kingdoms
    #3793540=Age of Empires II: DE - Chronicles: Alexander the Great
    3219700=Age of Empires II: DE – The Last Chieftains
    1389240=Age of Empires II: Definitive Edition - Lords of the West
    1869820=Age of Empires II: Definitive Edition - Dynasties of India
    1557210=Age of Empires II: Definitive Edition - Dawn of the Dukes
    # Animated Icons
    #2672800=Age of Empires II: Definitive Edition – La Hire’s Dry Humor Animated Icons
    #2672810=Age of Empires II: Definitive Edition – Joan’s Marvelous Moving Masterpieces Animated Icons
    #2672820=Age of Empires II: Definitive Edition – Barbarossa’s Barrel of Pickled Goods Animated Icons
    # Other
    #1039811=Enhanced Graphics Pack
    

    Prepare file configs.user.ini.

    [user::general]
    # Fill your account name as you would normally see.
    account_name=SpacesAreEvil65
    # Fill with your real steamid or leave empty for it to be auto-generated.
    account_steamid=
    # Choose one from steam_settings\supported_languages.txt (described in https://partner.steamgames.com/doc/store/localization/languages) or leave empty to be set as 'english'
    language=
    # Choose from 'Alpha-2' country code: https://www.iban.com/country-codes or leave empty to be set as 'US'
    ip_country=
    
    [user::saves]
    local_save_path=saves
    

    File supported_languages.txt is exactly as is from Reference 5.

    english
    french
    italian
    german
    japanese
    koreana
    brazilian
    russian
    schinese
    latam
    tchinese
    turkish
    vietnamese
    spanish
    polish
    hindi
    malay
    
  9. Prepare launcher script aoe2de.sh.

    #!/bin/sh
    PROTON_DISABLE_LSTEAMCLIENT=1 WINEPREFIX=~/.wine-aoe2de PROTONPATH=~/aoe2de/GE-Proton11-1 umu-run ~/aoe2de/steamclient_loader_x64.exe NOSTARTUP SKIPINTRO

    The first time you run, umu wants to download components from the Internet for your user. Client scripts set-initial-umu.sh might be able to prevent that requirement.

  10. Prepare scripts/ files.

    files/2026/listings/aoe2de-get-account-steamid.sh (Source)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #!/bin/sh
    if test -z "${INFILE}" ;
    then
        scriptdir="$( dirname "$( readlink -f "${0}" )" )"
        # find first one. IGNORE the saves/settings/configs.user.ini which is the auto-generated one.
        INFILE="$( find "${scriptdir}" "${scriptdir}/.." "${scriptdir}/../.." -ipath '*/steam_settings/configs.user.ini' -print -quit )"
    fi
    final_steamid="$( awk -F'=' '$1~/account_steamid/{print $NF}' "${INFILE}" )"
    echo "account_steamid = ${final_steamid}"
    
    files/2026/listings/aoe2de-get-username.sh (Source)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #!/bin/sh
    if test -z "${INFILE}" ;
    then
        scriptdir="$( dirname "$( readlink -f "${0}" )" )"
        # find first one
        INFILE="$( find "${scriptdir}" "${scriptdir}/.." "${scriptdir}/../.." -ipath '*/steam_settings/configs.user.ini' -print -quit )"
    fi
    
    awk -F'=' '$1~/account_name/{print $NF}' "${INFILE}"
    
    files/2026/listings/aoe2de-set-account-steamid.sh (Source)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #!/bin/sh
    # Useful because the emulator seems to pick the same number on every system when it needs to generate its own account_steamid.
    # Dependencies:
    #    dep-devuan: moreutils
    # Env vars: APPLY VERBOSE
    if test -z "${INFILE}" ;
    then
        scriptdir="$( dirname "$( readlink -f "${0}" )" )"
        # find first one. IGNORE the saves/settings/configs.user.ini which is the auto-generated one.
        INFILE="$( find "${scriptdir}" "${scriptdir}/.." "${scriptdir}/../.." -ipath '*/steam_settings/configs.user.ini' -print -quit )"
    fi
    
    # Steam id is 17 digits long, decimal, and starts with 765611982
    new_steamid="765611982$( </dev/random tr -dc '0-9' | head -c8 )"
    
    # modconf.py breaks on set account_steamid because of the underscore. I will have to do this with awk or sed or similar 2026-06-30-3 23:55
    #if test -n "${VERBOSE}" ; then echo "/usr/libexec/bgscripts/py/modconf.py ${APPLY:+-a} ${VERBOSE:+-v} -s '[user::general]' \"${INFILE}\" set account_steamid \"${new_steamid}\"" ; fi
    #/usr/libexec/bgscripts/py/modconf.py ${APPLY:+-a} ${VERBOSE:+-v} -s '[user::general]' "${INFILE}" set account_steamid "${new_steamid}"
    
    # So use basic awk instead
    if test -n "${VERBOSE}" ;
    then
        echo "awk -v \"b=${new_steamid}\" -F'=' 'BEGIN{OFS=\"=\"} \$1~/account_steamid/{\$2=b} {print}' \"${INFILE}\" | sponge \"${INFILE}\""
    fi
    if test -n "${APPLY}" ;
    then
        awk -v "b=${new_steamid}" -F'=' 'BEGIN{OFS="="} $1~/account_steamid/{$2=b} {print}' "${INFILE}" | sponge "${INFILE}"
        # Regardless if it worked, read the value and show it.
        final_steamid="$( awk -F'=' '$1~/account_steamid/{print $NF}' "${INFILE}" )"
        echo "account_steamid = ${final_steamid}"
    fi
    
    # modconf.py has a bug where it adds an additional newline after that updated line, so
    # collapse multiple empty lines into a single empty line: ref https://unix.stackexchange.com/a/72749
    awk '!NF {if (++n <= 1) print; next}; {n=0;print}' "${INFILE}" | sponge "${INFILE}"
    if test -z "${APPLY}" ; then echo "Dry run only! Use APPLY=1" 1>&2 ; fi
    
    files/2026/listings/aoe2de-set-cert-from-server.sh (Source)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #!/bin/sh
    IPADDRESS="${IPADDRESS:-${1}}"
    if test -z "${INFILE}" ;
    then
        scriptdir="$( dirname "$( readlink -f "${0}" )" )"
        INFILE="$( find "${scriptdir}" "${scriptdir}/.." "${scriptdir}/../.." -ipath '*/certificates/cacert.pem' -print -quit )"
    fi
    INFILE="$( readlink -f "${INFILE}" )"
    if test -z "${IPADDRESS}" ;
    then
        echo "Fatal! Provide \$1 or IPADDRESS (IP address or hostname) to fetch certificate. Aborted." 1>&2
        exit 1
    fi
    cert="$( </dev/null openssl s_client -connect "${IPADDRESS}:443" 2>/dev/null | openssl x509 -in /dev/stdin )"
    # Exit if the connection failed. Openssl x509 already printed an error, so do not print another.
    if test -z "${cert}" ; then exit 1 ; fi
    next_next_to_last="$( echo "${cert}" | awk '{a[NR]=$0} END {print a[NR-2]}' )"
    if ! grep -q -e "${next_next_to_last}" "${INFILE}" ;
    then
        echo "You need the cert! File ${INFILE} does not have the cert."
        if test -n "${APPLY}" ;
        then
            echo "Applying the the cert." 1>&2
            echo "${cert}" >> "${INFILE}"
        else
            echo "Dry run only! Please run with APPLY=1 to add the cert."
        fi
    else
        echo "File ${INFILE} has the cert already."
    fi
    
    files/2026/listings/aoe2de-set-etc-hosts.sh (Source)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    #!/bin/sh
    # Dependencies:
    #    dep-devuan: bgscripts
    # TODO:
    #    clean up #ageLANServer entries
    scriptdir="$( dirname "$( readlink -f "${0}" )" )"
    INFILE="${INFILE:-/etc/hosts}"
    mkdir -p "${scriptdir}/backups"
    # needs bgscripts
    sudo bup "${INFILE}" "${scriptdir}/backups"
    IPADDRESS="${IPADDRESS:-${1}}"
    if test -z "${IPADDRESS}" ;
    then
        echo "Fatal! Need \$1 or IPADDRESS. Aborted." 1>&1
        exit 1
    fi
    # I need to remove the loopback address for this host, only if this is the server
    echo "WARNING: for server only, please modify /etc/hosts and remove 127.0.0.1-type entries. Not implemented automatically yet." 1>&2
    
    # nice idea, but I don't want to depend on this
    # This only adds the relevant api.ageofempires.com entries.
    #sudo "${scriptdir}/../ageLANServer/linux/launcher/bin/config-admin" setup --game age2 --ip "${IPADDRESS}"
    
    _resolved="$( getent hosts "${IPADDRESS}" | awk '{print $1}' )"
    echo "Using resolved ip address ${_resolved}" 1>&2
    
    for word in \
    aoe-api.reliclink.com \
    aoe-api.worldsedgelink.com \
    api.ageofempires.com \
    cdn.ageofempires.com \
    pb-live-release1-api.worldsedgelink.com \
    pb-live-release2-api.worldsedgelink.com \
    pb-live-release3-api.worldsedgelink.com \
    pb-live-release4-api.worldsedgelink.com \
    pb-live-release5-api.worldsedgelink.com \
    pb-live-release6-api.worldsedgelink.com \
    pb-live-release7-api.worldsedgelink.com \
    pb-live-release8-api.worldsedgelink.com \
    pb-live-release9-api.worldsedgelink.com \
    ; do
        long="${word}#ageLANServer"
        # depends on bgscripts
        if test -n "${VERBOSE}" ; then
            echo "sudo updateval -v ${INFILE}" \"${_resolved} ${long}\" \"${_resolved} ${long}\"
        fi
        if test -n "${APPLY}" ; then
            sudo updateval -a "${INFILE}" "${_resolved} ${long}" "${_resolved} ${long}"
        fi
    done
    
    files/2026/listings/aoe2de-set-initial-mods.sh (Source)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    #!/bin/sh
    scriptdir="$( dirname "$( readlink -f "${0}" )" )"
    INFILE="${INFILE:-/mnt/public/Support/Games/aoe2de-on-linux/aoe2de-mods-subscribed.2026-06-26.tgz}"
    steamid="$( "${scriptdir}/get-account-steamid.sh" | awk '{print $NF}' )"
    WINEPREFIX="${WINEPREFIX:-${HOME}/.wine-aoe2de}"
    destdir="${WINEPREFIX}/drive_c/users/steamuser/Games/Age of Empires 2 DE/${steamid}/mods"
    if test -n "${VERBOSE}" ;
    then
        echo "tar -zx -C \"${destdir}\" -f \"${INFILE}\""
    fi
    if test -n "${APPLY}" ;
    then
        mkdir -p "${destdir}"
        tar -zx -C "${destdir}" -f "${INFILE}"
    else
        echo "Dry run only! Please run with APPLY=1 to apply the initial mods."
    fi
    
    files/2026/listings/aoe2de-set-initial-profile.sh (Source)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    #!/bin/sh
    scriptdir="$( dirname "$( readlink -f "${0}" )" )"
    indir="$( readlink -f "${scriptdir}/../initial-files" )"
    steamid="$( "${scriptdir}/get-account-steamid.sh" | awk '{print $NF}' )"
    WINEPREFIX="${WINEPREFIX:-${HOME}/.wine-aoe2de}"
    destdir="${WINEPREFIX}/drive_c/users/steamuser/Games/Age of Empires 2 DE/${steamid}"
    if test -n "${VERBOSE}" ;
    then
        echo "cp -pr \"${indir}/profile\" \"${destdir}\""
    fi
    if test -n "${APPLY}" ;
    then
        mkdir -p "${destdir}"
        cp -pr "${indir}/profile" "${destdir}"
    else
        echo "Dry run only! Please run with APPLY=1 to apply the initial profile."
    fi
    
    files/2026/listings/aoe2de-set-initial-registry.sh (Source)
    1
    2
    #!/bin/sh
    PROTON_DISABLE_LSTEAMCLIENT=1 WINEPREFIX=~/.wine-aoe2de PROTONPATH=~/aoe2de/GE-Proton11-1 umu-run regedit "x:\aoe2de\initial-files\aoe2de-partial.reg"
    
    files/2026/listings/aoe2de-set-initial-umu.sh (Source)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    #!/bin/sh
    scriptdir="$( dirname "$( readlink -f "${0}" )" )"
    indir="$( readlink -f "${scriptdir}/../initial-files" )"
    destdir="${HOME}/.local/share"
    if test -n "${VERBOSE}" ;
    then
        echo "cp -pr \"${indir}/umu\" \"${destdir}\""
    fi
    if test -n "${APPLY}" ;
    then
        mkdir -p "${destdir}"
        cp -pr "${indir}/umu" "${destdir}"
    else
        echo "Dry run only! Please run with APPLY=1 to apply the initial umu files."
    fi
    
    files/2026/listings/aoe2de-set-username.sh (Source)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #!/bin/sh
    # Dependencies:
    #    dep-devuan: bgscripts, moreutils
    # Env vars: APPLY VERBOSE
    USERNAME="${USERNAME:-${1}}"
    if test -z "${USERNAME}" ; then echo "Fatal! Need \$1 or USERNAME. Aborted." 1>&2 ; exit 1 ; fi
    if test -z "${INFILE}" ;
    then
        scriptdir="$( dirname "$( readlink -f "${0}" )" )"
        # find first one
        INFILE="$( find "${scriptdir}" "${scriptdir}/.." "${scriptdir}/../.." -ipath '*/steam_settings/configs.user.ini' -print -quit )"
    fi
    
    # This depends on bgscripts.
    /usr/libexec/bgscripts/py/modconf.py ${APPLY:+-a} ${VERBOSE:+-v} -s '[user::general]' "${INFILE}" set account_name "${USERNAME}"
    
    # modconf.py has a bug where it adds an additional newline after that updated line, so
    # collapse multiple empty lines into a single empty line: ref https://unix.stackexchange.com/a/72749
    awk '!NF {if (++n <= 1) print; next}; {n=0;print}' "${INFILE}" | sponge "${INFILE}"
    if test -z "${APPLY}" ; then echo "Dry run only! Use APPLY=1" 1>&2 ; fi
    
  11. Prepare ageLANServer assets for server.

    These assets are only needed for the machine that will run the server.

    mkdir -p ~/aoe2de/ageLANServer/linux
    tar -zx -C ~/aoe2de/ageLANServer/linux -f ~/aoe2de/prepare/ageLANServer_full_1.14.1_linux_x86-64.tar.gz
    

    Adjust battle-server-manager/resources/config.age2.toml

    Path = '../../../AoE2DE/BattleServer/BattleServer.exe'
    

    Adjust server/resources/config/config.toml

    Enabled = ["age2"]
    

    I could have written sed statements, but I didn't bother.

  12. Prepare initial files.

    I copied what I think is a reasonably configured "low graphics" user profile from ~/.wine-aoe2de.attempt2/drive_c/users/steamuser/Games/Age\ of\ Empires\ 2\ DE/7656119EXAMPLE892. Place it as ~/aoe2de/initial-files/profile.

    Prepare initial-files/README.md.

    # Profile
    This initial profile already sets graphics to low. Most, but probably not all, my preferred settings are loaded already.
    
    The destination for `profile/` is the following.
    
        ${WINEPREFIX}/drive_c/users/steamuser/Games/Age of Empires 2 DE/7656119EXAMPLE892
    
    # Umu
    This is a snapshot of `${HOME}/.local/share/umu` from 2026-07-01.
    
    # Registry key
    
    It appears some values are in registry, and some are in `${WINEPREFIX}/drive_c/users/steamuser/Games/Age\ of\ Empires\ 2\ DE/${account_steamid}/profile/Player.nfp`. So this is the ones from the registry. However, it also appears to use key `HKEY_USERS\{sid}-{UID}-1000\Software\Microsoft\Microsoft Games\Age of Empires II DE` which is not predictable.
    

    Prepare the initial umu from a working environment. I found one that works for clients that is less than 1GB, with only steamrt4/.

    cp -pr ~/.local/share/umu ~/aoe2de/initial-files/
    

    NOTE: It appears some values are in registry, and some are in ${WINEPREFIX}/drive_c/users/steamuser/Games/Age\ of\ Empires\ 2\ DE/${account_steamid}/profile/Player.nfp.

    This command exports the registry keys for the game to ${HOME}\aoe2de-partial.reg. This depends on symlink ${WINEPREFIX}/dosdevices/x: -> ${HOME}

    PROTON_DISABLE_LSTEAMCLIENT=1 WINEPREFIX=~/.wine-aoe2de PROTONPATH=~/aoe2de/GE-Proton11-1 umu-run regedit /E "x:\aoe2de\initial-files\aoe2de-partial.reg" "HKEY_CURRENT_USER\Software\Microsoft\Microsoft Games\Age of Empires II DE"
    
  13. Build release asset #2.

    tar -zc -C ~ -f ~/aoe2de-other-components-2026-07-01.tgz --exclude 'AoE2DE' --exclude 'prepare' aoe2de
    

    This tar operation only took about r0:03.30.

Description of this working environment

Operations

See also /mnt/public/Support/Games/aoe2de-on-linux/aoe2de-without-steam.md

Installing this offline bundle

A server and client get the same assets. The server needs some additional steps.

  1. Explode tarball of AoE2DE itself, to ~/aoe2de.

    tar -zxf /mnt/public/Support/Games/aoe2de-on-linux/aoe2de-alldlcs-raw.2026-06-28.tgz -C ~
    
  2. Explode the other components.

    tar -zxf /mnt/public/Support/Games/aoe2de-on-linux/aoe2de-other-components-2026-07-01.tgz -C ~
    

Installing a client

In ~/aoe2de, run some of the `scripts/set-*.sh`.

    APPLY=1 ./set-account-steamid.sh # randomize the steamid in the emulator
    APPLY=1 ./set-initial-profile.sh
    APPLY=1 ./set-initial-umu.sh
    APPLY=1 ./set-initial-mods.sh # optional
    APPLY=1 ./set-username.sh "Steve Harvee 3"
    ./set-initial-registry.sh

Some can only be run after the server is up and running; see section "Running a client".

Installing a server

Run each step from the "Installing a client" first, except the ones for after a server is running. When you adjust /etc/hosts, remove or comment any 127.0.0.1 <hostname>-style records.

It is possible that on a new system, you have to run this.

sudo setcap CAP_NET_BIND_SERVICE=+eip ~/aoe2de/ageLANServer/linux/server/server

Initialize a certificate.

~/aoe2de/ageLANServer/linux/server/bin/genCert.sh

Running a client

If you will play multiplayer, you need to know the IP address or hostname of the server. This sets the entries in /etc/hosts which can be done manually if necessary.

APPLY=1 ./set-etc-hosts.sh 192.168.1.45 # IP address or hostname of server
APPLY=1 ./set-cert-from-server.sh 192.168.1.45

Run the short launcher.

~/aoe2de/aoe2de.sh

Running a server

Run the battle server manager in a terminal. It will spawn a new one to run, and return this prompt to you for the next command.

~/aoe2de/ageLANServer/linux/battle-server-manager/start_age2.sh

Run the server.

~/aoe2de/ageLANServer/linux/server/start_age2.sh

Then run the client as described above in "Running a client".

Adding mods

With script

From the second asset, use ~/aoe2de/scripts/set-initial-mods.sh. It depends on this network path for this file.

/mnt/public/Support/Games/aoe2de-on-linux/aoe2de-mods-subscribed.2026-06-26.tgz

Manually

Find the generated userid from ~/aoe2de/saves/settings.configs.user.ini.

eval $( awk '/account_steamid/' ~/aoe2de/saves/settings/configs.user.ini | tr -dc 'a-zA-Z=0-9_' )

Visit the user's mods directory and copy in the mods I have provided.

cd ~/".wine-aoe2de/drive_c/users/steamuser/Games/Age of Empires 2 DE/${account_steamid}/mods"
tar -zxf /mnt/public/Support/Games/aoe2de-on-linux/aoe2de-mods-subscribed.2026-06-26.tgz

Updating the game

A full copy of the official game from steam needs to be updated, and then built into a tarball. The path would be similar to /mnt/vm2/pcb-009-steam/steamapps/common/AoE2DE.

cd /mnt/vm2/pcb-009-steam/steamapps/common
tar -zcf "AoE2DE-$( date "+%F" ).tgz" AoE2DE

Note that this would not have the exact same directory structure as the original first asset which was aoe2de/AoE2DE/.

Updating "other components"

These dependencies might have newer versions. Check the references, and follow the "Building the package" steps.

Updating mods

Build a new tarball from the upstream machine that has the desired mods.

tar -zc -C "/mnt/vm2/pcb-009-steam/steamapps/compatdata/813780/pfx/drive_c/users/steamuser/Games/Age of Empires 2 DE/${account_steamid}/mods" -f /mnt/public/Support/Games/aoe2de-on-linux/aoe2de-mods-subscribed.$( date "+%F" ).tgz

Related files

  • aoe2de-alldlcs-raw.2026-06-28.tgz
  • aoe2de-other-components-2026-07-01.tgz
  • aoe2de-mods-subscribed.2026-06-26.tgz

References

  1. Installing Proton on Linux: A Comprehensive Guide — linuxvox.com
  2. proton ge: https://github.com/GloriousEggroll/proton-ge-custom/releases/download/GE-Proton11-1/GE-Proton11-1.tar.gz from Releases · GloriousEggroll/proton-ge-custom
  3. Open-Wine-Components/umu-launcher: Unified launcher for Windows games on Linux
  4. UMU Database Search shows nothing for "Empires" about AoE2, so GAMEID can be left undefined.
  5. luskaner/ageLANServerLauncherCompanion: Libraries to enhance or fix the game behaviour when using a custom launcher in Age LAN Server
  6. Releases · Detanup01/gbe_fork
  7. luskaner/ageLANServer: Web Server (and its launcher) for AoE 1, 2, 3 (all DE), AoE 4 and AoM: RT supporting offline multiplayer
  8. Age of Empires II: Definitive Edition DLCs · SteamDB to find ids for the two DLCs I own that were not demonstrated in the configs.app.ini

Comments