Phantasy Star Online server
Introduction
Phantasy Star Online is the first online RPG game made for consoles and was published by SEGA in 2000 for the Dreamcast. After its initial succses, the game was ported to several platforms like PC, XBOX and GameCube. Online gaming was hosted on private SEGA servers at that time and are long time unavailable, but the fan community brough them back as private servers and since some years ago there are public source code for deploying our own servers at home!
Getting started
We’re going to follow this excellent blog entry from the guys at Super CD-ROM² as an entrypoint to build our own Docker image so it is cleaner to build and maintain new versions. Later on, we’ll use docker-compose to deploy it in our server of choice (be it a VM or a SBC like a Raspberry Pi)
Tools of the trade
- Code repositories:
- phosg from Martin Michelsen aka fuzziqersoftware: a tool required to build newserv, the actual PSO server. Active development as of 2024/06/30
- newserv, again from Martin Michelsen aka fuzziqersoftware: a game server, proxy, and reverse-engineering tool for Phantasy Star Online (PSO). The README.md file contains very detailed information on how to deploy and troubleshoot it and the developers are still active as of today (2024/06/30), releasing several versions a year
- resource_dasm, again from Martin Michelsen aka fuzziqersoftware: an optional tool that enables newserv to send memory patches and load DOL files on PSO GC clients. PSO GC clients can play PSO normally on newserv without this
- Container runtime:
- Architecture:
- We’ll be deploying the server in a [Proxmox] Alpine LXC to make sure we use the least amount of resources for this and so we can respawn it easily if something goes wrong
Setting our own repository
We’ll be hosting our own Docker images in GitHub by using GitHub Actions to ensure a proper CI workflow. For this, we’ll add the code repositories as submodules so we can update them easily if any new versions are released. Our folder structure is as follows:
Our Dockerfile should look like this:
Dockerfile
# 1st stage: prepare the base image for builds
FROM debian:bookworm-slim AS base
# Install dependencies
RUN apt update && apt install --no-install-recommends -y \
# Common build dependencies
cmake make g++ gcc \
# phosg build dependencies
zlib1g-dev python3 \
# newserv build dependencies
libevent-dev \
&& rm -rf /var/lib/apt/lists/*
# Build target
FROM base as build
# Define some argument variables
ARG BUILD_TYPE=Release
# First build and install phosg
COPY phosg /tmp/build/phosg
WORKDIR /tmp/build/phosg
RUN cmake . && make && make test && make install
# After that, build and install resource_dasm
COPY resource_dasm /tmp/build/resource_dasm
WORKDIR /tmp/build/resource_dasm
RUN cmake . && make && make install
# Then build newserv
COPY newserv /tmp/build/newserv
WORKDIR /tmp/build/newserv
# - Configure CMake and build
RUN cmake . && make
# Release target
FROM debian:bookworm-slim as release
# Install runtime dependencies
RUN apt update && apt install --no-install-recommends -y \
# phosg runtime dependencies
zlib1g \
# newserv runtime dependencies
libevent-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy required libraries and objects
#COPY --from=build /usr/local/lib/libphosg.a /usr/local/lib/
#COPY --from=build /usr/local/lib/libresource_file.a /usr/local/lib/
#COPY --from=build /usr/local/include/resource_file /usr/local/include/resource_file
#COPY --from=build /usr/local/include/resource_file /usr/local/include/resource_file
# Set the workdir for newserv
WORKDIR /usr/newserv
# Copy all binaries
COPY --from=build /usr/local/bin /usr/local/bin/
COPY --from=build /tmp/build/newserv/newserv /usr/local/bin/newserv
# Copy newserv files
COPY --from=build /tmp/build/newserv/system /usr/newserv/system
# Set a default config file within the image
#RUN cp system/config.example.json system/config.json
ENTRYPOINT [ "newserv" ]
Where:
base
: is the base image with all the required dependencies for compiling the several parts of the projectbuild
: is the image that compiles and installs each componentrelease
: is the image that should only contain the bare minimum dependencies and binaries required to run the PSO server
⚠️ TODO: Improve the Docker images layers and research whether it’s best to include the
system
folder inside the container image or externally mount it
Building the server
Creating the Docker image
Run the following command from the same folder where the Dockerfile
is:
Running the server
Preparing a server configuration file
You must bind a config.json
file to the Docker container so newserv
finds it and configures the PSO server according to your needs. You can take newserv/system/config.example.json
as an example and modify whatever parameter so it matches your network and server features.
Once done, save it as config.json
.
Executing the Docker container
Run the following command depending on your situation:
network=host
mode is the most straightforward but the less secure:(WIP) Isolating the container and exposing the ports should be the way to go:
Run pso-server in isolated mode
docker run -d --rm --name pso-server -p 53:53 -p 9000:9000 -p 9001:9001 -p 9002:9002 -p 9003:9003 -p 9064:9064 -p 9100:9100 -p 9103:9103 -p 9200:9200 -p 9201:9201 -p 9202:9202 -p 9203:9203 -p 9204:9204 -p 9300:9300 -p 5100:5100 -p 5110:5110 -p 5122:5122 -v /path/to/your/config.json:/usr/newserv/system/config.json pso-server:release
where:
--name
is the container name of our server-p
is each exposed port from inside the container. Setting--network=host
discards these as all ports are exposed to the host-d
means that the container execution is detached from the terminal and runs on background--rm
means that the container will be deleted once its main process ends. Any data generated inside the container and not stored in a bind volume will be lost-v
is for binding volumes inside the container, allowing persistence of data. In this case, we’re binding an external server config sonewserv
finds it when called
⚠️ TODO: Don’t use
host
as the docker network type when running the container! Try to expose only the required ports and redirect the DNS requests towards it
⚠️ TODO: Add screenshots of the server running and some clients connecting to it!
Maintaning the server
Currently all the server data is stored inside the Docker container (quests, players’ info, etc.), so if we want to add new quests or patches we have to recreate the image. newserv
should be able to reload any new data stored in the system
folder on runtime (pending to verify), so next steps would involve externally mounting the system
folder or just bind new folders inside there.
As of now, any information stored during runtime will be lost on server shutdown.
⚠️ TODO: Learn where the quests and users are stored so we can persist their information
Updating the server
GitHub Actions
⚠️ TODO: build a CI workflow with GitHub Actions to automate the Docker image generation and publishing to the repository’s container registry!