Last active
May 12, 2022 22:13
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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