Procházet zdrojové kódy

feat(bfeserver): add Battlefront Eternal server (#5116)

Adds Battlefront Eternal as a new server: bfeserver, shortname bfe. The server runs on Node.js 24, installed into serverfiles/node from nodejs.org and checked for updates on every update run. The retail game data comes from SteamCMD (app 1058020, needs an account that owns the game) and is prepared with the release's own tools.

Fixes #5112
Daniel Gibbs před 4 dny
rodič
revize
71f16e541c

+ 224 - 0
lgsm/config-default/config-lgsm/bfeserver/_default.cfg

@@ -0,0 +1,224 @@
+##################################
+######## Default Settings ########
+##################################
+# DO NOT EDIT, ANY CHANGES WILL BE OVERWRITTEN!
+# Copy settings from here and use them in either:
+# common.cfg - applies settings to every instance.
+# [instance].cfg - applies settings to a specific instance.
+
+#### Game Server Settings ####
+
+## SteamCMD Login | https://docs.linuxgsm.com/steamcmd#steamcmd-login
+# The Steam account must own STAR WARS Battlefront (Classic, 2004).
+# Battlefront Eternal prepares its game data from this copy.
+steamuser="username"
+steampass='password'
+
+## Health Port
+# Local health and admin endpoint. Each instance needs its own.
+healthport="18450"
+
+## Server Parameters | https://docs.linuxgsm.com/configuration/start-parameters#additional-parameters
+## Game Server Docs | https://battlefronteternal.com/servers/
+# Server name, port, max players and playlist are set in config.json.
+startparameters="${serverfiles}/bfe/host-runtime/server/src/main.js --serve"
+
+## Extra Parameters | https://docs.linuxgsm.com/configuration/start-parameters#extra-parameters
+# Added to the end of startparameters.
+extraparams=""
+
+#### LinuxGSM Settings ####
+
+## LinuxGSM Stats
+# Send useful stats to LinuxGSM developers.
+# https://docs.linuxgsm.com/configuration/linuxgsm-stats
+# (on|off)
+stats="off"
+
+## Notification Alerts
+# (on|off)
+
+# Display IP | https://docs.linuxgsm.com/alerts#display-ip
+displayip=""
+
+# More info | https://docs.linuxgsm.com/alerts#more-info
+postalert="off"
+
+# Alert on Start/Stop/Restart
+statusalert="off"
+
+# Discord Alerts | https://docs.linuxgsm.com/alerts/discord
+discordalert="off"
+discordwebhook="webhook"
+
+# Email Alerts | https://docs.linuxgsm.com/alerts/email
+emailalert="off"
+email="email@example.com"
+emailfrom=""
+
+# Gotify Alerts | https://docs.linuxgsm.com/alerts/gotify
+gotifyalert="off"
+gotifytoken="token"
+gotifywebhook="webhook"
+
+# IFTTT Alerts | https://docs.linuxgsm.com/alerts/ifttt
+iftttalert="off"
+ifttttoken="accesstoken"
+iftttevent="linuxgsm_alert"
+
+# ntfy Alerts | https://docs.linuxgsm.com/alerts/ntfy
+ntfyalert="off"
+ntfytopic="LinuxGSM"
+ntfyserver="https://ntfy.sh"
+ntfytoken=""
+ntfyusername=""
+ntfypassword=""
+ntfypriority=""
+ntfytags=""
+
+# Matrix Alerts | https://docs.linuxgsm.com/alerts/matrix
+matrixalert="off"
+matrixhomeserver="matrix.org"
+matrixtoken="accesstoken"
+matrixroom=""
+
+# Pushbullet Alerts | https://docs.linuxgsm.com/alerts/pushbullet
+pushbulletalert="off"
+pushbullettoken="accesstoken"
+channeltag=""
+
+# Pushover Alerts | https://docs.linuxgsm.com/alerts/pushover
+pushoveralert="off"
+pushovertoken="accesstoken"
+pushoveruserkey="userkey"
+
+# Rocket.Chat Alerts | https://docs.linuxgsm.com/alerts/rocket.chat
+rocketchatalert="off"
+rocketchatwebhook="webhook"
+
+# Slack Alerts | https://docs.linuxgsm.com/alerts/slack
+slackalert="off"
+slackwebhook="webhook"
+
+# Telegram Alerts | https://docs.linuxgsm.com/alerts/telegram
+# You can add a custom cURL string eg proxy (useful in Russia) in "curlcustomstring".
+# For example "--socks5 ipaddr:port" for socks5 proxy see more in "curl --help all".
+telegramapi="api.telegram.org"
+telegramalert="off"
+telegramtoken="accesstoken"
+telegramchatid=""
+telegramthreadid=""
+telegramsilentnotification="false"
+curlcustomstring=""
+
+## Stop | https://docs.linuxgsm.com/commands/start-stop-restart
+stoponlyifnoplayers="off"
+stoponlyifnoplayersallcommands="on"
+
+## Player Warnings | https://docs.linuxgsm.com/features/player-warnings
+# Minutes to warn players in-game before a stop, restart or update. 0 to disable.
+# e.g. 15 warns at 15, 10, 5, 3 and 1 minutes.
+warnplayerstimer="0"
+
+## Updating | https://docs.linuxgsm.com/commands/update
+updateonstart="off"
+
+## Backup | https://docs.linuxgsm.com/commands/backup
+maxbackups="4"
+maxbackupdays="30"
+stoponbackup="on"
+
+## Logging | https://docs.linuxgsm.com/features/logging
+consolelogging="on"
+logdays="7"
+
+## Monitor | https://docs.linuxgsm.com/commands/monitor
+# Query delay time
+querydelay="5"
+
+## ANSI Colors | https://docs.linuxgsm.com/features/ansi-colors
+ansi="on"
+
+#### Advanced Settings ####
+
+## Message Display Time | https://docs.linuxgsm.com/features/message-display-time
+sleeptime="0.5"
+
+## SteamCMD Settings | https://docs.linuxgsm.com/steamcmd
+# Server appid
+appid="1058020"
+steamcmdforcewindows="yes"
+# SteamCMD Branch | https://docs.linuxgsm.com/steamcmd/branch
+branch=""
+betapassword=""
+# Master Server | https://docs.linuxgsm.com/steamcmd/steam-master-server
+steammaster="false"
+
+## Stop Mode | https://docs.linuxgsm.com/features/stop-mode
+# 1: tmux kill
+# 2: CTRL+c
+# 3: quit
+# 4: quit 120s
+# 5: stop
+# 6: q
+# 7: exit
+# 8: 7 Days to Die
+# 9: GoldSrc
+# 10: Avorion
+# 11: end
+# 12: shutdown
+# 13: soulmask
+# 14: Running With Rifles
+stopmode="2"
+
+## Query mode
+# 1: session only
+# 2: gamedig (gsquery fallback)
+# 3: gamedig
+# 4: gsquery
+# 5: tcp
+querymode="5"
+querytype=""
+
+## Console type
+consoleverbose="yes"
+consoleinteract="no"
+
+## Game Server Details
+# Do not edit
+gamename="Battlefront Eternal"
+engine="bfe"
+glibc="2.28"
+
+#### Directories ####
+# Edit with care
+
+## Game Server Directories
+systemdir="${serverfiles}"
+executabledir="${serverfiles}/node/bin"
+executable="./node"
+servercfgdir="${systemdir}/bfe-state"
+servercfg="config.json"
+servercfgdefault="config.json"
+servercfgfullpath="${servercfgdir}/${servercfg}"
+preexecutable="env BFE_STATE_DIR=${servercfgdir} BFE_ENGINE_DIR=${systemdir}/bfe/host-runtime/engine BFE_DATA_DIR=${systemdir}/bfe-data PORT=${healthport}"
+
+## Backup Directory
+backupdir="${lgsmdir}/backup"
+
+## Logging Directories
+[ -n "${LGSM_LOGDIR}" ] && logdir="${LGSM_LOGDIR}" || logdir="${rootdir}/log"
+lgsmlogdir="${logdir}/script"
+consolelogdir="${logdir}/console"
+lgsmlog="${lgsmlogdir}/${selfname}-script.log"
+consolelog="${consolelogdir}/${selfname}-console.log"
+alertlog="${lgsmlogdir}/${selfname}-alert.log"
+postdetailslog="${lgsmlogdir}/${selfname}-postdetails.log"
+
+## Logs Naming
+lgsmlogdate="${lgsmlogdir}/${selfname}-script-$(date '+%Y-%m-%d-%H:%M:%S').log"
+consolelogdate="${consolelogdir}/${selfname}-console-$(date '+%Y-%m-%d-%H:%M:%S').log"
+
+## Log Parameters
+logtimestamp="off"
+logtimestampformat="%Y-%m-%d %H:%M:%S"

+ 1 - 0
lgsm/data/almalinux-8.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl.i686
 bd
 bf1942,ncurses-libs.i686
+bfe
 bfv,glibc.i686
 bmdm,ncurses-libs.i686
 bo

+ 1 - 0
lgsm/data/almalinux-9.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl.i686
 bd
 bf1942,ncurses-libs.i686
+bfe
 bfv,glibc.i686
 bmdm,ncurses-libs.i686
 bo

+ 1 - 0
lgsm/data/centos-7.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl.i686
 bd
 bf1942,ncurses-libs.i686
+bfe
 bfv,glibc.i686
 bmdm,ncurses-libs.i686
 bo

+ 1 - 0
lgsm/data/centos-8.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl.i686
 bd
 bf1942,ncurses-libs.i686
+bfe
 bfv,glibc.i686
 bmdm,ncurses-libs.i686
 bo

+ 1 - 0
lgsm/data/centos-9.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl.i686
 bd
 bf1942,ncurses-libs.i686
+bfe
 bfv,glibc.i686
 bmdm,ncurses-libs.i686
 bo

+ 1 - 0
lgsm/data/debian-10.csv

@@ -11,6 +11,7 @@ bb
 bb2,libcurl4-gnutls-dev:i386
 bd
 bf1942,libc6:i386
+bfe
 bfv,libc6:i386
 bmdm,libncurses5:i386
 bo

+ 1 - 0
lgsm/data/debian-11.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl4-gnutls-dev:i386
 bd
 bf1942,libc6:i386
+bfe
 bfv,libc6:i386
 bmdm,libncurses5:i386
 bo

+ 1 - 0
lgsm/data/debian-12.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl4-gnutls-dev:i386
 bd
 bf1942,libc6:i386
+bfe
 bfv,libc6:i386
 bmdm,libncurses5:i386
 bo

+ 1 - 0
lgsm/data/debian-13.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl4-gnutls-dev:i386
 bd
 bf1942,libc6:i386
+bfe
 bfv,libc6:i386
 bmdm
 bo

+ 1 - 0
lgsm/data/debian-9.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl4-gnutls-dev:i386
 bd
 bf1942,libc6:i386
+bfe
 bfv,libc6:i386
 bmdm,libncurses5:i386
 bo

binární
lgsm/data/gameicons/bfe-icon.png


+ 1 - 0
lgsm/data/rhel-7.csv

@@ -14,6 +14,7 @@ pw
 bb2,libcurl.i686
 bd
 bf1942,ncurses-libs.i686
+bfe
 bfv,glibc.i686
 bmdm,ncurses-libs.i686
 bo

+ 1 - 0
lgsm/data/rhel-8.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl.i686
 bd
 bf1942,ncurses-libs.i686
+bfe
 bfv,glibc.i686
 bmdm,ncurses-libs.i686
 bo

+ 1 - 0
lgsm/data/rhel-9.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl.i686
 bd
 bf1942,ncurses-libs.i686
+bfe
 bfv,glibc.i686
 bmdm,ncurses-libs.i686
 bo

+ 1 - 0
lgsm/data/rocky-8.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl.i686
 bd
 bf1942,ncurses-libs.i686
+bfe
 bfv,glibc.i686
 bmdm,ncurses-libs.i686
 bo

+ 1 - 0
lgsm/data/rocky-9.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl.i686
 bd
 bf1942,ncurses-libs.i686
+bfe
 bfv,glibc.i686
 bmdm,ncurses-libs.i686
 bo

+ 1 - 0
lgsm/data/serverlist.csv

@@ -11,6 +11,7 @@ bb,bbserver,BrainBread,ubuntu-26.04
 bb2,bb2server,BrainBread 2,ubuntu-26.04
 bd,bdserver,Base Defense,ubuntu-26.04
 bf1942,bf1942server,Battlefield 1942,ubuntu-26.04
+bfe,bfeserver,Battlefront Eternal,ubuntu-26.04
 bfv,bfvserver,Battlefield: Vietnam,ubuntu-26.04
 bmdm,bmdmserver,Black Mesa: Deathmatch,ubuntu-26.04
 bo,boserver,Ballistic Overkill,ubuntu-26.04

+ 1 - 0
lgsm/data/ubuntu-16.04.csv

@@ -14,6 +14,7 @@ pw
 bb2,libcurl4-gnutls-dev:i386
 bd
 bf1942,libc6:i386
+bfe
 bfv,libc6:i386
 bmdm,libncurses5:i386
 bo

+ 1 - 0
lgsm/data/ubuntu-18.04.csv

@@ -14,6 +14,7 @@ bb
 bb2,libcurl4-gnutls-dev:i386
 bd
 bf1942,libc6:i386
+bfe
 bfv,libc6:i386
 bmdm,libncurses5:i386
 bo

+ 1 - 0
lgsm/data/ubuntu-20.04.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl4-gnutls-dev:i386
 bd
 bf1942,libc6:i386
+bfe
 bfv,libc6:i386
 bmdm,libncurses5:i386
 bo

+ 1 - 0
lgsm/data/ubuntu-22.04.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl4-gnutls-dev:i386
 bd
 bf1942,libc6:i386
+bfe
 bfv,libc6:i386
 bmdm,libncurses5:i386
 bo

+ 1 - 0
lgsm/data/ubuntu-23.04.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl4-gnutls-dev:i386
 bd
 bf1942,libc6:i386
+bfe
 bfv,libc6:i386
 bmdm,libncurses5:i386
 bo

+ 1 - 0
lgsm/data/ubuntu-23.10.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl4-gnutls-dev:i386
 bd
 bf1942,libc6:i386
+bfe
 bfv,libc6:i386
 bmdm,libncurses5:i386
 bo

+ 1 - 0
lgsm/data/ubuntu-24.04.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl4-gnutls-dev:i386
 bd
 bf1942,libc6:i386
+bfe
 bfv,libc6:i386
 bmdm
 bo

+ 1 - 0
lgsm/data/ubuntu-26.04.csv

@@ -12,6 +12,7 @@ bb
 bb2,libcurl4-gnutls-dev:i386
 bd
 bf1942,libc6:i386
+bfe
 bfv,libc6:i386
 bmdm
 bo

+ 2 - 0
lgsm/modules/check_system_requirements.sh

@@ -51,6 +51,8 @@ elif [ "${shortname}" == "pz" ]; then
 	ramrequirementgb="3"
 elif [ "${shortname}" == "rsdw" ]; then
 	ramrequirementgb="2"
+elif [ "${shortname}" == "bfe" ]; then
+	ramrequirementgb="2"
 elif [ "${shortname}" == "rwr" ]; then
 	ramrequirementgb="1"
 elif [ "${shortname}" == "rust" ]; then

+ 2 - 0
lgsm/modules/command_check_update.sh

@@ -38,6 +38,8 @@ elif [ "${shortname}" == "orar" ]; then
 	update_openra.sh
 elif [ "${shortname}" == "vints" ]; then
 	update_vints.sh
+elif [ "${shortname}" == "bfe" ]; then
+	update_bfe.sh
 elif [ "${shortname}" == "bb" ]; then
 	update_bb.sh
 elif [ "${shortname}" == "ut99" ]; then

+ 2 - 0
lgsm/modules/command_update.sh

@@ -79,6 +79,8 @@ elif [ "${shortname}" == "orar" ]; then
 	update_openra.sh
 elif [ "${shortname}" == "vints" ]; then
 	update_vints.sh
+elif [ "${shortname}" == "bfe" ]; then
+	update_bfe.sh
 elif [ "${shortname}" == "bb" ]; then
 	update_bb.sh
 elif [ "${shortname}" == "ut99" ]; then

+ 5 - 0
lgsm/modules/core_modules.sh

@@ -787,6 +787,11 @@ update_ut99.sh() {
 	fn_fetch_module
 }
 
+update_bfe.sh() {
+	modulefile="${FUNCNAME[0]}"
+	fn_fetch_module
+}
+
 update_bb.sh() {
 	modulefile="${FUNCNAME[0]}"
 	fn_fetch_module

+ 20 - 30
lgsm/modules/fix_steamcmd.sh

@@ -65,21 +65,16 @@ fi
 
 # place new hardlink for the file to the disk
 if [ ! -f "${steamclientsdk64}" ]; then
-	fixname="steamclient.so sdk64 hardlink"
-	fn_fix_msg_start
-	if [ ! -d "${steamsdk64}" ]; then
-		mkdir -p "${steamsdk64}"
-	fi
-	if [ -f "${HOME}/.steam/steamcmd/linux64/steamclient.so" ]; then
-		ln "${HOME}/.steam/steamcmd/linux64/steamclient.so" "${steamclientsdk64}"
-	elif [ -f "${steamcmddir}/linux64/steamclient.so" ]; then
-		ln "${steamcmddir}/linux64/steamclient.so" "${steamclientsdk64}"
-	elif [ -f "${HOME}/.local/share/Steam/steamcmd/linux64/steamclient.so" ]; then
-		ln "${HOME}/.local/share/Steam/steamcmd/linux64/steamclient.so" "${steamclientsdk64}"
-	else
-		fn_print_fail_nl "Could not copy any steamclient.so 64bit for the gameserver"
-	fi
-	fn_fix_msg_end
+	for steamclientso in "${HOME}/.steam/steamcmd/linux64/steamclient.so" "${steamcmddir}/linux64/steamclient.so" "${HOME}/.local/share/Steam/steamcmd/linux64/steamclient.so"; do
+		if [ -f "${steamclientso}" ]; then
+			fixname="steamclient.so sdk64 hardlink"
+			fn_fix_msg_start
+			mkdir -p "${steamsdk64}"
+			ln "${steamclientso}" "${steamclientsdk64}"
+			fn_fix_msg_end
+			break
+		fi
+	done
 fi
 
 # Helps fix: [S_API FAIL] SteamAPI_Init() failed; unable to locate a running instance of Steam,or a local steamclient.so.
@@ -96,21 +91,16 @@ fi
 
 # place new hardlink for the file to the disk
 if [ ! -f "${steamclientsdk32}" ]; then
-	fixname="steamclient.so sdk32 link"
-	fn_fix_msg_start
-	if [ ! -d "${steamsdk32}" ]; then
-		mkdir -p "${steamsdk32}"
-	fi
-	if [ -f "${HOME}/.steam/steamcmd/linux32/steamclient.so" ]; then
-		ln "${HOME}/.steam/steamcmd/linux32/steamclient.so" "${steamclientsdk32}"
-	elif [ -f "${steamcmddir}/linux32/steamclient.so" ]; then
-		ln "${steamcmddir}/linux32/steamclient.so" "${steamclientsdk32}"
-	elif [ -f "${HOME}/.local/share/Steam/steamcmd/linux32/steamclient.so" ]; then
-		ln "${HOME}/.local/share/Steam/steamcmd/linux32/steamclient.so" "${steamclientsdk32}"
-	else
-		fn_print_fail_nl "Could not copy any steamclient.so 32bit for the gameserver"
-	fi
-	fn_fix_msg_end
+	for steamclientso in "${HOME}/.steam/steamcmd/linux32/steamclient.so" "${steamcmddir}/linux32/steamclient.so" "${HOME}/.local/share/Steam/steamcmd/linux32/steamclient.so"; do
+		if [ -f "${steamclientso}" ]; then
+			fixname="steamclient.so sdk32 link"
+			fn_fix_msg_start
+			mkdir -p "${steamsdk32}"
+			ln "${steamclientso}" "${steamclientsdk32}"
+			fn_fix_msg_end
+			break
+		fi
+	done
 fi
 
 # Helps fix: WARNING: Failed to load 32-bit libtinfo.so.5 or libncurses.so.5.

+ 23 - 0
lgsm/modules/info_game.sh

@@ -975,6 +975,27 @@ fn_info_game_bf1942() {
 	serverpassword="${serverpassword:-"NOT SET"}"
 }
 
+# Config Type: json
+# Parameters: false
+# Comment: none
+# Example: "name": "SERVERNAME",
+# Filetype: json
+fn_info_game_bfe() {
+	if [ -f "${servercfgfullpath}" ]; then
+		fn_info_game_json "configip" ".bind // empty"
+		fn_info_game_json "discoveryport" ".discoveryPort // empty"
+		fn_info_game_json "maxplayers" ".maxPlayers // empty"
+		fn_info_game_json "port" ".gamePort // empty"
+		fn_info_game_json "servername" ".name // empty"
+	fi
+	configip="${configip:-"0.0.0.0"}"
+	discoveryport="${discoveryport:-"18452"}"
+	maxplayers="${maxplayers:-"64"}"
+	port="${port:-"18451"}"
+	queryport="${port}"
+	servername="${servername:-"NOT SET"}"
+}
+
 # Config Type: con
 # Parameters: true
 # Comment: # or //
@@ -2470,6 +2491,8 @@ elif [ "${shortname}" == "av" ]; then
 	fn_info_game_av
 elif [ "${shortname}" == "bf1942" ]; then
 	fn_info_game_bf1942
+elif [ "${shortname}" == "bfe" ]; then
+	fn_info_game_bfe
 elif [ "${shortname}" == "bfv" ]; then
 	fn_info_game_bfv
 elif [ "${shortname}" == "bo" ]; then

+ 14 - 1
lgsm/modules/info_messages.sh

@@ -736,7 +736,7 @@ fn_info_messages_ports_edit() {
 
 	startparameterslocation="${red}UNKNOWN${default}"
 	# engines/games that require editing in the config file.
-	local ports_edit_array=("ac" "arma3" "armar" "bo" "bt" "ct" "dst" "eco" "idtech2" "idtech3" "idtech3_ql" "jc2" "jc3" "lwjgl2" "mcb" "nec" "pc" "pc2" "prism3d" "pz" "qw" "refractor" "renderware" "rw" "rwr" "sb" "sdtd" "st" "stn" "ts3" "tw" "terraria" "unreal" "unreal2" "unreal3" "vints" "xnt" "wurm")
+	local ports_edit_array=("ac" "arma3" "armar" "bfe" "bo" "bt" "ct" "dst" "eco" "idtech2" "idtech3" "idtech3_ql" "jc2" "jc3" "lwjgl2" "mcb" "nec" "pc" "pc2" "prism3d" "pz" "qw" "refractor" "renderware" "rw" "rwr" "sb" "sdtd" "st" "stn" "ts3" "tw" "terraria" "unreal" "unreal2" "unreal3" "vints" "xnt" "wurm")
 	for port_edit in "${ports_edit_array[@]}"; do
 		if [ "${shortname}" == "ut3" ]; then
 			startparameterslocation="${servercfgdir}/UTWeb.ini"
@@ -765,6 +765,8 @@ fn_info_messages_ports() {
 		portcommand="ss -tuplwn | grep AvorionServer"
 	elif [ "${shortname}" == "bf1942" ]; then
 		portcommand="ss -tuplwn | grep bf1942_lnxded"
+	elif [ "${shortname}" == "bfe" ]; then
+		portcommand="ss -tuplwn | grep MainThread"
 	elif [ "${shortname}" == "bfv" ]; then
 		portcommand="ss -tuplwn | grep bfv_linded"
 	elif [ "${shortname}" == "ce" ]; then
@@ -930,6 +932,15 @@ fn_info_messages_bf1942() {
 	} | column -s $'\t' -t
 }
 
+fn_info_messages_bfe() {
+	{
+		fn_port "header"
+		fn_port "Game" port udp
+		fn_port "Query" queryport tcp
+		fn_port "LAN Discovery" discoveryport udp
+	} | column -s $'\t' -t
+}
+
 fn_info_messages_bfv() {
 	{
 		fn_port "header"
@@ -1904,6 +1915,8 @@ fn_info_messages_select_engine() {
 		fn_info_messages_av
 	elif [ "${shortname}" == "bf1942" ]; then
 		fn_info_messages_bf1942
+	elif [ "${shortname}" == "bfe" ]; then
+		fn_info_messages_bfe
 	elif [ "${shortname}" == "bfv" ]; then
 		fn_info_messages_bfv
 	elif [ "${shortname}" == "bo" ]; then

+ 5 - 0
lgsm/modules/install_config.sh

@@ -346,6 +346,11 @@ elif [ "${shortname}" == "bs" ]; then
 	fn_default_config_remote
 	fn_set_config_vars
 	fn_list_config_locations
+elif [ "${shortname}" == "bfe" ]; then
+	array_configs+=(config.json)
+	fn_default_config_remote
+	fn_set_config_vars
+	fn_list_config_locations
 elif [ "${shortname}" == "bb" ]; then
 	array_configs+=(server.cfg)
 	fn_default_config_remote

+ 2 - 0
lgsm/modules/install_server_files.sh

@@ -285,6 +285,8 @@ elif [ "${shortname}" == "orar" ]; then
 	update_openra.sh
 elif [ "${shortname}" == "vints" ]; then
 	update_vints.sh
+elif [ "${shortname}" == "bfe" ]; then
+	update_bfe.sh
 elif [ "${shortname}" == "bb" ]; then
 	update_bb.sh
 elif [ "${shortname}" == "ut99" ]; then

+ 256 - 0
lgsm/modules/update_bfe.sh

@@ -0,0 +1,256 @@
+#!/bin/bash
+# LinuxGSM update_bfe.sh module
+# Author: Daniel Gibbs
+# Contributors: https://linuxgsm.com/contrib
+# Website: https://linuxgsm.com
+# Description: Handles updating of Battlefront Eternal servers.
+
+moduleselfname="$(basename "$(readlink -f "${BASH_SOURCE[0]}")")"
+
+fn_update_node_remote() {
+	# Gets the latest Node.js 24 build and the installed version.
+	nodeurl="https://nodejs.org/dist/latest-v24.x"
+	nodeshasums=$(curl -s "${nodeurl}/SHASUMS256.txt")
+	nodefilename=$(echo "${nodeshasums}" | grep -o "node-v[0-9.]*-linux-${nodearch}.tar.xz" | head -1)
+	nodeversion=$(echo "${nodefilename}" | cut -d '-' -f 2)
+	nodehash=$(echo "${nodeshasums}" | grep " ${nodefilename}$" | awk '{print $1}')
+	if [ -z "${nodefilename}" ] || [ -z "${nodehash}" ]; then
+		fn_print_failure_nl "Unable to get Node.js build"
+		fn_script_log_fail "Unable to get Node.js build"
+		core_exit.sh
+	fi
+	nodelocalversion=""
+	if [ -x "${serverfiles}/node/bin/node" ]; then
+		nodelocalversion=$("${serverfiles}/node/bin/node" --version 2> /dev/null)
+	fi
+}
+
+fn_update_node_dl() {
+	# Installs the Node.js runtime the server runs on.
+	fn_fetch_file "${nodeurl}/${nodefilename}" "" "" "" "${tmpdir}" "${nodefilename}" "nochmodx" "norun" "forcedl" "${nodehash}"
+	rm -rf "${serverfiles:?}/node.new"
+	fn_dl_extract "${tmpdir}" "${nodefilename}" "${serverfiles}/node.new" "${nodefilename%.tar.xz}"
+	rm -rf "${serverfiles:?}/node"
+	mv "${serverfiles}/node.new" "${serverfiles}/node"
+	fn_clear_tmp
+}
+
+fn_update_node() {
+	fn_update_node_remote
+	if [ "${nodelocalversion}" != "${nodeversion}" ]; then
+		fn_update_node_dl
+	fi
+}
+
+fn_update_node_compare() {
+	fn_print_dots "Checking for update: nodejs.org"
+	fn_update_node_remote
+	if [ "${nodelocalversion}" != "${nodeversion}" ]; then
+		date '+%s' > "${lockdir:?}/update.lock"
+		fn_print_ok_nl "Checking for update: nodejs.org"
+		fn_print "\n"
+		fn_print_nl "${bold}${underline}Node.js update${default} available"
+		fn_print_nl "* Local version: ${red}${nodelocalversion:-none}${default}"
+		fn_print_nl "* Remote version: ${green}${nodeversion}${default}"
+		fn_print "\n"
+		fn_script_log_info "Node.js update available"
+		fn_script_log_info "Local version: ${nodelocalversion:-none}"
+		fn_script_log_info "Remote version: ${nodeversion}"
+		previousbuild="Node.js ${nodelocalversion:-none}"
+		localbuild="Node.js ${nodelocalversion:-none}"
+		remotebuild="Node.js ${nodeversion}"
+		if [ "${commandname}" == "UPDATE" ]; then
+			unset updateonstart
+			fn_update_apply fn_update_node_dl
+			localbuild="${remotebuild}"
+			alert="update"
+		elif [ "${commandname}" == "CHECK-UPDATE" ]; then
+			alert="check-update"
+		fi
+		alert.sh
+	else
+		fn_print_ok_nl "Checking for update: nodejs.org"
+		fn_print "\n"
+		fn_print_nl "${bold}${underline}No Node.js update${default} available"
+		fn_print_nl "* Local version: ${green}${nodelocalversion}${default}"
+		fn_print_nl "* Remote version: ${green}${nodeversion}${default}"
+		fn_print "\n"
+		fn_script_log_info "No Node.js update available"
+		fn_script_log_info "Local version: ${nodelocalversion}"
+		fn_script_log_info "Remote version: ${nodeversion}"
+	fi
+}
+
+fn_update_prepare() {
+	# Prepares the game data from the owned game when the release's tools change.
+	datakey=$(jq -r '.files[] | select((.path | startswith("resources/tools/")) or (.path | startswith("resources/toolchain/python/"))) | "\(.path) \(.sha256)"' "${serverfiles}/bfe/bfe-tree.json" | LC_ALL=C sort)
+	datakey=$(printf '%s' "${datakey}" | sha256sum | cut -c 1-32)
+	if [ -f "${serverfiles}/bfe-data/.lgsm-data-key" ] && [ "$(cat "${serverfiles}/bfe-data/.lgsm-data-key")" == "${datakey}" ]; then
+		return
+	fi
+	if [ ! -d "${serverfiles}/GameData" ]; then
+		fn_print_failure_nl "Game data not found: ${serverfiles}/GameData"
+		fn_script_log_fail "Game data not found: ${serverfiles}/GameData"
+		echo -e "* The Steam account must own STAR WARS Battlefront (Classic, 2004)."
+		echo -e "* Run ./${selfname} validate to download it."
+		core_exit.sh
+	fi
+	preparelog="${lgsmlogdir}/${selfname}-prepare.log"
+	echo -e "preparing game data, this can take several minutes"
+	fn_script_log_info "Preparing game data ${datakey}"
+	rm -rf "${serverfiles:?}/bfe-data.new"
+	mkdir -p "${serverfiles}/bfe-data.new"
+	(
+		cd "${serverfiles}/bfe/tools" || exit 1
+		env -i PATH="${serverfiles}/bfe/toolchain/bin:/usr/local/bin:/usr/bin:/bin" HOME="${serverfiles}/bfe-data.new" LANG="C.UTF-8" BFE_GAME_DIR="${serverfiles}" BFE_CONTENT_DIR="${serverfiles}/bfe-data.new" KTX2_ENCODER="off" PYTHONUNBUFFERED="1" PYTHONDONTWRITEBYTECODE="1" PYTHONIOENCODING="utf-8" "${serverfiles}/bfe/toolchain/python/bin/python3" -u prepare.py --core-only --jobs "$(nproc)" > "${preparelog}" 2>&1
+	)
+	exitcode=$?
+	if [ "${exitcode}" -ne 0 ]; then
+		fn_print_failure_nl "Preparing game data"
+		fn_script_log_fail "Preparing game data, see ${preparelog}"
+		tail -n 20 "${preparelog}"
+		rm -rf "${serverfiles:?}/bfe-data.new"
+		core_exit.sh
+	fi
+	echo "${datakey}" > "${serverfiles}/bfe-data.new/.lgsm-data-key"
+	rm -rf "${serverfiles:?}/bfe-data"
+	mv "${serverfiles}/bfe-data.new" "${serverfiles}/bfe-data"
+	fn_print_ok_nl "Preparing game data"
+	fn_script_log_pass "Preparing game data"
+}
+
+fn_update_dl() {
+	# Download and extract files to tmpdir.
+	fn_update_node
+	fn_fetch_file "${remotebuildurl}" "" "" "" "${tmpdir}" "${remotebuildfilename}" "nochmodx" "norun" "forcedl" "${remotebuildhash}"
+	rm -rf "${serverfiles:?}/bfe"
+	fn_dl_extract "${tmpdir}" "${remotebuildfilename}" "${serverfiles}/bfe"
+	fn_update_prepare
+	echo "${remotebuild}" > "${localbuildfile}"
+	fn_clear_tmp
+}
+
+fn_update_localbuild() {
+	# Gets local build info.
+	fn_print_dots "Checking local build: ${remotelocation}"
+	if [ -f "${localbuildfile}" ]; then
+		localbuild="$(cat "${localbuildfile}")"
+	fi
+	if [ -z "${localbuild}" ]; then
+		fn_print_error "Checking local build: ${remotelocation}: missing local build info"
+		fn_script_log_error "Missing local build info"
+		fn_script_log_error "Set localbuild to 0"
+		localbuild="0"
+	else
+		fn_print_ok "Checking local build: ${remotelocation}"
+		fn_script_log_pass "Checking local build"
+	fi
+}
+
+fn_update_remotebuild() {
+	# Gets remote build info.
+	apiurl="https://get.battlefronteternal.com/downloads/latest"
+	remotebuildresponse=$(curl -s "${apiurl}/SHA256SUMS")
+	remotebuildfilename=$(echo "${remotebuildresponse}" | grep -o "battlefront-eternal-server_[^ ]*_linux-${archivearch}\.tar\.xz" | head -1)
+	remotebuildhash=$(echo "${remotebuildresponse}" | grep " ${remotebuildfilename}$" | awk '{print $1}')
+	remotebuild=$(echo "${remotebuildfilename}" | cut -d '_' -f 2)
+	remotebuildurl="${apiurl}/${remotebuildfilename}"
+	if [ "${firstcommandname}" != "INSTALL" ]; then
+		fn_print_dots "Checking remote build: ${remotelocation}"
+		# Checks if remotebuild variable has been set.
+		if [ -z "${remotebuild}" ] || [ -z "${remotebuildhash}" ]; then
+			fn_print_fail "Checking remote build: ${remotelocation}"
+			fn_script_log_fail "Checking remote build"
+			core_exit.sh
+		else
+			fn_print_ok "Checking remote build: ${remotelocation}"
+			fn_script_log_pass "Checking remote build"
+		fi
+	else
+		# Checks if remotebuild variable has been set.
+		if [ -z "${remotebuild}" ] || [ -z "${remotebuildhash}" ]; then
+			fn_print_failure "Unable to get remote build"
+			fn_script_log_fail "Unable to get remote build"
+			core_exit.sh
+		fi
+	fi
+}
+
+fn_update_compare() {
+	fn_print_dots "Checking for update: ${remotelocation}"
+	# Update has been found or force update.
+	if [ "${localbuild}" != "${remotebuild}" ] || [ "${forceupdate}" == "1" ]; then
+		# Create update lockfile.
+		date '+%s' > "${lockdir:?}/update.lock"
+		fn_print_ok_nl "Checking for update: ${remotelocation}"
+		fn_print "\n"
+		fn_print_nl "${bold}${underline}Update${default} available"
+		fn_print_nl "* Local build: ${red}${localbuild}${default}"
+		fn_print_nl "* Remote build: ${green}${remotebuild}${default}"
+		if [ -f "${rootdir}/.dev-debug" ]; then
+			fn_print_nl "Remote build info"
+			fn_print_nl "* apiurl: ${apiurl}"
+			fn_print_nl "* remotebuildfilename: ${remotebuildfilename}"
+			fn_print_nl "* remotebuildurl: ${remotebuildurl}"
+			fn_print_nl "* remotebuild: ${remotebuild}"
+		fi
+		fn_print "\n"
+		fn_script_log_info "Update available"
+		fn_script_log_info "Local build: ${localbuild}"
+		fn_script_log_info "Remote build: ${remotebuild}"
+		fn_script_log_info "${localbuild} > ${remotebuild}"
+
+		if [ "${commandname}" == "UPDATE" ]; then
+			unset updateonstart
+			fn_update_apply fn_update_dl
+			alert="update"
+		elif [ "${commandname}" == "CHECK-UPDATE" ]; then
+			alert="check-update"
+		fi
+		alert.sh
+	else
+		fn_print_ok_nl "Checking for update: ${remotelocation}"
+		fn_print "\n"
+		fn_print_nl "${bold}${underline}No update${default} available"
+		fn_print_nl "* Local build: ${green}${localbuild}${default}"
+		fn_print_nl "* Remote build: ${green}${remotebuild}${default}"
+		fn_print "\n"
+		fn_script_log_info "No update available"
+		fn_script_log_info "Local build: ${localbuild}"
+		fn_script_log_info "Remote build: ${remotebuild}"
+		if [ -f "${rootdir}/.dev-debug" ]; then
+			fn_print_nl "Remote build info"
+			fn_print_nl "* apiurl: ${apiurl}"
+			fn_print_nl "* remotebuildfilename: ${remotebuildfilename}"
+			fn_print_nl "* remotebuildurl: ${remotebuildurl}"
+			fn_print_nl "* remotebuild: ${remotebuild}"
+		fi
+	fi
+}
+
+if [ "$(uname -m)" == "aarch64" ]; then
+	archivearch="arm64"
+	nodearch="arm64"
+else
+	archivearch="amd64"
+	nodearch="x64"
+fi
+
+localbuildfile="${datadir}/${selfname}-bfe-version"
+# The location where the builds are checked and downloaded.
+remotelocation="get.battlefronteternal.com"
+
+if [ "${firstcommandname}" == "INSTALL" ]; then
+	fn_update_remotebuild
+	fn_update_dl
+else
+	fn_print_dots "Checking for update"
+	fn_print_dots "Checking for update: ${remotelocation}"
+	fn_script_log_info "Checking for update: ${remotelocation}"
+	fn_update_localbuild
+	fn_update_remotebuild
+	fn_update_compare
+	if [ "${localbuild}" == "${remotebuild}" ] && [ "${forceupdate}" != "1" ]; then
+		fn_update_node_compare
+	fi
+fi