Skip to content

Instantly share code, notes, and snippets.

@TechnologistAU
Last active May 12, 2022 22:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TechnologistAU/7f5e62446bc3f5baba0ac372140eeeb7 to your computer and use it in GitHub Desktop.
Save TechnologistAU/7f5e62446bc3f5baba0ac372140eeeb7 to your computer and use it in GitHub Desktop.
This script automates downloading, compiling, patching and running Quake 3 on the Raspberry Pi.
#!/bin/bash
################################################################################
#
# This script automates downloading, compiling, patching and running Quake 3 on
# the Raspberry Pi.
#
# This script is written and maintained by Chris Lowe.
#
# http://www.technologist.site
#
# NOTE: This script requires a copy of "PAK0.PK3" from your orginal Quake 3
# Arena disc. Use the '--source' argument to specify the location of the
# required files. This location should contain the following file structure:
#
# (baseq3)
# pak0.pk3
# (missionpack)
# pak0.pk3 *
#
# With these minimal files available, the script will attempt to download and
# apply the required patch files. To avoid the download, you can also provide a
# copy of the Linux Q3 1.32 patch on the storage device:
#
# linuxq3apoint-1.32b-3.x86.run
# (baseq3)
# pak0.pk3
# (missionpack)
# pak0.pk3 *
#
# Alternatively, if you already have the additional PK3 files from the patch,
# you can include those on the storage device:
#
# (baseq3)
# pak0.pk3
# pak1.pk3
# pak2.pk3
# pak3.pk3
# pak4.pk3
# pak5.pk3
# pak6.pk3
# pak7.pk3
# pak8.pk3
# (missionpack)
# pak0.pk3 *
# pak1.pk3 *
# pak2.pk3 *
# pak3.pk3 *
#
# * NOTE: The missionpack files are optional, and only required if you wish to
# play Quake 3 TEAM Area.
#
# To mount a USB storage device, use the following commands:
# sudo mkdir /mnt/usb
# sudo mount /dev/sda1 /mnt/usb
#
# ./quake3.sh --source /mnt/usb
#
# To mount a Windows network share, use the following commands:
# sudo mkdir /mnt/net
# sudo mount -t cifs -o user=USER,password=PASSWORD //192.168.1.10/SHARE /mnt/net
#
# ./quake3.sh --source /mnt/net
#
#################################################################################
STARTDIR=$(pwd)
HOMEDIR=$(echo ~)
################################################################################
QUAKE3_NAME="Quake 3"
QUAKE3_ROOT=$HOMEDIR/quake3
QUAKE3_CONF=$HOMEDIR/.q3a
QUAKE3_MAKE=$QUAKE3_ROOT/build_rpi_raspbian.sh
QUAKE3_GAME=$QUAKE3_ROOT/build/release-linux-arm
QUAKE3_EXEC=$QUAKE3_GAME/ioquake3.arm
QUAKE3_SRCE=$HOMEDIR
QUAKE3_GIT_URL=https://:@github.com/raspberrypi/quake3.git
QUAKE3_PATCH_FILE=linuxq3apoint-1.32b-3.x86.run
QUAKE3_PATCH_SIZE=30915710
QUAKE3_PATCH_LIST=( \
ftp://ftp.tw.freebsd.org/pub/distfiles/$QUAKE3_PATCH_FILE \
ftp://ftp.gamers.org/pub/idgames/idstuff/quake3/linux/$QUAKE3_PATCH_FILE \
ftp://ftp.filearena.net/.pub1/gentoo/distfiles/$QUAKE3_PATCH_FILE \
ftp://ftp.gameaholic.com/pub/mirrors/ftp.idsoftware.com/quake3/linux/$QUAKE3_PATCH_FILE \
ftp://ftp.demon.nl/pub/idsoftware/idstuff/quake3/linux/$QUAKE3_PATCH_FILE \
ftp://sourceforge.mirrorservice.org/sites/distfiles.gentoo.org/distfiles/$QUAKE3_PATCH_FILE \
ftp://ftp.idsoftware.com/idstuff/quake3/linux/$QUAKE3_PATCH_FILE \
)
QUAKE3_FLAG_1080P=0
QUAKE3_FLAG_TIMEDEMO=0
QUAKE3_ARGS_GAME="+set fs_game \"baseq3\""
QUAKE3_ARGS_VIDEO_QUALITY="+set r_vertexLight \"0\" +set r_lodbias \"0\" +set r_picmip \"0\" +set r_texturebits \"32\" +set r_textureMode \"GL_LINEAR_MIPMAP_LINEAR\""
QUAKE3_ARGS_VIDEO_FOV="+set cg_fov \"90\""
QUAKE3_ARGS_VIDEO_1080p="+set r_mode \"-1\" +set r_customwidth \"1920\" +set r_customheight \"1080\""
QUAKE3_ARGS_SOUND="+set s_initsound \"1\""
QUAKE3_ARGS_TIMEDEMO="+timedemo \"1\" +set demodone \"quit\" +set demoloop \"demo four; set nextdemo vstr demodone\" +vstr demoloop"
################################################################################
#
# Prerequisite ($1)
#
# $1: Package Name
#
# Determines if a prerequisite package is already installed, and if not then
# installs the missing prerequisite.
#
################################################################################
Prerequisite() {
installed=$(dpkg -s $1 2>&1 | grep -c "Status: install ok installed")
if [ "$installed" -eq 0 ]; then
echo -n 'Installing Prerequisite "'$1'" ... '
sudo apt-get install $1 -qq -y > /dev/null 2>&1
echo 'Done'
fi
}
################################################################################
#
# GitClone ($1 $2 $3 $4)
#
# $1: Full GIT clone URL
# $2: Source Name
# $3: Source Path
# $4: Makefile or build script
#
# Determines if the source code exists, and if not then performs a GIT Clone of
# the specified code repository.
#
################################################################################
GitClone() {
if [ ! -d "$3" ] || [ ! -f "$4" ]; then
echo -n 'Cloning GIT source "'$2'" ... '
git clone $1 > /dev/null 2>&1
if [ $? -eq 0 ]; then
sed -i "s/-lvmcs_rpc_client//g" $QUAKE3_MAKE
sed -i "s/-lEGL/-lbrcmEGL/g" $QUAKE3_MAKE
sed -i "s/-lGLESv2/-lbrcmGLESv2/g" $QUAKE3_MAKE
echo 'Done'
else
echo 'Failed'
exit 1
fi
fi
}
################################################################################
#
# Compile ($1 $2 $3 $4)
#
# $1: Source Name
# $2: Source Path
# $3: Makefile or build script
# $4: Compiled executable
#
# Determines whether the compiled executable exists, and if not then performs a
# build of the source code using the specified Makefile or build script. Reports
# the time taken to perform the build.
#
################################################################################
Compile() {
if [ -d "$2" ] && [ -f "$3" ]; then
if [ ! -x "$4" ]; then
echo -n 'Compiling source "'$1'" ... '
cd $2
{ time $3; } 2>&1 | awk '/^real\t.*s$/ { printf "%s\n", $2 }'
fi
else
echo 'Source not found!'
exit 1
fi
}
################################################################################
#
# Copy ($1 $2 $3)
#
# $1: Source Directory
# $2: Destination directory
# $3: Filename
#
# Determines whether a specified destination file exists, and if not then copies
# the specified source file to the specified destination. The eXecute attribute
# is removed from the destination file after copying.
#
################################################################################
Copy() {
if [ ! -f "$2/$3" ] && [ -f "$1/$3" ]; then
echo -n 'Copying file to "'$2'/'$3'" ... '
cp "$1/$3" "$2/$3"
chmod -x "$2/$3"
echo 'Done'
fi
}
################################################################################
#
# CopyPak3Files ($1 $2 $3)
#
# $1: Source Directory
# $2: Destination Directory
# $3: Count
#
# If the destination directory exists, attempt to copy the specified number of
# PAK?.PK3 files from the source to destination directories.
#
################################################################################
CopyPak3Files() {
for (( i=0; i<=$3; i++ )); do
Copy "$1" "$2" "pak$i.pk3"
done
}
################################################################################
#
# Download ($1)
#
# $1: Source URLs
# $2: Destination Path
# $3: Source Name
#
# Determines whether the specified file exists, and if not then attempts to
# download the file from a list of known source URLs.
#
################################################################################
Download() {
if [ ! -f "$2/$3" ]; then
echo -n 'Downloading "'$3'" ... '
cd $2
urls=$1
for url in ${urls[@]}; do
wget -q -t 1 $url
if [ $? -eq 0 ] && [ -f "$2/$3" ]; then
break
fi
if [ -f "$2/$3" ]; then rm "$2/$3"; fi
done
if [ -f "$2/$3" ]; then
echo 'Done'
return 0
else
echo 'Failed'
return 1
fi
fi
}
################################################################################
#
# Patch ()
#
# Determines whether the Quake 3 patch is required, and if so then obtains a
# local copy of the patch file from either storage device (if available) or by
# download. Once obtained, the patch file is extracted to a temporary directory,
# the required files are moved to the game directories, and the temporary
# directory is removed.
#
################################################################################
Patch() {
patch=0
# Check if the additional baseq3 PAK?.pk3 files exist (1-8)
for i in {1..8}; do
if [ ! -f "$QUAKE3_GAME/baseq3/pak$i.pk3" ]; then ((patch++)); fi
done
# Check if the additional missionpack PAK?.pk3 files exist (1-3)
for i in {1..3}; do
if [ ! -f "$QUAKE3_GAME/missionpack/pak$i.pk3" ]; then ((patch++)); fi
done
# Is patching required?
if [ $patch -gt 0 ]; then
# Attempt to copy patch file from local source
Copy "$QUAKE3_SRCE" "$QUAKE3_ROOT" "$QUAKE3_PATCH_FILE"
# Attempt to download patch file from known source list
Download "$QUAKE3_PATCH_LIST" "$QUAKE3_ROOT" "$QUAKE3_PATCH_FILE"
echo -n 'Patching from "'$QUAKE3_PATCH_FILE'" ... '
# Create a temporary directory
TEMPDIR=$(mktemp -d)
# Extract files from the patch archive into the temporary directory
offset=$(head -n 355 "$QUAKE3_ROOT/$QUAKE3_PATCH_FILE" | wc -c | tr -d " ")
for s in $QUAKE3_PATCH_SIZE
do
blocks=$(expr $s / 1024)
bytes=$(expr $s % 1024)
dd if="$QUAKE3_ROOT/$QUAKE3_PATCH_FILE" ibs=$offset skip=1 obs=1024 conv=sync 2> /dev/null | \
{ test $blocks -gt 0 && dd ibs=1024 obs=1024 count=$blocks ; \
test $bytes -gt 0 && dd ibs=1 obs=1024 count=$bytes ; } 2> /dev/null | \
tar xzf - -C "$TEMPDIR" 2>&1 || \
{ echo Failed > /dev/tty; kill -15 $$; }
offset=$(expr $offset + $s)
done
# Move the updated PAK?.PK3 files into the game directories
mv -f "$TEMPDIR/baseq3/"*.pk3 "$QUAKE3_GAME/baseq3/"
mv -f "$TEMPDIR/missionpack/"*.pk3 "$QUAKE3_GAME/missionpack/"
# Remove the temporary directory
rm -rf "$TEMPDIR"
echo 'Done'
fi
}
################################################################################
#
# Launch ()
#
# Determines whether the Quake 3 executable exists and whether the script is
# being run from the local console session. If both conditions are true, then
# build a command line string from the scripts' command line arguments and then
# launch the game!
#
################################################################################
Launch() {
if [ -d "$QUAKE3_ROOT" ] && [ -x "$QUAKE3_EXEC" ]; then
# Build Command Line Arguments
QUAKE3_CMD_ARGS="$QUAKE3_ARGS_GAME $QUAKE3_ARGS_VIDEO_QUALITY $QUAKE3_ARGS_VIDEO_FOV $QUAKE3_ARGS_SOUND"
if [ $QUAKE3_FLAG_1080P -eq 1 ]; then
QUAKE3_CMD_ARGS="$QUAKE3_CMD_ARGS $QUAKE3_ARGS_VIDEO_1080p"
fi
if [ $QUAKE3_FLAG_TIMEDEMO -eq 1 ]; then
QUAKE3_CMD_ARGS="$QUAKE3_CMD_ARGS $QUAKE3_ARGS_TIMEDEMO"
fi
# Launch Quake 3
if [ $QUAKE3_FLAG_TIMEDEMO -eq 1 ]; then
$QUAKE3_EXEC $QUAKE3_CMD_ARGS 2>&1 | awk '/^[0-9]+ frames [0-9]+\.[0-9] seconds [0-9]+\.[0-9] fps / { printf "%s fps\n", $5 }'
else
$QUAKE3_EXEC $QUAKE3_CMD_ARGS
fi
else
echo 'Quake 3 could not be found!'
fi
}
################################################################################
#
# MAIN
#
################################################################################
cd $HOMEDIR
# Process Command Line Arguments
for arg in "$@"
do
if [ "$arg" = "-n" ]; then
key="$arg"
else
key=$(echo "${arg%%=*}")
fi
val=$(echo "${arg#*=}")
case $key in
--1080|--1080p)
QUAKE3_FLAG_1080P=1
;;
-c|--clear)
if [ -d "$QUAKE3_CONF" ]; then
rm -fr "$QUAKE3_CONF"
fi
;;
-f|--fov)
if [ $val -ge 0 ] && [ $val -le 160 ]; then
QUAKE3_ARGS_VIDEO_FOV="+set cg_fov \"$val\""
else
echo 'The value provided for Field-of-View is out of range (0-160, default is 90).'
exit 1
fi
;;
-g|--game)
if [ -d "$QUAKE3_GAME/$val" ]; then
QUAKE3_ARGS_GAME="+set fs_game \"$val\""
else
echo 'The specified game directory does not exist: '$val
exit 1
fi
;;
-m|--mp|--missionpack|--q3ta)
if [ -d "$QUAKE3_GAME/missionpack" ]; then
QUAKE3_ARGS_GAME="+set fs_game \"missionpack\""
else
echo 'The specified game directory does not exist: missionpack'
fi
;;
-n|--ns|--nosound|--soundoff)
QUAKE3_ARGS_SOUND="+set s_initsound \"0\""
;;
-t|--td|--timedemo)
QUAKE3_FLAG_TIMEDEMO=1
;;
-s|--source)
if [ -f "$val/baseq3/pak0.pk3" ]; then
QUAKE3_SRCE=$val
else
echo "Invalid source path"
exit 1
fi
;;
--help)
echo 'Quake 3 script for Raspberry Pi'
echo ' --1080p Forces the game engine to render at 1920 x 1080.'
echo ' -c, --clear Clear the game configuration.'
echo ' -f, --fov=<FOV> Specifies value for Field-of-View (default 90).'
echo ' -g, --game=<GAME> Specifies the game directory (default "baseq3").'
echo ' -m, --missionpack, --q3ta Launches the Team Arena mission pack add-on.'
echo ' -n, --nosound Disables the game sound engine.'
echo ' -s, --source=<PATH> Specifies the path to the required source files.'
echo ' -t, --timedemo Executes a timedemo benchmark of the FOUR demo'
echo ' and returns a result in Frames Per Second (FPS).'
echo ' --help Display this help and exit.'
echo
echo 'Git: <http://gist.github.com/03e0d0072cf4032d3494ad6f700eafb2>'
echo 'Web: <http://www.lowefamily.com.au>'
exit 1
;;
*)
echo 'Invalid argument: '$arg
exit 1
;;
esac
done
# Install Prerequisites
Prerequisite "git"
Prerequisite "libsdl1.2-dev"
# Perform a GIT Clone of the Quake 3 source
GitClone "$QUAKE3_GIT_URL" "$QUAKE3_NAME" "$QUAKE3_ROOT" "$QUAKE3_MAKE"
# Compile the Quake 3 source
Compile "$QUAKE3_NAME" "$QUAKE3_ROOT" "$QUAKE3_MAKE" "$QUAKE3_EXEC"
# Copy the PAK?.PK3 files to the game directories
CopyPak3Files "$QUAKE3_SRCE/baseq3" "$QUAKE3_GAME/baseq3" 8
CopyPak3Files "$QUAKE3_SRCE/missionpack" "$QUAKE3_GAME/missionpack" 3
# Patch the game
Patch
# Launch the game
Launch
cd $STARTDIR
################################################################################
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment