Demo
To demonstrate the capabilities of this setup, we created a small demo where the P4wnP1 loads a ROM (Pokémon Yellow) and a Game Boy emulator via MSC and starts playing it automatically.
Storage Preparation
Start by preparing the required files.
Create a payload directory in /usr/local/P4wnP1/helper.
1. mGBA
This guide uses the mGBA emulator.
- Download the AppImage here.
- Rename it to
MGBA.appimage - And place it in the payload directory.
2. Pokémon Yellow
Make sure to legally dump your own copy of the game. :)
Then add the ROM to the payload directory, renamed to POKY.gb.
3. Key Extender
There is one significant problem:
The P4wnP1 does not provide an API for holding down keys. The standard press() and type() functions only hold keys for 1ms.
This is a problem for Game Boy games, which poll inputs from a hardware register rather than using input events like a terminal would.
To work around this we use a small Python script that listens to the P4wnP1’s HID input events and repeats all desired key presses, holding them down for 100ms (configurable). With the script running, the emulator sees the following:
$ wev
[ 16: wl_keyboard] key: serial: 28554; time: 4622645; key: 53; state: 1 (pressed)
sym: x (120), utf8: 'x'
[ 16: wl_keyboard] key: serial: 28555; time: 4622646; key: 53; state: 0 (released)
sym: x (120), utf8: ''
[ 16: wl_keyboard] key: serial: 28556; time: 4622647; key: 53; state: 1 (pressed)
sym: x (120), utf8: 'x'
[ 16: wl_keyboard] key: serial: 28557; time: 4622747; key: 53; state: 0 (released)
sym: x (120), utf8: ''
This wev (Wayland event viewer) output shows the P4wnP1’s original key press (released after 1ms) followed immediately by the re-emitted press held for 100ms.
This works, but introduces another problem: unlike X11, Wayland does not share input events globally. Each process only receives input targeted at its own window. This means there is no way for the script to capture all HID input events.
Warning
Root access is required for this demo!
This is not an ideal solution, but it was the only approach that got the P4wnP1 to reliably control a Game Boy emulator. It is only necessary because the P4wnP1 is primarily designed as a text injector, not a game controller. For the purposes of this demo we will prepare the target system with a udev rule and leave it at that.
Start by adding a new udev rule on the system the demo will run on:
# add this to /etc/udev/rules.d/99-input.rules
KERNEL=="uinput", GROUP="input", MODE="0660"
KERNEL=="event*", GROUP="input", MODE="0660"
This will add read and write permission to root and the input group for the input and event device files created by udev!
Next add yourself to the input group and reload udev:
$ sudo usermod -aG input $USER
$ newgrp input
$ sudo udevadm control --reload && sudo udevadm trigger
To make sure everything is changed ideally reboot now or start a new session. Opening a new terminal and typing
$ groups
should list input.
Also test if the permissions on the device files are good. It should look like this:
$ cd /dev/input
$ ls -l
Permissions Size User Group Date Modified Name
crw-rw---- 13,64 root input 5 Jun 10:57 event0
crw-rw---- 13,65 root input 5 Jun 10:57 event1
...
So let’s setup the script.
The script is in the demo’s gitea repo in the key_extendr directory.
To build this we need some dependencies:
- You will need pyinstaller on your system, it will take care of the script’s dependencies though!
- The
uinputkernelmodule must be installed as we need to manually include its.solibrary.
Run the following to clone the project:
$ git clone https://git.magnusku.de/Magnus/p4wnp1_gb_autoplay
$ cd p4wnp1_gb_autoplay/key_extendr
The script detects the USB stick by its name which you can set under Product Name in the P4wnP1’s web UI.
Edit the script’s DEV_NAME_FRAG variable to contain a unique fragment within the name you chose!
Then bundle the script, Python VM and dependencies into a binary using pyinstaller:
$ make all
The resulting binary can be found at build/key_extendr. Add that to the payload!
4. Building the Image
Once everything is in place, the payload directory should look like this:
root@kali:/usr/local/P4wnP1/helper# ls *
genimg
payload:
key_extendr MGBA.appimage POKY.gb
If it does, create the FAT32 image as before:
$ ./genimg -i ./payload -o pokemon -l POKEMON -s 2048
Next, enable mass storage.
Payload Preparation
Now onto the payload!
The script can also be found in the gitea repo we cloned earlier.
1. Configuration
First cd into the root directory of your clone.
Installation requires you to have access to the p4wnp1 via ssh.
- Change its IP address in the project’s root Makefile if needed (
TARGETvariable). - In
./config.jsset the drive label of the above created image, the keyboard layout and the needed inputs to open a terminal on the target system
2. Build and ship
Now just ready up the payload and put it on the pi:
$ make all # build -> "./build/<NAME>.js"
$ make sync # sync with pi per ssh
This will concatenate all of the source .js files into one payload and copy it into the payload directory on the pi.
P4wnP1 settings
Now open the P4wnP1’s webUI and change the following:
- In the USB Settings enable Keyboard inputs and Mass Storage Emulation, select the Image we created.
- In the USB device configuration make sure the
Product Namecontains the fragment we set in thekey_extendr.pyconfig
And that’s all!
Execution
Now we’re ready. Execute the payload either via the WebUI or by running
$ make execute
in the projects root.
Enjoy the demo.