Packaging Ports for PortMaster
Index
To release a Port on PortMaster we have some guidelines that need to be followed:
Portname requirements
The portname must start with either a lowercase letter (a-z) or a number (0-9).
You can then have a combination of lowercase letters (a-z), numbers (0-9), periods (.), or underscores (_).
There is no limit on the length of the name, but keep it short.
This name must not clash with any other existing ports.
New Port Structure:
Ports are now contained within the
port
top level directory, each port has its own sub-directory named after the port itself. Each port must adhere to the
portname
rules stated above. Each port must have a
port.json
,
screenshot.{jpg,png}
,
README.md
,
gameinfo.xml
, a port script and a port directory. It may optionally include a
cover.{jpg,png}
.
The script should have capital letters (like
Port Name.sh
) and must end in
.sh
, the port directory should be the same as the containing directory. Some legacy ports have different names, new ports won't be accepted unless they follow the new convention.
Scripts and port directories must be unique across the whole project, checks will be run to ensure this is right.
A port directory might look like the following:
- portname/
- port.json
- README.md
- screenshot.jpg
- gameinfo.xml
- cover.jpg (Optional)
- Port Name.sh
- portname/
- licenses/LICENSE Files
- <portfiles here>
port.json
This is used by portmaster, this should include all the pertinent info for the port, we have a handy port.json generator here . Make sure to select the correct architecture. If the game is using a runtime e.g. Godot/Mono/Java no arch needs to be entered.
Example from Defblade.
{
"version": 4,
"name": "defblade.zip",
"items": [
"Defblade.sh",
"defblade"
],
"items_opt": [],
"attr": {
"title": "Defblade",
"porter": [
"Cebion"
],
"desc": "A precision pixel-art platformer with sword combat. Parkour across tricky obstacles and fight enemies to earn coins - lose them all and it's game over.",
"desc_md": null,
"inst": "Ready to run out of the box, no additional files needed.",
"inst_md": null,
"genres": [
"platformer"
],
"image": null,
"rtr": true,
"exp": false,
"runtime": [],
"store": [],
"availability": "full",
"reqs": [],
"arch": [
"aarch64"
],
"min_glibc": ""
}
}
README.md
This adds additional info for the port on the wiki, we have a handy README.md generator here . Please always add a dedicated thank you note for the developer/creator. Without these people we would not be here.
Example:
## Notes
Thanks to [snej55](https://github.com/snej55) for making Defblade, a precision pixel-art platformer with tight parkour and sword-combat challenges.
## Controls
| Button | Action |
|--|--|
| D-Pad Up / A | Jump |
| D-Pad Down | Down |
| D-Pad Left | Left |
| D-Pad Right | Right |
| B | Attack |
| Start | Confirm |
## Compile
git clone https://github.com/snej55/paper-world.git
cd paper-world
cmake -S . -B build -G Ninja -DPORTMASTER_BUILD=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build
Screenshot
For each port, we include a screeshot for use in the PortMaster GUI and the online catalogue page. This must:
- Show gameplay or the main function of the port -- not just the title screen.
- Use a 4:3 aspect ratio with minimum resolution of 640×480.
-
Capture letterboxing or pillarboxing if that is how the game displays on a 4:3 handheld screen.
Save this file as
screenshot.png
For convinient use we also have a screenshot tool for making screenshots on your device. https://github.com/Cebion/Portmaster_builds/releases/download/1.0/screenshot.rar
You can use these scripts to capture either screenshots or videos on your device. Depending on your device you might need to adjust the width and height values.
Gameinfo.xml & Cover
Portmaster installs Metadata including a cover to emulationstation upon a Port install. For this we use a custom gameinfo.xml with all the data needed for Emulationstation and a cover file.
The Coverfile should always show gameplay in additon to other media like boxart or logo. If no cover is used Portmaster will use the screenshot instead.
To edit existing metadata and to create a new gameinfo.xml file you can use following tool: https://portmaster.games/metadata-editor.html
Here is the structure of a filled out gameinfo.xml
<?xml version="1.0" encoding="utf-8"?>
<gameList>
<game>
<path>./Defblade.sh</path>
<name>Defblade</name>
<desc>A precision pixel-art platformer with sword combat. Parkour across tricky obstacles and fight enemies to earn coins - lose them all and it's game over.</desc>
<releasedate>20241124T000000</releasedate>
<developer>snej55</developer>
<publisher>Open Source</publisher>
<genre>Platformer</genre>
<image>./defblade/screenshot.png</image>
</game>
</gameList>
Licensefile
Please add licensefiles for all sources, libraries and assets you used into a licenses subfolder.
For example:
- game project open source file (if it's an open source game)
- gptokeyb license file
- sdl1.2 compat license file
- gl4es license file
- box86 / box64 license files
- .so libraries files
You often can find the libraries either in the source folder you compiled or in distributions under /usr/share/doc/package/copyright
The Launchscript .sh
The script should have capital letters (like
Port Name.sh
) and must end in
.sh
, the port directory should be the same as the containing directory. Some legacy ports have different names, new ports won't be accepted unless they follow the new convention.
Below we pick apart a launchscript and explain what each function does:
# Below we assign the source of the control folder (which is the PortMaster folder) based on the distro:
#!/bin/bash
XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
if [ -d "/opt/system/Tools/PortMaster/" ]; then
controlfolder="/opt/system/Tools/PortMaster"
elif [ -d "/opt/tools/PortMaster/" ]; then
controlfolder="/opt/tools/PortMaster"
elif [ -d "$XDG_DATA_HOME/PortMaster/" ]; then
controlfolder="$XDG_DATA_HOME/PortMaster"
else
controlfolder="/roms/ports/PortMaster"
fi
source $controlfolder/control.txt # We source the control.txt file contents here
# The $ESUDO, $directory, $param_device and necessary sdl configuration controller configurations will be sourced from the control.txt file shown [here]
# If a Port is built for armhf architecture only (Need for Speed 2 for example) we set this flag so that some environment condition variables are set in the CFWs mod files.
# Example "https://github.com/PortsMaster/PortMaster-GUI/blob/main/PortMaster/mod_JELOS.txt"
export PORT_32BIT="Y" # If using a 32 bit port, else comment it out.
# We source custom mod files from the portmaster folder example mod_jelos.txt which containts pipewire fixes
[ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt"
# We pull the controller configs like the correct SDL2 Gamecontrollerdb GUID from the get_controls function from the control.txt file here
get_controls
# We switch to the port's directory location below & set the variable for the gamedir and a configuration dir easier handling below
GAMEDIR=/$directory/ports/portfolder/
CONFDIR="$GAMEDIR/conf/"
# Ensure the conf directory exists
mkdir -p "$GAMEDIR/conf"
# Switch to the game directory
cd $GAMEDIR
# Log the execution of the script, the script overwrites itself on each launch
> "$GAMEDIR/log.txt" && exec > >(tee "$GAMEDIR/log.txt") 2>&1
# Some ports like to create save files or settings files in the user's home folder or other locations. We map these config folders so we can either preconfigure games and or have the savefiles in one place. I
# You can either use XDG variables to redirect the Ports to our gamefolder if the port supports it:
# Set the XDG environment variables for config & savefiles
export XDG_DATA_HOME="$CONFDIR"
# OR
# Use bind_directories to reroute that to a location within the ports folder.
bind_directories ~/.portfolder $GAMEDIR/conf/.portfolder
# Port specific additional libraries should be included within the port's directory in a separate subfolder named libs.aarch64, libs.armhf or libs.x64
export LD_LIBRARY_PATH="$GAMEDIR/libs.${DEVICE_ARCH}:$LD_LIBRARY_PATH"
# Provide appropriate controller configuration if it recognizes SDL controller input
export SDL_GAMECONTROLLERCONFIG="$sdl_controllerconfig"
# If a port uses GL4ES (libgl.so.1) a folder named gl4es.aarch64 etc. needs to be created with the libgl.so.1 file in it. This makes sure that each cfw and device get the correct GL4ES export.
if [ -f "${controlfolder}/libgl_${CFW_NAME}.txt" ]; then
source "${controlfolder}/libgl_${CFW_NAME}.txt"
else
source "${controlfolder}/libgl_default.txt"
fi
# We launch gptokeyb using this $GPTOKEYB variable as it will take care of sourcing the executable from the central location,
# assign the appropriate exit hotkey dependent on the device (ex. select + start for most devices and minus + start for the
# rgb10) and assign the appropriate method for killing an executable dependent on the OS the port is run from.
# With -c we assign a custom mapping file else gptokeyb will only run as a tool to kill the process.
# For $ANALOG_STICKS we have the ability to supply multiple gptk files to support 1 and 2 analogue stick devices in different ways.
# For a proper documentation how gptokeyb works: [Link](https://github.com/PortsMaster/gptokeyb)
$GPTOKEYB "portexecutable.${DEVICE_ARCH}" -c "./portname.gptk.$ANALOG_STICKS" &
# Do some platform specific stuff right before the port is launched but after GPTOKEYB is run.
pm_platform_helper "$GAMEDIR/portexecutable.${DEVICE_ARCH}"
# Now we launch the port's executable with multiarch support. Make sure to rename your file according to the architecture you built for. E.g. portexecutable.aarch64
./portexecutable.${DEVICE_ARCH} Launch the executable
# Cleanup any running gptokeyb instances, and any platform specific stuff.
pm_finish
Examples:
Basic Launchscript
for open source ports with no specific engines and use of gp2keyb for controls and some needed libraries
#!/bin/bash
XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
if [ -d "/opt/system/Tools/PortMaster/" ]; then
controlfolder="/opt/system/Tools/PortMaster"
elif [ -d "/opt/tools/PortMaster/" ]; then
controlfolder="/opt/tools/PortMaster"
elif [ -d "$XDG_DATA_HOME/PortMaster/" ]; then
controlfolder="$XDG_DATA_HOME/PortMaster"
else
controlfolder="/roms/ports/PortMaster"
fi
source $controlfolder/control.txt
#export PORT_32BIT="Y" # If using a 32 bit port
[ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt"
get_controls
GAMEDIR=/$directory/ports/portfolder/
CONFDIR="$GAMEDIR/conf/"
mkdir -p "$GAMEDIR/conf"
cd $GAMEDIR
> "$GAMEDIR/log.txt" && exec > >(tee "$GAMEDIR/log.txt") 2>&1
export XDG_DATA_HOME="$CONFDIR"
export LD_LIBRARY_PATH="$GAMEDIR/libs.${DEVICE_ARCH}:$LD_LIBRARY_PATH"
export SDL_GAMECONTROLLERCONFIG="$sdl_controllerconfig"
#export TEXTINPUTINTERACTIVE="Y"
# if XDG Path does not work
# Use bind_directories to reroute that to a location within the ports folder.
bind_directories ~/.portfolder $GAMEDIR/conf/.portfolder
# If using gl4es
#if [ -f "${controlfolder}/libgl_${CFW_NAME}.txt" ]; then
# source "${controlfolder}/libgl_${CFW_NAME}.txt"
#else
# source "${controlfolder}/libgl_default.txt"
#fi
$GPTOKEYB "portexecutable.${DEVICE_ARCH}" -c "./portname.gptk.$ANALOGSTICKS" &
pm_platform_helper "$GAMEDIR/portexecutable.${DEVICE_ARCH}"
./portexecutable.${DEVICE_ARCH}
pm_finish
Godot3 Game Example Launchscript
#!/bin/bash
XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
if [ -d "/opt/system/Tools/PortMaster/" ]; then
controlfolder="/opt/system/Tools/PortMaster"
elif [ -d "/opt/tools/PortMaster/" ]; then
controlfolder="/opt/tools/PortMaster"
elif [ -d "$XDG_DATA_HOME/PortMaster/" ]; then
controlfolder="$XDG_DATA_HOME/PortMaster"
else
controlfolder="/roms/ports/PortMaster"
fi
source $controlfolder/control.txt
[ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt"
get_controls
GAMEDIR=/$directory/ports/portfolder/
CONFDIR="$GAMEDIR/conf/"
> "$GAMEDIR/log.txt" && exec > >(tee "$GAMEDIR/log.txt") 2>&1
mkdir -p "$GAMEDIR/conf"
cd $GAMEDIR
runtime="frt_3.2.3"
if [ ! -f "$controlfolder/libs/${runtime}.squashfs" ]; then
# Check for runtime if not downloaded via PM
if [ ! -f "$controlfolder/harbourmaster" ]; then
pm_message "This port requires the latest PortMaster to run, please go to https://portmaster.games/ for more info."
sleep 5
exit 1
fi
$ESUDO $controlfolder/harbourmaster --quiet --no-check runtime_check "${runtime}.squashfs"
fi
# Set the XDG environment variables for config & savefiles
export XDG_DATA_HOME="$CONFDIR"
export SDL_GAMECONTROLLERCONFIG="$sdl_controllerconfig"
# If XDG Path does not work
# Use _directories to reroute that to a location within the ports folder.
bind_directories ~/.portfolder $GAMEDIR/conf/.portfolder
# Setup Godot
godot_dir="$HOME/godot"
godot_file="$controlfolder/libs/${runtime}.squashfs"
$ESUDO mkdir -p "$godot_dir"
$ESUDO umount "$godot_file" || true
$ESUDO mount "$godot_file" "$godot_dir"
PATH="$godot_dir:$PATH"
# By default FRT sets Select as a Force Quit Hotkey, with this we disable that.
export FRT_NO_EXIT_SHORTCUTS=FRT_NO_EXIT_SHORTCUTS
$GPTOKEYB "$runtime" -c "./godot.gptk" &
pm_platform_helper "$godot_dir/$runtime"
"$runtime" $GODOT_OPTS --main-pack "gamename.pck"
if [[ "$PM_CAN_MOUNT" != "N" ]]; then
$ESUDO umount "$godot_dir"
fi
pm_finish
Love2d Example Launchscript
#!/bin/bash
XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
if [ -d "/opt/system/Tools/PortMaster/" ]; then
controlfolder="/opt/system/Tools/PortMaster"
elif [ -d "/opt/tools/PortMaster/" ]; then
controlfolder="/opt/tools/PortMaster"
elif [ -d "$XDG_DATA_HOME/PortMaster/" ]; then
controlfolder="$XDG_DATA_HOME/PortMaster"
else
controlfolder="/roms/ports/PortMaster"
fi
source $controlfolder/control.txt
[ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt"
get_controls
GAMEDIR=/$directory/ports/portfolder
CONFDIR="$GAMEDIR/conf/"
mkdir -p "$GAMEDIR/conf"
cd $GAMEDIR
> "$GAMEDIR/log.txt" && exec > >(tee "$GAMEDIR/log.txt") 2>&1
# Set the XDG environment variables for config & savefiles
export XDG_DATA_HOME="$CONFDIR"
# If XDG Path does not work
# Use bind_directories to reroute that to a location within the ports folder.
bind_directories ~/.portfolder $GAMEDIR/conf/.portfolder
export LD_LIBRARY_PATH="$GAMEDIR/libs.${DEVICE_ARCH}:$LD_LIBRARY_PATH"
export SDL_GAMECONTROLLERCONFIG="$sdl_controllerconfig"
source $controlfolder/runtimes/"love_11.5"/love.txt
# Run the love runtime
$GPTOKEYB "$LOVE_GPTK" &
pm_platform_helper "$LOVE_BINARY"
$LOVE_RUN "$GAMEDIR/lovegame"
pm_finish
Gamemaker Studio gmloader Example Launchscript
#!/bin/bash
XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
if [ -d "/opt/system/Tools/PortMaster/" ]; then
controlfolder="/opt/system/Tools/PortMaster"
elif [ -d "/opt/tools/PortMaster/" ]; then
controlfolder="/opt/tools/PortMaster"
elif [ -d "$XDG_DATA_HOME/PortMaster/" ]; then
controlfolder="$XDG_DATA_HOME/PortMaster"
else
controlfolder="/roms/ports/PortMaster"
fi
export controlfolder
source $controlfolder/control.txt
export PORT_32BIT="Y" # If using a 32 bit port
[ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt"
get_controls
GAMEDIR=/$directory/ports/portfolder/
cd $GAMEDIR
> "$GAMEDIR/log.txt" && exec > >(tee "$GAMEDIR/log.txt") 2>&1
export LD_LIBRARY_PATH="$GAMEDIR/libs.${DEVICE_ARCH}:$LD_LIBRARY_PATH"
export GMLOADER_DEPTH_DISABLE=1
export GMLOADER_SAVEDIR="$GAMEDIR/gamedata/"
export GMLOADER_PLATFORM="os_linux"
export SDL_GAMECONTROLLERCONFIG="$sdl_controllerconfig"
$ESUDO chmod +x "$GAMEDIR/gmloader"
# if no .gptk file is used use $GPTOKEYB "gmloader" &
$GPTOKEYB "gmloader" -c ./controls.gptk &
pm_platform_helper "gmloader"
./gmloader donor.apk
pm_finish
Pyxel engine Example Launchscript
#!/bin/bash
XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
if [ -d "/opt/system/Tools/PortMaster/" ]; then
controlfolder="/opt/system/Tools/PortMaster"
elif [ -d "/opt/tools/PortMaster/" ]; then
controlfolder="/opt/tools/PortMaster"
elif [ -d "$XDG_DATA_HOME/PortMaster/" ]; then
controlfolder="$XDG_DATA_HOME/PortMaster"
else
controlfolder="/roms/ports/PortMaster"
fi
source $controlfolder/control.txt
[ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt"
get_controls
GAMEDIR=/$directory/ports/portfolder/
CONFDIR="$GAMEDIR/conf"
PYXEL_PKG="gamename.pyxapp"
cd "${GAMEDIR}"
> "${GAMEDIR}/log.txt" && exec > >(tee "${GAMEDIR}/log.txt") 2>&1
mkdir -p "$GAMEDIR/conf"
bind_directories "$HOME/.config/.pyxel/gamename" "$CONFDIR"
# note: replace gamename with the appropriate value for the game
# Load Pyxel runtime
runtime="pyxel_2.2.8_python_3.11"
export pyxel_dir="$HOME/pyxel"
mkdir -p "${pyxel_dir}"
if [ ! -f "$controlfolder/libs/${runtime}.squashfs" ]; then
# Check for runtime if not downloaded via PM
if [ ! -f "$controlfolder/harbourmaster" ]; then
pm_message "This port requires the latest PortMaster to run, please go to https://portmaster.games/ for more info."
sleep 5
exit 1
fi
$ESUDO $controlfolder/harbourmaster --quiet --no-check runtime_check "${runtime}.squashfs"
fi
if [[ "$PM_CAN_MOUNT" != "N" ]]; then
$ESUDO umount "${pyxel_dir}"
fi
$ESUDO mount "$controlfolder/libs/${runtime}.squashfs" "${pyxel_dir}"
export SDL_GAMECONTROLLERCONFIG="$sdl_controllerconfig"
$GPTOKEYB "pyxel" &
pm_platform_helper "${pyxel_dir}/bin/pyxel"
# Enable Pyxel virtual env
source "${pyxel_dir}/bin/activate"
export PYTHONHOME="${pyxel_dir}"
export PYTHONPYCACHEPREFIX="${GAMEDIR}/${runtime}.cache"
# play the pyxel package stored in gamedata
"${pyxel_dir}/bin/pyxel" play "${GAMEDIR}/gamedata/${PYXEL_PKG}"
# Alternatively, run a python script file
# "${pyxel_dir}/bin/pyxel" run gamedata/main.py
if [[ "$PM_CAN_MOUNT" != "N" ]]; then
$ESUDO umount "${pyxel_dir}"
fi
pm_finish
Note: see
here
howto to build a
pyxapp
package
Launchscript functions and error handling
Some games require installation or patches on first run. We can use functions inside shell to keep code organized. Use error handling inside functions to keep the launchscript stable.
Example:
# Functions
install() {
pm_message "Performing first-run setup..."
# Purge unneeded files
rm -rf assets/*.exe assets/*.dll assets/.gitkeep
# Rename data.win
pm_message "Moving game files..."
mv "./assets/data.win" "./game.droid" || return 1
mv assets/* ./
rmdir assets
# Do localization fonts and xdelta patch if low ram
if [ $DEVICE_RAM -lt 2 ]; then
rm -rf "$GAMEDIR/localization_fonts.csv"
mv patch/localization_fonts.csv ./
find $GAMEDIR -type f -iname "*.ttf" ! -iname "Commodore Rounded v1-1.ttf" ! -iname "small_pixel.ttf" -delete
apply_patch && rm -rf "$GAMEDIR/patch" # Only remove if function is successful
fi
}
apply_patch() {
pm_message "Applying patch..."
if [ -f "$controlfolder/xdelta3" ]; then
error=$("$controlfolder/xdelta3" -d -s "$GAMEDIR/game.droid" "$GAMEDIR/patch/iosas.xdelta" "$GAMEDIR/game2.droid" 2>&1)
if [ $? -eq 0 ]; then
rm -rf "$GAMEDIR/game.droid"
mv "$GAMEDIR/game2.droid" "$GAMEDIR/game.droid"
pm_message "Patch applied successfully."
else
pm_message "Failed to apply patch. Error: $error"
rm -f "$GAMEDIR/game2.droid"
return 1
fi
else
pm_message "Error: xdelta3 not found in $controlfolder. Try updating PortMaster."
return 1
fi
}
if [ ! -f "$GAMEDIR/game.droid" ] && [ ! -f "$GAMEDIR/.installed" ]; then
install && touch "$GAMEDIR/.installed" # Only touch if function is successful
fi
Several things to note here:
-
The line for moving the game.droid file immediately returns
1if it couldn't do it. This prevents the install function from proceeding if a critical task wasn't completed. -
The
apply_patchfunction and.csvfile are only used if the target device has less than 2GB of RAM, making use of the$DEVICE_RAMvariable filled bycontrol.txt. -
The
$GAMEDIR/patchdirectory is only removed if theapply_patchfunction is successful, by using&&. This allows the user to correct any mistakes during the install process without having to reinstall the port. -
The
apply_patchfunction itself is a nest of IF conditionals to assist with error checking. It returns1if it failed. -
The
installedfunction is only run once if successful. If it was successfully completed, a.installedfile is created, preventing future runs of the function.
Testing Requirements
Before submitting a PR, your port must be thoroughly tested.
Required Testing Process
-
Create a testing thread in our #testing-n-dev Discord channel
-
Test your port on all major CFWs and Standard Resolutions:
CFW Tests: [] AmberELEC [] dArkOS [ ] MuOS ROCKNIX -> [] Libmali -> [] Panfrost -> [] Adreno (Optional) [] Knulli
Resolutions: [ ] 480x320 (Optional) [] 640x480 [] 720x720 (RGB30) (Optional) [] Higher resolutions (e.g., 1280)
- Address any feedback from the community testing
Note: Pull Requests submitted without documented testing in the #testing-n-dev channel will not be accepted. This ensures quality and compatibility across our supported devices.
Creating a Pull Request
Once your port is tested on all major CFWs and devices, you can submit it to the main PortMaster repo. There are two ways to prepare your PR: the sparse checkout script (recommended) or a full clone.
Option 1: Sparse checkout with
pmsetup.sh
(recommended)
The main repo is huge, so this method clones only what your port needs.
- Fork the main repo: https://github.com/PortsMaster/PortMaster-New . If your port is Nintendo, Capcom, etc. related, or a decomp for example, fork https://github.com/PortsMaster-MV/PortMaster-MV-New instead.
-
Get the
pmsetup.shscript and edit theREPOvariable near the top to point at your own fork , not the upstream repo:
REPO="git@github.com:YOUR_USERNAME/PortMaster-New.git"
- Run the script with your port's branch name, a local folder to work in, and the paths you need:
./pmsetup.sh yourportname yourportname_repo ports/yourportname/
If your port also needs a runtime squashfs (Godot, Weston, etc.), add it as an extra argument, same as the osmos example.
What this does:
-
Clones only
ports/yourportname/(creating it if it doesn't exist yet), plustools/,SOURCE_SETUP.txt, andREADME.md. - Checks out a new local branch with the name you gave it.
- Leaves you with a normal git working directory, just without the other 40,000+ files you don't need.
-
Add your port files inside
yourportname_repo/ports/yourportname/. Follow the structure of an existing merged port: the.shscript,port.json,gameinfo.xml, README, cover/screenshot, and the game subfolder with binaries and libs. - Commit and push to your fork:
git add .
git commit -m "Add yourportname port"
git push origin yourportname
- Open the PR on GitHub from your fork's branch against the upstream PortMaster-New repo, same as before. Your branch now contains a validated, correctly structured port folder instead of a raw clone with scripts run against it.
#!/bin/bash
# Parameters
BRANCH="$1"
LOCAL_PATH="$2"
shift 2
CHECKOUT_PATHS=("$@")
# Repo Tools
CHECKOUT_PATHS=("tools/" "SOURCE_SETUP.txt" "README.md" ".gitignore" "${CHECKOUT_PATHS[@]}")
# Git repo
REPO="git@github.com:binarycounter/PortMaster-New.git"
# Ensure at least one checkout path
if [ -z "$BRANCH" ] || [ -z "$LOCAL_PATH" ] || [ ${#CHECKOUT_PATHS[@]} -eq 0 ]; then
echo "Usage: $0 <branch-name> <local-dir> <paths...>"
exit 1
fi
# Clone with partial checkout
git clone --filter=blob:none --no-checkout "$REPO" "$LOCAL_PATH"
cd "$LOCAL_PATH"
# Enable sparse checkout WITHOUT cone mode for better file handling
git sparse-checkout init
# Set branch if it exists remotely
if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
git checkout "$BRANCH"
else
# Create new branch locally
git checkout -b "$BRANCH"
fi
# Create any missing folder structure locally
for path in "${CHECKOUT_PATHS[@]}"; do
# Remove leading slash if present to avoid double slashes
clean_path="${path#/}"
# If it's a directory, ensure it ends with a slash
if [[ "$clean_path" == */ ]]; then
mkdir -p "$clean_path"
else
mkdir -p "$(dirname "$clean_path")"
fi
done
# Write sparse-checkout patterns directly to the file
{
for path in "${CHECKOUT_PATHS[@]}"; do
# Remove leading slash if present to avoid double slashes
clean_path="${path#/}"
# Anchor the pattern to the repository root with a leading slash
echo "/$clean_path"
done
} > .git/info/sparse-checkout
# Checkout files
git checkout
echo "Sparse checkout complete for branch '$BRANCH' in directory '$LOCAL_PATH'"
Option 2: Full clone (Cebion's way)
If you work on lots of ports all the time, a full clone can be more convenient. This is the manual workflow Cebion uses on WSL2:
- Fork the PortMaster repo.
- Open WSL2 and clone your fork:
git clone git@github.com:YOUR_USERNAME/PortMaster-New.git
cd PortMaster-New
- Create a branch for your port:
git checkout -b yourportname
-
Add your port files in
ports/yourportname/. - Commit and push:
git add .
git commit
git push
Large files
If you add a file that is larger than 90 MB, you will have to run the script
tools/build_data.py
. It will split the file into 50 MB chunks suitable for committing to GitHub. If you edit the large file, just rerun the script and it will update the chunks. This also adds the file to the
.gitignore
in the port's directory, so the large file itself will not be committed to the repo.
After you open the PR
Your port will be checked automatically, and PortMaster crew members will review it again by hand.