QEMU GameZIP Server: Difference between revisions

From Flashpoint Datahub
Jump to navigation Jump to search
m (category)
No edit summary
Line 14: Line 14:
The GameZIP implementation still uses Apache but it runs on Alpine Linux instead. This allows the use of FUSE file systems to achieve a simulated view of "merged htdocs" without the extraction of GameZIPs.
The GameZIP implementation still uses Apache but it runs on Alpine Linux instead. This allows the use of FUSE file systems to achieve a simulated view of "merged htdocs" without the extraction of GameZIPs.
This instance of Alpine Linux is emulated under QEMU. QEMU was chosen because it requires no driver installation or special privileges and Alpine Linux because of its low file size and overhead.
This instance of Alpine Linux is emulated under QEMU. QEMU was chosen because it requires no driver installation or special privileges and Alpine Linux because of its low file size and overhead.
The Linux guest cannot directly access files on the host file system, so an FTP server runs on the host to share needed files.
The Linux guest cannot directly access files on the host file system, instead, when a file lookup fails, the request is proxied to a fallback server to allow loading files for Legacy games. This is the Legacy Apache server in the case of Flashpoint Ultimate or the PHP router in the case of Flashpoint Infinity.


=== Why emulate Linux? ===
=== Why emulate Linux? ===
Line 28: Line 28:
This path is the target of a union mount, which merges the following paths:
This path is the target of a union mount, which merges the following paths:
* /root/base: Contains the PHP scripts required for mounting game ZIPs and the <code>.htaccess</code> that rewrites the HTTP Host header into a subfolder.
* /root/base: Contains the PHP scripts required for mounting game ZIPs and the <code>.htaccess</code> that rewrites the HTTP Host header into a subfolder.
* /tmp/htdocs: (Initially unmounted) Contains the contents of the existing merged htdocs, for non-GameZIP games.
* /tmp/<code>[device]</code>: (Initially unmounted) Contains the contents of a single GameZIP.
* /tmp/_<code>[game]</code>.zip: (Initially unmounted) Contains the contents of a single GameZIP.


When the emulated Alpine Linux instance starts, only <code>/root/base</code> is part of the union.
When the emulated Alpine Linux instance starts, only <code>/root/base</code> is part of the union.
Line 37: Line 36:
The scripts found in <code>/root/base</code> make up the API used for controlling the union mount.
The scripts found in <code>/root/base</code> make up the API used for controlling the union mount.


* [GET] <code>http://127.0.0.1:22500/wake.php</code>
* [GET] <code>http://127.0.0.1:22500/mount.php?file=[device_serial_code]</code>


This endpoint must receive a request at least once before games in the old htdocs folder can be played. The launcher should send a request to it on startup.
This endpoint allows mounting a game ZIP from an attached block device into the virtual htdocs.
 
This performs the following actions:
 
1. Mounts <code>Server\htdocs</code> from Flashpoint into <code>/mnt/htdocs</code> via FTP (using curlftpfs)
 
2. Mounts <code>Games</code> from Flashpoint into <code>/mnt/games</code> via FTP (using curlftpfs)
 
3. Mounts <code>/mnt/htdocs</code> into <code>/tmp/htdocs</code> as a case-insensitive mirror (using fuzzyfs)
 
4. Adds <code>/tmp/htdocs</code> to the union mount.
 
* [GET] <code>http://127.0.0.1:22500/mount.php?file=[filename]</code>
 
This endpoint allows mounting a game ZIP file into the virtual htdocs. The specified file must be located in the Games folder under the Flashpoint directory.
It has a few possible outcomes which it will print in plain text:
It has a few possible outcomes which it will print in plain text:
  “OK” (200): The file was mounted successfully.
  “OK” (200): The file was mounted successfully.
Line 67: Line 52:
This performs the following actions:
This performs the following actions:


1. If <code>wake.php</code> has not run yet. It will run first.
1. Finds the attached block device with the specified serial.
If no device was found with this serial, it checks again once every 100 ms.
If no matching device was found after 10 seconds, "NO_SUCH_FILE" is returned.


2. Mounts <code>/mnt/games/[filename]</code> to <code>/tmp/[filename]</code> as a ZIP file (using fuse-zip)
2. A symbolic link is created from <code>/dev/[device]</code> to <code>/tmp/[device].zip</code>
This is done in order to trick AVFS into thinking that the block device is a regular ZIP file.
AVFS can transparently access ZIP files as a folder by appending a pound sign after the filename.
An AVFS mount of the root directory exists in <code>/root/.avfs</code>


3. Mounts <code>/tmp/[filename]</code> to <code>/tmp/_[filename]</code> as a case-insensitive mirror (using fuzzyfs)
3. Mounts <code>/root/.avfs/tmp/[device].zip#</code> to <code>/tmp/[device]</code> as a case-insensitive mirror using fuzzyfs
fuzzyfs is a read-only filesystem that allows case-insensitive access to files.


4. Adds <code>/tmp/_[filename]</code> to the union mount.
4. Adds <code>/tmp/[device]/content</code> to the union mount.
This is done by unmounting <code>/var/www/localhost/htdocs</code> then re-mounting with a new set of settings.


=== Shell access ===
=== Shell access ===

Revision as of 15:05, 18 May 2021

The server allows accessing zipped game contents, also known as GameZIPs, as well as merged htdocs. This page aims to cover the details of the Flashpoint implementation.

Glossary

  • GameZIP: A zipped curation content folder, according to the GameZIP specification. Read More
  • merged htdocs: The root folder where web content is served from, contains matching file structure for hostnames used by games in Flashpoint.
  • FUSE: Filesystem in Userspace
  • union mount: A union mount is a mount which can appear to merge the contents of several directories, while keeping their physical content separate. [1]

Introduction

In the past, Flashpoint's server was an Apache httpd installation running on Windows. The GameZIP implementation still uses Apache but it runs on Alpine Linux instead. This allows the use of FUSE file systems to achieve a simulated view of "merged htdocs" without the extraction of GameZIPs. This instance of Alpine Linux is emulated under QEMU. QEMU was chosen because it requires no driver installation or special privileges and Alpine Linux because of its low file size and overhead. The Linux guest cannot directly access files on the host file system, instead, when a file lookup fails, the request is proxied to a fallback server to allow loading files for Legacy games. This is the Legacy Apache server in the case of Flashpoint Ultimate or the PHP router in the case of Flashpoint Infinity.

Why emulate Linux?

For our purposes, Apache is limited to a single web root and cannot serve files from ZIPs. Custom file systems require custom Windows drivers and are less mature or performant than FUSE. In addition, the use of Linux allows for the use of the entire GNU/Linux toolkit which is very flexible. It also provides a security advantage, as it sandboxes potentially untrusted PHP scripts.

How does it work?

QEMU-FP.png

The Apache installation has a single web root, found at /var/www/localhost/htdocs. This path is the target of a union mount, which merges the following paths:

  • /root/base: Contains the PHP scripts required for mounting game ZIPs and the .htaccess that rewrites the HTTP Host header into a subfolder.
  • /tmp/[device]: (Initially unmounted) Contains the contents of a single GameZIP.

When the emulated Alpine Linux instance starts, only /root/base is part of the union.

Mount API

The scripts found in /root/base make up the API used for controlling the union mount.

This endpoint allows mounting a game ZIP from an attached block device into the virtual htdocs. It has a few possible outcomes which it will print in plain text:

“OK” (200): The file was mounted successfully.
“ALREADY_MOUNTED” (200):
The file was already previously mounted and remains available.
“NO_SUCH_FILE” (400):
The specified file was not found under the Games folder.
“NO_CONTENT_FOLDER” (400):
The specified file was not mounted because it did not contain a content folder.
“BAD_ZIP” (400):
The specified file was not mounted because it was not a valid ZIP file.

This performs the following actions:

1. Finds the attached block device with the specified serial. If no device was found with this serial, it checks again once every 100 ms. If no matching device was found after 10 seconds, "NO_SUCH_FILE" is returned.

2. A symbolic link is created from /dev/[device] to /tmp/[device].zip This is done in order to trick AVFS into thinking that the block device is a regular ZIP file. AVFS can transparently access ZIP files as a folder by appending a pound sign after the filename. An AVFS mount of the root directory exists in /root/.avfs

3. Mounts /root/.avfs/tmp/[device].zip# to /tmp/[device] as a case-insensitive mirror using fuzzyfs fuzzyfs is a read-only filesystem that allows case-insensitive access to files.

4. Adds /tmp/[device]/content to the union mount. This is done by unmounting /var/www/localhost/htdocs then re-mounting with a new set of settings.

Shell access

Open Data\services.json with a text editor. Remove "-display", "none" from the arguments array in Apache Webserver and save. The next time you run Flashpoint, you'll get a terminal to Alpine Linux. The root user has a blank password.

QEMU.png

Press Ctrl+C to terminate the tail command, this is used to pipe the contents of the Apache access log to the Flashpoint Launcher.

To avoid the Linux boot time when starting the launcher, a QEMU savestate is used.

  • Press Ctrl+Alt+2 to access the QEMU console. Type loadvm quick to reset the state of the machine.
  • Press Ctrl+Alt+1 to return to the Alpine Linux shell.

Building the Alpine Linux image

Prerequisites

Flashpoint contains a subset of the QEMU binaries to cut down of space. This means qemu-img is missing.

  • Download the latest QEMU binaries [2]
  • Extract them to the Server folder in Flashpoint

Installing Alpine Linux

Download Fresh Alpine Linux image (Updated: 2020-09-21)

  • Download the latest "Virtual" Alpine Linux release for x86 processors [3]
  • Create a virtual disk image
qemu-img create -f qcow2 alpine.qcow2 2G

This will create a virtual disk image which will grow as needed up to a size of 2 GB.

  • Boot the machine
qemu-system-i386 -m 512 -net nic,model=e1000 -net user -hda alpine.qcow2 -boot d -cdrom alpine-virt-3.12.0-x86.iso

This will boot the machine with maximum 512 MB of RAM and a bridged networking card.

Go through the installation process. Once done, power off the machine using poweroff.

It is recommended to back up the qcow2 image at this point.

Setting up the Flashpoint toolchain

  • Boot the machine
qemu-system-i386 -m 512 -net nic,model=e1000 -net user -hda alpine.qcow2
  • Run the setup script (on the guest)
cd /tmp
wget https://go.nul.sh/fpultimate -O setup
chmod +x setup
./setup

For Infinity use: https://go.nul.sh/fpinfinity

  • Clean up /tmp and power off the machine (on the guest)
rm -r /tmp/*
poweroff
  • Shrink your qcow2 image (Recommended)
qemu-img convert -O qcow2 alpine.qcow2 alpine_setup.qcow2
copy /Y alpine_setup.qcow2 alpine.qcow2

Finalizing the image

Finally, we need to set up our special save state to resume execution from.

  • Boot the machine
qemu-system-i386 -m 512 -net nic,model=e1000 -net user -hda alpine.qcow2
  • Run the following commands (on the guest)
modprobe fuse
unionfs /root/base /var/www/localhost/htdocs -o allow_other
clear
tail -f /var/log/apache2/access.log >/dev/ttyS0
  • Press Ctrl+Alt+2. Type savevm quick and press Enter. Wait, then quit QEMU.

Your alpine.qcow2 image is ready!