GameMaker Studio ¶
GameMaker Studio is a 2D game engine built around a drag-and-drop editor and its own scripting language, GML. It exports to Windows, macOS, Linux, Android, iOS and the major consoles.
It does not export to ARM Linux, and that is the whole problem. A GameMaker game has no build that runs natively on the handhelds PortMaster targets, so every GameMaker port goes through a compatibility layer instead.
It is still the most-ported engine in the library by a wide margin.
How PortMaster runs it¶
GMLoader and its successor GMLoaderNext, written by JohnnyOnFlame and contributors, are GameMaker compatibility layers for ARM Linux. They share a codebase and history with the PS Vita project YoYoLoader.
Both wrap libyoyo.so, the official GameMaker runner for Android. They load
that ARM executable into memory, resolve its imports against native functions,
and patch it where needed so it runs. The effect is a minimal Android-like
environment in which an Android GameMaker build executes directly on ARM Linux.
Most GameMaker ports now use GMLoaderNext. The rest still run on the original GMLoader.
The research documentation covers the origin and the deeper technical detail.
Compatibility¶
GameMaker can compile a game two ways, and only one of them ports. Bytecode builds are portable; YoYo Compiler (YYC) builds compile GML straight to machine code, which is faster and harder to tamper with but ties the game to the platform it was built for.
Since the loader runs the Android runner, an Android build is what's needed:
| Platform | Bytecode compiler | YoYo Compiler (YYC) |
|---|---|---|
| Android | Yes | Yes |
| Windows | Yes | No |
| Linux | Yes | No |
| macOS | Yes | No |
Which loader applies depends on the GameMaker version and the device architecture:
| Loader | GameMaker versions | Architecture |
|---|---|---|
gmloader |
2022.x and earlier | ARMv7 / armhf |
gmloadernext.armhf |
all | ARMv7 / armhf |
gmloadernext.aarch64 |
2.2.1 and later | ARMv8 / aarch64 |
GMLoader is deprecated. Use it only when nothing else works.
Identifying a game¶
Two facts decide everything else: the GameMaker version, and whether the game was built as bytecode or YYC.
UndertaleModTool
answers both. Open the game's data file, which will be data.win, game.unx,
game.ios or game.droid, extracting it from an APK or the game folder first
if needed:
- The version is under Data > General Info.
- A warning appears on load if the game uses YYC.
UndertaleModTool makes an educated guess at the version, so treat it as approximate rather than authoritative.
For finding candidates in the first place, itch.io and SteamDB both list games by engine. Free games are the better starting point, since most of the commercially successful portable ones are already ported.
Wrappers¶
The loader needs a wrapper APK carrying the GameMaker runtime libraries for the
target architecture, and those libraries have to match the loader you picked:
ARMv7 libraries for armhf, ARMv8 for aarch64.
Prebuilt wrappers are available from the GMloader-ports repository.
For final packaging we prefer building a custom wrapper: download the matching GameMaker Studio version, set up the Android export, export an open-source example, launch it once, then strip the assets back out.
Port structure¶
Portname.sh
portname/
├── lib/
│ ├── armv8a/
│ ├── armv7a/
│ ├── libopenal.so.1
│ ├── libzip.so.5
│ └── libcrypto.so.1
├── assets/
│ └── .gitkeep
├── saves/
│ └── .gitkeep
├── gmloader.json
├── patches/
│ └── patchscript
├── portname.gptk
└── portname.port
lib/ holds Android AOSP libraries taken from a prebuilt image provided by
Google. It's named lib because these are native Android libraries and this is
the path structure Android uses. The libs.${DEVICE_ARCH} folder contains
libraries native to either aarch64 or armhf. GMLoaderNext needs at minimum
libcrypto, libopenal and libzip.
licenses/ contains the license agreements for each library and binary used
in GMLoader.
assets/ is where the end user puts their game data, typically everything
from a Steam or GOG install folder, or the contents of an itch.io archive. Once
patchscript has finished on first run, this folder can be removed.
saves/ holds the game's save data. The name is conventional rather than
required.
gmloader.json carries per-port configuration for GMLoaderNext. It can be
renamed to portname.json.
{
"save_dir": "saves",
"apk_path": "my_game.port",
"show_cursor": false,
"disable_controller": false,
"force_platform": "os_windows"
}
patches/patchscript is a bash script run on first boot by the PortMaster
patcher. It deliberately avoids the .sh extension so it doesn't interfere with
PortMaster's own scripts. There's a
worked example
to work from.
portname.gptk holds the gptokeyb mapping. Include an empty one even when
the game has native gamepad support, since it helps with debugging.
back = \"
start = \"
up = \"
down = \"
left = \"
right = \"
a = \"
b = \"
x = \"
y = \"
l1 = \"
l2 = \"
l3 = \"
r1 = \"
r2 = \"
r3 = \"
left_analog_up = \"
left_analog_down = \"
left_analog_left = \"
left_analog_right = \"
right_analog_up = \"
right_analog_down = \"
right_analog_left = \"
right_analog_right = \"
portname.port is an archive structured like an APK but without the
Android-specific references. It contains a lib folder with the GameMaker
runtime, and once the port is packed, an assets folder holding the game data
that was previously in portname/assets.
Why the game data is bundled this way
GameMaker's free
and professional
runtime licences both state that the runtime may be distributed
"as an integrated and inseparable part of your content". Bundling the game
data with the runtime inside portname.port is our good-faith way of
meeting that condition.
Patching and common fixes¶
Most GameMaker ports need the game's data file modified before it runs properly on a handheld. There are two ways to ship those modifications, and newer ports increasingly use the second.
Precomputed patches (xdelta)¶
Build the modified data file on a PC, ship the binary difference, and apply it
on device at first launch.
XDelta3 creates the patch from
the difference between the original and modified files, and the xdelta3 binary
in the PortMaster control folder applies it.
# Check if "data.win" exists and its MD5 checksum matches, then apply the patch
if [ -f "assets/data.win" ]; then
checksum=$(md5sum "assets/data.win" | awk '{print $1}')
if [ "$checksum" = "4b97bb2da8c515d787fe70aa03550ce5" ]; then
$ESUDO $controlfolder/xdelta3 -d -s "assets/data.win" -f "./patch/patch.xdelta3" "assets/game.droid" && \
rm "assets/data.win"
fi
fi
This is simple and fast, but the patch is tied to one exact build of the game, which is why the checksum guard is there.
On-device patching (UndertaleModCli)¶
The newer approach runs UndertaleModTool's command line interface,
UndertaleModCli.dll, on the handheld itself through .NET. The port ships the
transformation rather than the result, so it works from whatever copy of the
game the user actually owns.
It needs two runtimes, declared in the port's port.json and mounted by the
patchscript before use. gmtoolkit.squashfs provides utmt-cli and gmtools;
dotnet-8.0.12.squashfs provides dotnet itself.
TOOLKIT="$HOME/gmtoolkit"
RUNTIME="$controlfolder/libs/gmtoolkit.squashfs"
if [ -f "$RUNTIME" ]; then
$ESUDO mkdir -p "$TOOLKIT"
$ESUDO umount "$TOOLKIT" 2>/dev/null || true
$ESUDO mount "$RUNTIME" "$TOOLKIT"
else
echo "This port requires the GMToolkit runtime. Please download it."
sleep 2
patch_failure
fi
The dotnet runtime is mounted the same way, from
$controlfolder/libs/dotnet-8.0.12.squashfs onto $HOME/mono. Three
UndertaleModCli verbs cover nearly every port.
dump externalises the game's textures and writes out a converted data
file. This is the most common use by a wide margin, since it both compresses
textures and produces the game.droid the loader wants.
dotnet "$TOOLKIT/utmt-cli/UndertaleModCli.dll" \
dump "$DATADIR/data.win" \
-e "$DATADIR/textures" "$DATADIR/game.droid"
load -s runs a UndertaleModTool C# script (.csx) against the data file,
which is how the UTMT scripts below get applied on device rather than by hand.
dotnet "$TOOLKIT/utmt-cli/UndertaleModCli.dll" \
load "$DATADIR/data.win" \
-s "$GAMEDIR/tools/NewTextureRepacker.csx" -o "$DATADIR/data2.win"
replace swaps out GML code entries from .gml files shipped in the port,
which is the cleanest way to change game logic without carrying a whole patched
data file.
CODEARGS=()
for file in "$GAMEDIR/tools/gml/"*.gml; do
[ -f "$file" ] || continue
entry=$(basename "$file" .gml)
CODEARGS+=(--code "$entry=$file")
done
dotnet "$TOOLKIT/utmt-cli/UndertaleModCli.dll" \
replace "$DATADIR/data.win" -o "$DATADIR/data2.win" "${CODEARGS[@]}"
Patching this way takes noticeably longer than applying an xdelta, so use the PortMaster patching program to show progress.
Packing audio into the wrapper¶
GMLoader can have trouble loading audio that isn't packed into the wrapper APK. This packs the OGGs in, and can be adapted for games that use audiogroups.
# Check for .ogg files and move to APK
if [ -n "$(ls ./assets/*.ogg 2>/dev/null)" ]; then
zip -r -0 ./portname.port ./assets/
echo "Zipped contents to ./portname.port"
else
echo "No .ogg files found"
fi
NewTextureRepacker¶
A texture export and repack script by JohnnyOnFlame, shipped as a .csx and run
through load -s. Fixes crashes caused by oversized texture pages on Mali GPUs,
fixes broken fonts on the same, and reduces RAM usage.
Bytecode up/downgraders¶
UndertaleModTool scripts that move a game between bytecode versions, which can improve compatibility for GMS1 and GMS2 games.
GMTools¶
GameMaker games handle audio in several ways: streamed externally like
Undertale, grouped into audiogroup.dat files, or embedded in the data file.
Anything not streamed gets loaded into memory at runtime, which is a problem on
low-memory handhelds. GMTools analyses
the audiogroups and the data file, converts any .wav it finds to .ogg, and
can compress to a target bitrate. The RAM saving is significant, and audio
quality matters little through handheld speakers. It ships inside the
gmtoolkit runtime alongside utmt-cli.
Tools¶
- GameMaker Studio is the engine itself. If a game is open source we can build it directly and make the port Ready to Run, as with Spelunky Classic HD.
- UndertaleModTool
examines and modifies GameMaker data files (
data.win,game.unx,game.ios,game.droid) via scripts. Its command line interface,UndertaleModCli, is what runs on-device in newer ports. - XDelta3 GUI creates
.xdeltapatch files from the difference between two data files. A CLI variant ships with PortMaster to apply them to legally obtained game files. - GMTools, by PortMaster crew member Cyril (kotzebuedog), handles audio analysis and compression.
- GMLoader and GMLoaderNext are the compatibility binaries themselves, for armhf and aarch64 respectively.
Building GMLoaderNext from source¶
Info
Only needed for development and contribution. Prebuilt binaries ship in the example packages.
Clone the repository with its submodules:
Build for the target platform:
Building on Debian Bullseye for older platforms:
make -f Makefile.gmloader \
ARCH=aarch64-linux-gnu \
LLVM_FILE=/usr/lib/llvm-11/lib/libclang-11.so.1 \
LLVM_INC=/usr/aarch64-linux-gnu/include/c++/10/aarch64-linux-gnu \
-j$(nproc)
Generate the libc dependencies:
python3 scripts/generate_libc.py aarch64-linux-gnu \
--llvm-includes /usr/aarch64-linux-gnu/include/c++/10/aarch64-linux-gnu \
--llvm-library-file "/usr/lib/llvm-11/lib/libclang-11.so.1"
Then copy the redistributable libraries into the application directory:
The project documentation has the full detail.
Example ports¶
Real GameMaker ports in the library, useful to unpack and look at: