A large chunk of PortMaster ports are games using the GameMaker Engine. This engine (referred to as GMS) is an excellent beginner engine for getting into porting, but can also quickly become pretty advanced. This primer hopes to accurately summarize a few key points for how GMS ports are created and work.
PortMaster Engineers heavily rely on a few major tools that make GMS ports successful.
-
GameMaker Studio is the engine in which these games are created. If a GMS game is open source, we can use GMS to build it ourselves and make it "ready to run". Example: Pocket Crystal League.
-
UndertaleModTool is a fantastic tool for both examining GameMaker files (
data.win
,game.droid
, andgame.unx
) and modifying them via scripts. -
XDelta3 GUI is a gui version of xdelta3 that allows creating
.xdelta
patch files from differences in two files, used for GameMaker game modifications. A cli variant exists in PortMaster for applying these patches to legally obtained game files. -
GMTools, by PortMaster Crew Member Cyril aka kotzebuedog, is a python script that handles audio analysis and compression.
-
GMLoader and GMLoader-Next are the compatibility binaries that translate android vm bytecode to linux vm. These two (used for armhf and aarch64 respectively) read an android GameMaker runner library
libyoyo.so
and translate it for linux execution.
In order for a GMS port to work, we have to match the runtime version with an android wrapper. To figure out which runtime a game was built with, we use UTMT. We load the data file, check under the General Info tree, and it's right there as Version.
GameMaker games can be compiled one of two ways. There's VM mode, which means the game runs in a virtual environment and the game code is inside the datafile. The other method is YYC (YoYo Compiler), which turns the game code into machine c++ code and throws it all into the executable file.
Remember that GMLoader is a translator for android. If a game is YYC (UndertaleModTool will tell you), it might still work if it was compiled for the android platform. If a game is YYC but it was compiled for Linux, there's a chance we can get it to run with box86 or box64. But if a game is YYC and it was compiled for Windows or MacOSX, there's nothing we can do since the game code is inside the native executable for that platform.
Sometimes UTMT will be used to modify the data file itself. The following are common adjustments for small-arm handhelds:
- Changing display resolution to enforce fullscreen (some games maintain a specific aspect ratio)
- Changing control schemes (usually to disable conflicting keyboard/mouse inputs or gamepad inputs)
- Changing cpu-intensive tasks (such as drawing calculations in loops, such as in Isles of Sea and Sky)
- Flushing textures via draw_texture_flush(); which believe it or not isn't always present
We also use UTMT to export and repack textures. GameMaker atlases textures at 2048x2048 pages by default. This leads to longer load times and increased memory usage for Mali. While compressing textures might seem like a straightforward solution to reduce memory usage, a more effective approach is to use UTMT's texture repacker script that organizes textures into smaller atlases.
Imagine you're unrolling a map of the United States, but you're only going to look at a particular part of it. If you're only going to look at one state, you don't need most of the map. You can cut the map into smaller pieces and only take out the section you need. This means there's more space on the table where you put the map, and you can unroll map sections for, say, Virginia and California without having to unroll a section of every state in between.
An atlas functions like a grouping mechanism, consolidating similar textures into a single large image. This allows GameMaker to load multiple textures into memory simultaneously, minimizing wasted space. By reducing gaps (blank areas) in memory, less memory is wasted. Fewer gaps mean that the available memory is used more efficiently. Additionally, using atlases reduces the number of draw calls required to render textures on the screen. Each texture switch can incur a performance cost, so by grouping textures, the game can render them more efficiently. This is a crucial benefit to small-arm handhelds with limited cpu and ram.
If a GMS data file requires modifications, we need to then supply a method for the end-user to make use of the modifications without having to open up UTMT themselves. PortMaster strives to make ports as accessible and seamless as possible. XDelta3 both creates a patch file containing the differences between our two GMS data files, and applies patch files on-device during the first-time launch process for a port.
Not all GameMaker games are the same when it comes to audio. Developers have a variety of audio options, from streaming external audio (like Undertale), to grouping audio files into audiogroup.dat
files, to embedding audio files into the GMS data file. When audio files aren't streamed, they're loaded into memory at runtime. For low-mem handhelds, this can cause problems. GMTools analyzes all audiogroups and the data file and converts any .wav
files it finds to .ogg
, and can also compress audio to a specified bitrate. For small-arm handhelds, audio quality isn't a big concern--the handhelds use small speakers, after all.
When audio compression and texture repacking aren't enough (a game runs, but is still slow), it's time to dive deeper to find out why. GameMaker uses rooms as a space to load content--and will often contain preplaced static content such as tiles to avoid draw calls later. A game can have several different rooms, like one for the title screen, and then another for the game itself, etc. These rooms are loaded into memory wholesale, so if a room has massive dimensions (think 10,000 x 20,000), it might also have a massive amount of object instances. This means the entire game will chug while using that room. A good example of this is UFO 50's Ninpek game. If we open UFO 50 in UndertaleModTool and load rm34_Ninpek
, we can instantly see why it's choppy and slow on small-arm handhelds: the room dimensions are 15,360 x 216. Ninpek is a sidescrolling game with a constantly moving camera, so it's natural for the developer to create a room like this to hold the content and flow. However, because the room contains so many instances, it slows to a crawl on low-power devices. There's not much we can do about a room like this, though, at least not without heavily tampering with the game--which would essentially deviate the project from a simple compatibility patch and turn it into a complete mod.
Isles of Sea and Sky is another fine example of performance troubleshooting. In IOSAS, entering specific areas will result in a huge framerate drop--which instantly recovers as soon as the area is left. This again indicates a problem with rooms. IOSAS has a specific set of rooms called "god rooms" that load a specific script that has to do with "god gem" special effects. This special effect in question is a calculation loop for drawing lines to synchronize with gems rotating around a sprite. Calculations are already cpu-intensive tasks, so putting them into a loop that executes every frame is asking for low performance. This particular case was resolved by modifying the loop to retrieve variables from a file pm-config.ini
, an ini file specially made for the port. Inside, the user can configure two variables: Idol_SFX
and FrameSkip
. If Idol_SFX=1
, meaning the loop is allowed to execute, then FrameSkip=x
will dictate when the loop will execute. Instead of executing every frame (FrameSkip=0
), we can modify the value so the loop only executes every 20 or 30 frames. This results in a change: our low framerate is now a stutter, where the game "pauses" once every x frames when it performs the calculation step. This means the special effect also isn't perfectly aligned--but it preserves the artistic effect somewhat while compensating for low cpu power.
By using the above tools, PortMaster Engineers enable modern GMS games to run on small-arm handhelds. We do our utmost to retain the original game's features and feel, focusing solely on optimizations. We don't expect indie developers to consider such optimizations in the modern market, but we're fortunate to have the ability to perform them ourselves in order to bring GMS games to a wider audience.