PlayGuard

Troubleshooting

This page describes v1.3.0, the latest release. The development version

Getting back into a locked console, the common problems, reporting a bug, and the developer mode.

On this page

Locked out?

PlayGuard only sees the parental controls of the system it runs on: emuMMC and sysMMC each have their own (a PIN removed on one is still there on the other). Run it on each one that needs fixing.

Second-hand console

You know the PIN, but the previous owner's phone app is still linked (unlinking fails, or a factory reset asks for their account).

Security › Unlink the companion app, then, if you want no parental controls at all, Delete all parental controls. Both work offline, on emuMMC as on sysMMC.

PIN forgotten

Show the PIN asks for the PIN itself, so it cannot help here. With the recovery sysmodule installed (below), drop a RESCUE file and restart: the recovery screen then sets a new one (it never shows the old one). Without it, Delete all parental controls starts again (a settings backup is saved first; it never contains the PIN): with Protect PlayGuard with the PIN on (the default, Before a change), put the SD card in a computer and set "pin_lock" to "off" in sd:/config/playguard/config.json first.

The play timer blocks everything (0-minute limit) and the PIN is forgotten

PlayGuard itself cannot start then. Install the optional recovery sysmodule (playguard-rescue.zip) beforehand; when locked out, drop an empty config/playguard/RESCUE file on the SD card and boot — it unlocks the console so PlayGuard can open. See sysmodule/README.md.

The module must follow PlayGuard's version: with each PlayGuard update, extract that release's playguard-rescue.zip again (or Update on the notification bell's The recovery sysmodule is out of date), then restart the console. An older module leaves a report PlayGuard cannot check: the recovery screen then offers nothing, says the module is older, and doing RESCUE again gives the same until it is updated.

Homebrew menu locked

Console › Forwarders › Lock the homebrew menu keeps the album and a game started with R from opening the homebrew menu. If PlayGuard's forwarder and the protected menu are both gone (deleted in System Settings, or on the other system: the lock is on the SD card, read by sysMMC and emuMMC both):

  • From a computer: delete atmosphere/config/override_config.ini on the SD card (or put back config/playguard/override_config.ini.backup, the file as it was), then restart the console.
  • With the recovery sysmodule installed (optional): drop an empty config/playguard/RESCUE file on the SD card and restart; the sysmodule lifts the lock at boot, and PlayGuard says so at its next start. An update of Atmosphère since it was locked lifts it the same way.

Console not modded

PlayGuard cannot help: it needs Atmosphère. Nintendo support's master-key procedure is the official way.

Common problems

The time left does not go down

Today says The console is not counting play time right now when it has not moved for about 90 s. The likely causes:

  • The network clock was never set. Console › Network clock › Measure the time difference, then set it (network clock).
  • The clock was set back. After a jump back, the count stops until the clock passes the old time; nothing to do.
  • A limit was just written. Start a game for a minute, then check again.
  • Parental controls are temporarily unlocked. Play time may not be counted then: Today › Turn back on (or a tap on the header's pill).

My limits changed by themselves

  • The phone app is still linked. Its next sync overwrites what PlayGuard set: Security › Unlink the companion app.
  • Another tool, or a profile. Today shows an amber card for limits that differ from the ones PlayGuard last saw, and History › Changes made with PlayGuard lists each change seen outside PlayGuard. Ⓐ on one puts the previous value back.
  • A scheduled profile: put in force when its period began, or the limits it replaced put back once it ended (Play timer › Profiles › Scheduled profile).

The child played past the limit

  • The console only warns. Play timer › When the time is up: pick Suspend the software.
  • Parental controls were left unlocked. While they are, today's limit is on hold: the header's pill says so on every tab, and a tap on it leads to Today › Turn back on. A restart of the console locks them too.
  • The clock was set back. The count stops until the clock passes the old time (what counts).
  • The other system. emuMMC and sysMMC each have their own parental controls: set them on both, or hide the hekate boot menu (compatibility).

The whole limit is available again today

Changing the clock (the network clock or the console clock) starts today's play time over. Set the clock before the child plays, not after.

PlayGuard opens read-only

The console runs a firmware newer than the newest one tested: PlayGuard checks whether a newer release supports it and offers to update (newer firmware). A crossed-out pencil right of the title says the app is read-only; a tap on it opens the firmware screen.

PlayGuard asks for the PIN all the time, or never

Security › Protect PlayGuard with the PIN: Before a change (the default) asks once, before the first change, and the PIN holds until PlayGuard closes. Ask for the PIN again after 5 minutes asks again after them. Show the PIN always asks.

A game does not start, or shows a "!" on the HOME menu

Console › Installed games says what is wrong with each one and what to do; save data always stays (installed games).

A protected forwarder shows error 2347-0100

Its gate (config/playguard/gate-….nro) is no longer the file the forwarder was made with: it was changed or replaced on the SD card, so the forwarder does not start it. Open PlayGuard from its own forwarder: it writes the gate back at start; Console › Forwarders › Install them all again makes every forwarder again with it.

A protected forwarder closes at once ("an error occurred")

Before any PlayGuard screen, with no error code (Atmosphère's report in atmosphere/crash_reports/ names the forwarder module, result 2000-0000): it was made by a PlayGuard whose gate was built without its assets, which the forwarder's loader refused. Update PlayGuard, then make it again: the bell and Console › Forwarders offer it for every forwarder made with an older loader.

A protected forwarder stays on its gate's screen after the PIN

The PIN accepted, the screen with the homebrew's icon stays and nothing more happens: its gate drew that screen with a single buffer, and waited for ever to draw the next one. Update PlayGuard, then make it again: the bell and Console › Forwarders offer it for every forwarder made with an older gate.

A forwarder shows "The software could not be started"

The console refused to start it: a signature check is still in place. In the order to look:

  • sigpatch files beside sys-patch. .ips files in atmosphere/kip_patches/fs_patches, loader_patches or atmosphere/exefs_patches/es_patches while sys-patch runs: two tools patching the same checks, the cause the sys-patch and sigpatch threads on GBAtemp give most. Console › Game patches warns of it, and Ⓐ there sets them aside (moved to config/playguard/sigpatches-aside/, never deleted; hekate's bootloader/patches.ini stays). Restart the console.
  • A pattern sys-patch did not apply. Forwarders need its fs and loader patches, every pattern it has for this firmware (noncasigchk_17.0.0+, nocntchk_19.0.0+, noacidsigchk_10.0.0+), not only one per module. About › Help & diagnostics › Export a diagnostic report lists, on its game patches line, sys-patch's version and each pattern left out; sd:/config/sys-patch/log.ini holds the same. Check that config/sys-patch/config.ini patches this system (patch_emummc=1 on emuMMC) and turns none of them off; sys-patch's own maintainer says 23.0.x needs no newer sys-patch than 1.6.2.3.
  • No restart since the patches changed. sys-patch patches at boot.
  • A forwarder made by an older PlayGuard. The bell and Console › Forwarders offer to make again those built with an older loader.

The bell or a tab shows an amber badge

Something needs you: Ⓨ opens the list, and Ⓐ on an entry does the fix or opens where it is made (notifications).

Reporting a bug

An error shows the system's code (such as 0x0001088E) with what is known about it: what to do for the codes whose meaning is documented, otherwise a report helps.

  1. About › Help & diagnostics › Export a diagnostic report saves a text file in sd:/config/playguard/logs/reports/: firmware, Atmosphère version, how long the start-up took and how long the slow screens took (the change history, All games, the installed games check, the tabs that refresh themselves), clocks, storage, serial-blanking and game-patch status, what Atmosphère's DNS redirection does with Nintendo's parental-control and time servers (read from its hosts file on the SD card, not a network test, with the end of Atmosphère's own atmosphere/logs/dns_mitm_startup.log), the raw result of every parental-control query, and the logs: how the last run ended, the memory and SD-card space left, the newest crash files and the last 80 lines logged. It never contains the PIN or the serial number.
  2. Open an issue and attach it.

Sending a report online

With the console online, About › Help & diagnostics › Send a report… (Ⓧ on the diagnostic report in developer mode) sends it to a secret gist in your GitHub account, after saying exactly what goes. A report saved earlier in logs/reports/ can be sent the same way.

  • What goes: the report and each file as a file of its own:
  • the latest crash reports: PlayGuard's last 3 in logs/crashes/, and Atmosphère's last 2 of a homebrew from the past week, in sd:/atmosphere/crash_reports/ (another homebrew's crash can be one of them: the list shown before sending says which);
  • the logs of this run and the one before (logs/sessions/; a long one: its start and its last 64 KiB);
  • the debug files: the play-timer block reference, the end of the play-timer recording while Record the play timer is on, the last 20 changes of the change history (without names of games, accounts or profiles, and with their date only), and PlayGuard's settings.
  • Signing in: signed out, PlayGuard offers to sign in first; GitHub account, just below once signed in, signs out; when GitHub refuses the gist, PlayGuard offers to sign in again.
  • Afterwards: it shows two QR codes, with the link under the first one, its id in groups of four to read out or type: the report, for whoever already helps you, and the bug-report form with the link, your versions, how PlayGuard was started (over a game, from its HOME menu icon or from the Album; add the menu) and the system (sysMMC or emuMMC) already filled in, to scan to report a bug. The link also stays in the notifications, and each one is kept in logs/uploads/ as an Internet shortcut (.url) that a computer opens in its browser.

Anyone with the link can read the report, until you delete the gist on GitHub, and a bug report opened with it is public: the confirmation says so.

What PlayGuard keeps on the SD card

Everything is in sd:/config/playguard/logs/, plain text a computer opens:

  • sessions/session_<date>.txt — one file per run: everything PlayGuard logged (what used to reach a PC over nxlink only), each line with its time and level (E error, W warning, I information), and a last line saying how the run ended: === end: clean exit ===, or === end: crash, see logs/crashes/… ===. A file stops growing at 1 MiB.
  • crashes/crash_<date>.txt — one file per crash, its first line saying what happened:
  • a CPU exception (a bad pointer, an undefined instruction, a stack overflow): the kind of fault and the address it touched, the backtrace and the registers. Addresses are offsets into playguard.nro (playguard+0x1a2b3c): the linker map of the build (the -linker-map artifact of its CI run) gives the function. PlayGuard then ends as before: the console's error screen, and Atmosphère's own report in sd:/atmosphere/crash_reports/;
  • an uncaught exception: its type and message, and the backtrace;
  • a run that did not end: found at the next start, when the last run's session file has no end line (a crash PlayGuard could not catch, closed from the HOME menu while it hung, or the console turned off), with that run's last 60 lines.

The crash files caught while PlayGuard runs end with the last 80 lines logged before the crash. - reports/, uploads/, play_timer/ — the saved reports, the links of the reports sent online, the play-timer recording.

About › Help & diagnostics sets how many reports, crash files, session logs and links are kept (each folder on its own; the newest always stays).

Developer mode (investigating a new firmware)

Press Ⓨ seven times on About › Version; the developer tools appear at the end of About › Help & diagnostics. It adds:

  • a read-only switch — with it on, the app cannot change anything: the safe way to investigate a new firmware (the firmware screen offers it directly);
  • the diagnostic report on screen (Ⓨ saves it, Ⓧ sends it online), and a shortcut to export it from the Play timer tab;
  • Console self-test: what PlayGuard checks on this console by itself before the console checklist is gone through by hand — the PIN, the signature patches, the data folder, each forwarder PlayGuard made (on the HOME menu, protected, seen working through its gate), the gate on the SD card, what Atmosphère reads of the homebrew menu's lock, the ways back, the recovery sysmodule. It changes nothing, and writes its result as Markdown to config/playguard/self-test.md, to paste in the pull request;
  • Install another build in place, to test a fix before it is released (below);
  • Compare the play-timer block (only while the phone app is linked and can sync — not with a hosts file blocking Nintendo; also in Security), to decode settings PlayGuard does not show yet: save the raw block as a reference, change one setting in the phone app, come back — PlayGuard lists the values that changed (saved in logs/reports/ on request). Different bedtimes per day, set from the phone app, could be confirmed this way. Attach that file to an issue, or send it with the report (Send a report…).
  • Record the play timer: every 30 s while PlayGuard is open, one line of what the play timer reports (time left, time spent, the raw settings block…) in logs/play_timer/log.csv, a spreadsheet-ready file, plus a line right after each change PlayGuard makes, saying which (its last column, event), and a note when the clock moved more than the time that went by. Left open over midnight, or until the time is up, it shows what one report cannot: when the time spent resets, what the console says near the end. The switch is remembered; it only records in developer mode. What is known so far is in parental-controls.md.
  • Clear the cache and Delete the log files, each after a confirmation saying how many files and how much space: the cache (cache/ and each system's nand/<system>/cache/: game names and icons, the Activity's last figures, the build list) is read again from the console when needed; the log files are the crash files, the session logs (but this run's, still being written), the saved reports and the play-timer recording, while uploads/ (the removal links of the reports sent online) and the block reference stay. Settings, history, backups, profiles and exports are never touched.
  • Run the migrations again: the moves of the earlier layouts — the data folder next to the .nro, the logs before their subfolders, what every system shared — each on a line to check or uncheck (all checked at first; A toggles one), then Run the N chosen and a confirmation listing them with what each moves. They run now, whatever migrations.json records (each one otherwise runs once, at the first start of a build that needs it; the level it records is never lowered); a file already in place is never replaced. A dialog says how many entries moved; each step is in the session log.
  • Frame statistics: one line per second in logs/frame_stats.txt, the time the screen took to draw and the frames that came late, to weigh one screen against another on the console. Background glow turns off the soft light behind the screens (the line's mode then ends in -noglow), to measure what it costs.

Installing another build

Install another build puts a build in place of PlayGuard's own .nro, to test a fix before it is released.

What it lists, each kind in a tab of its own (Ⓛ / Ⓡ, shown on either side of the tabs, go from one to the next; the screen opens on the installed build's, and each tab has, after its name, a sparkle and the number of builds newer than the installed one):

  • one of the last 20 releases (pre-releases included, tagged);
  • one of the last 20 commits of main;
  • the newest build of an open pull request from a branch of this repository (never from a fork: its code has not been reviewed yet). A on the pull requests' tab, when it is shown, sorts them the other way (newest build first, or by number): A's hint says which.

Each build shows its title (a commit's or a pull request's subject; a commit's whole message is its description) over tags: the kind of change (Feature, Fix, Docs…) and its scope, Breaking, the pull request a commit came from, how its checks ended (CI OK, CI failed, CI running), pre-release, a check when it is the installed build or a sparkle when it is newer; how old it is (in minutes, hours, then days) and its commit at the right. A build opens its page first: tags (its kind, its checks, the check or the sparkle), a card of facts (built, size, commit, checksum, when GitHub deletes it), Install, See on GitHub (its page as a QR code for a phone), a warning when its checks are still running, then a release's notes or a pull request's description, drawn from GitHub's Markdown (headings, lists, task boxes, code, quotes and alerts, tables).

Safe to read: everything that comes from GitHub is shown as plain text: no link is opened, no HTML or script is run, and invisible characters that could disguise a title (right-to-left overrides, zero-width characters, control characters, invalid UTF-8) are removed.

Signing in. The releases need nothing; the others are the build workflow's artifacts, which GitHub hands to signed-in users only. GitHub account signs in with a code and a QR code to scan with a phone. The only permissions asked are to read the build workflow's files (Actions) and to create gists, for Send a report…; otherwise the token can only read what is public. It is kept in github_token, never sent with a report, and GitHub account signs out. Signed in, Ⓨ (Search) takes a pull request's number (open or closed) or a commit (7 to 40 hex digits) and finds its build, from a branch of this repository only.

Installing: PlayGuard downloads the build, checks it (size, the SHA-256 GitHub records, the NRO header), puts it in place of its own .nro and restarts on it (the PIN is asked first whenever one is set). Its window says which step it is on, the size and speed of the download, and which build it replaces; Cancel goes once the build is being put in place.

  • The list is kept for 10 minutes and shown at once, with how old it is (Ⓧ fetches it again, and so does the screen every 3 minutes while a build's checks are running); while it is fetched, a bar at the header's right shows how far along it is. What installing does is said at the screen's foot.
  • A download cut short (network lost) goes on from where it stopped the next time the same build is installed.
  • Artifacts expire after 90 days.

Going back. The build replaced is kept (playguard.nro.previous): Go back to the previous build in the same list swaps them again, after the PIN; it names that build by its version, its pull request and its commit. If a build installed from here (or by Update in place) crashes before its first screen, the next start of PlayGuard offers to go back to the previous build on a plain text screen, before anything else is loaded (A goes back, B starts it anyway). Going back asks for the PIN on the console's own PIN screen when one is set (not PlayGuard's lock screen, which may be what crashes); without it, the build installed starts. The same list goes back to any release at any time.

Which build runs: About › Version shows the commit in developer mode. Every screen's header shows the running version next to the title: a release's in a grey tag (1.2.0), any other build's in an amber tag followed by its commit, so a test build is never taken for the release.

View on GitHub