Skip to the content
PCBKitNew board
2 layers35.6 × 34.3 mmOpenscex, the printed circuit board in 3D

Stealth SCEx region-unlock modchip firmware for PlayStation and PSone on an ATtiny85: internal oscillator trimmed against the console's SUBQ frame rate, one region per build, MISRA C:2012 clean, simulator- and mutation-tested

Size
35.6 × 34.3 mm
Copper layers
2
Thickness
1.6 mm
Parts
13
Nets
12
Pads
35
Tracks, vias
71, 38
Pours
2
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
4 Oct 2026
Updated
3 Nov 2026

Overview

Openscex is an open-source printed circuit board by gufranco, from the gufranco/openscex-modchip repository on GitHub, published under the MIT licence. It is a 2-layer board of 35.6 × 34.3 mm with 13 parts on 12 nets, routed with 71 tracks and 38 vias.

It is built around the ATTINY85-20PU. Its parts include 3 connectors, 2 capacitors, 1 resistor and 1 IC. It belongs with the retro computing and games boards and the AVR (ATmega, ATtiny) boards of this library.

Main components

1

README

README.md

openscex-modchip

Stealth SCEx region unlock for the PlayStation and PSone, on an ATtiny85 that trims its own clock against the console.

English | 日本語 | 中文

ci platform mutation release coverage license


Quick start | Wiring | Status indicator | Comparison | Report your console

6084 bytes of flash · 8 board families, PU-7 to PM-41(2) · 3 regions · 0 MISRA deviations · 100% host line and branch coverage · 669/669 mutants killed

bash
gh release download --repo gufranco/openscex-modchip --pattern 'openscex-modchip-attiny85.hex' --pattern SHA256SUMS
sha256sum -c SHA256SUMS --ignore-missing
avrdude -c <programmer> -p attiny85 -U flash:w:openscex-modchip-attiny85.hex:i

[!IMPORTANT] The current firmware passes the full simulation gate and has not yet run on a console. Measure the tap voltages before wiring. Source: compatibility reports, tests/sim.

Region-unlock firmware for the original PlayStation and the PSone, on an ATtiny85. It sends the configured region string only inside the SUBQ region-check window and keeps DATA high-impedance the rest of the time. It does not patch the boot ROM, so Japanese fat consoles and the PAL PSone also need a patched BIOS. It is not an optical-drive emulator, does not support PS2 or Saturn, and does not defeat LibCrypt. Source: src/run.c, src/inject.c, AGENTS.md.

Source
Silent after acceptance
No string starts after the first program-area frame; one already under way finishes, within 181 ms. Only a new lead-in read, such as an anti-mod TOC re-read, is served again.
Disc swaps without a lid wire
1.5 s without SUBQ frames counts as a swap and re-arms the chip for the next disc.
src/inject.c, src/loop.c, src/run.c
Self-trimmed timing
The chip times the console's 75 Hz SUBQ frames and trims its internal oscillator to within 1%. No clock wire.
One region, one window
Only the configured region string, only inside the SUBQ region-check window, at most 16 strings per arming.
src/trim.c, src/region.c, include/pscu/config.h
Status LED
One pin reports boot stages, each disc's result and wiring faults. A final build only blinks briefly and reports each fault once.
Proven in code
MISRA C:2012 clean, 100% host coverage, a simavr console model, mutation testing, byte-identical rebuilds.
src/indicator.c, tests/host, tools/mutate.py

Overview

PropertyValueSource
Console targetsPlayStation fat PU-7 through PU-23, PSone PM-41 and PM-41(2)assets/psnee, PsNee PSNee.ino L372-L406
MCUATtiny85 (8-pin DIP)include/port/registers.h, ATtiny25/45/85 datasheet 2586Q
MethodSCEx injectionsrc/inject.c, psx-spx cdromdrive.md, SCEx
Regionone per build, REGION=jp|us|eu (default us)Makefile, src/region.c
Clockthe internal 8 MHz RC oscillator, trimmed against the console's SUBQ frame ratesrc/trim.c, ATtiny25/45/85 datasheet 2586Q
Wires4 signals (SQCK, SUBQ, DATA, WFCK) plus power; optional LEDinclude/port/registers.h, PsNee MCU.h L453-L530
ToolchainC17, MISRA C:2012 zero deviations, pinned Docker imageDockerfile, AGENTS.md

Supported build per console

ConsoleBoardREGIONSecond region checkSource
Fat US/Canada, SCPH-1001PU-8usnoneconsolemods PS1 region information, PsNee PSNee.ino L10-L17
Fat US/Canada, SCPH-550x1/700x1/900x1PU-18 to PU-23usnoneconsolemods PS1 region information, PsNee PSNee.ino L10-L17
Fat PAL, SCPH-1002PU-8eunoneconsolemods PS1 region information, PsNee PSNee.ino L10-L17
Fat PAL, SCPH-550x2/900x2PU-18 to PU-22eunoneconsolemods PS1 region information, PsNee PSNee.ino L10-L17
PSone US/Canada, SCPH-101PM-41 / PM-41(2)usnoneconsolemods PS1 region information, PsNee PSNee.ino L10-L17
PSone PAL, SCPH-102PM-41 / PM-41(2)euin the boot ROM; needs a patched BIOSconsolemods PS1 region information, PsNee PSNee.ino L33-L38
PSone Japan, SCPH-100PM-41jpin the boot ROM; needs a patched BIOSconsolemods PS1 region information, PsNee PSNee.ino L33-L38
Fat Japan, SCPH-1000/3000/3500/5000/5500/7000/7500/9000PU-7 to PU-23jpin the boot ROM; needs a patched BIOS, though early SCPH-1000 units boot the disc anywayconsolemods PS1 region information, PsNee PSNee.ino L33-L38
Asia, SCPH-xxx3unknownjpnoneconsolemods PS1 region information, PsNee PSNee.ino L10-L17
Asia Video CD, SCPH-5903unknownjp + VCD_FILTER=onnoneconsolemods PS1 region information, PsNee PSNee.ino L456-L490

The PU-7 and PU-8 use the static-gate method, as in PsNee V9.0. Dev boards (DTL-H120x, PU-9) read burned discs natively and need no chip. Source: PsNee PSNee.ino L33-L38, PsNee PSNee.ino L10-L17, PsNee PSNee.ino L456-L490, src/subq.c.

Quick start

Each release carries prebuilt images: us, eu, jp, jp-vcd for the SCPH-5903, a quiet -final variant of each (see Status indicator), a SHA256SUMS file, the license, toolchain.spdx.json, an SPDX bill of materials of the toolchain that built them, and a build-provenance attestation for every one of those files, the bill of materials included. Check a download before flashing it:

bash
sha256sum -c SHA256SUMS --ignore-missing
gh attestation verify openscex-modchip-attiny85.hex --repo gufranco/openscex-modchip

Releases are versioned automatically from the commit history. To build from source, run every build, check and test through make in the pinned Docker toolchain; only avrdude runs on the host. Source: .releaserc.json, .github/workflows/release.yml.

ToolPurposeSource
Dockerruns the pinned toolchainDocker, Dockerfile
Gitclones the repositoryGit
avrdudeflashes the image to the chipavrdude
Python 3.13 or laterruns make's Docker wrapper on the hostPython, tools/docker_make.py, pyproject.toml
bash
git clone https://github.com/gufranco/openscex-modchip.git
cd openscex-modchip
make REGION=us                                        # America
make REGION=jp VCD_FILTER=on                          # SCPH-5903
make REGION=us PROFILE=final                          # America, quiet indicator
make REGION=us PROFILE=trace                          # America, debug trace on pin 2
avrdude -c <programmer> -p attiny85 -U flash:w:build/attiny85/release/openscex-modchip-attiny85.hex:i
avrdude -c <programmer> -p attiny85 -U lfuse:w:0xE2:m -U hfuse:w:0xDD:m -U efuse:w:0xFF:m

Any ISP works, including an Arduino as ISP. Write the flash first and the fuses last. The low fuse 0xE2 selects the internal 8 MHz oscillator, the slow-rising-power start-up delay and no clock divider (CKSEL 0010, SUT 10; datasheet Tables 6-6 and 6-7). The firmware also clears the clock prescaler at boot, so CKDIV8 cannot slow it. Reflashing erases the calibration record and oscillator trim; the chip learns them again. Source: Arduino as ISP, ATtiny25/45/85 datasheet 2586Q, src/run.c.

A new ATtiny85 ships with CKDIV8 programmed and runs at 1 MHz, so the ISP clock must stay below 250 kHz until the fuses are written; the datasheet requires each half of the ISP clock to last more than two CPU cycles. An Arduino as ISP already does. With another programmer, add avrdude's -B, for example -B 8, for the first write. Source: ATtiny25/45/85 datasheet 2586Q, avrdude manual, -B.

BuildFuses (low / high / extended)Source
Internal 8 MHz with brown-out detection at 2.7 V, recommended0xE2 / 0xDD / 0xFFATtiny25/45/85 datasheet 2586Q
Internal 8 MHz without brown-out detection0xE2 / 0xDF / 0xFFATtiny25/45/85 datasheet 2586Q

Brown-out detection holds the chip in reset below 2.7 V, the ATtiny85's minimum supply for its speed grade; keep it on. The firmware also measures its supply before every string and holds back below about 2.75 V, shown as code 7. That limit still passes a 3.3 V supply 5 percent low on a chip whose bandgap reads high. Source: ATtiny25/45/85 datasheet 2586Q, src/supply.c.

Verify with the Makefile targets:

bash
make size        # image size; fails when less than 256 bytes of flash stay free
make test        # host tests, simavr console model and its stack check
make analyse     # formatting, lint, types, licences, MISRA and the Python tests
make repro       # every release image, built twice from clean, byte-identical
make mutate      # mutation testing on the logic layer
make mutate_platform  # mutation of run.c and engine.c, killed by the simulator
make bench_ci    # console bench: this firmware against PsNee in the same simulated console

CI runs the same gates on every push to main and every pull request, as defined in .github/workflows/ci.yml. make mutate_platform runs in its own workflow, .github/workflows/mutation.yml, whenever the firmware, the simulator suite, the mutation tool or the pinned toolchain changes. make hooks enables the commit-message and formatting checks locally.

MCU pinout

The four SCEx signals and the indicator pin keep PsNee's ATtiny85 assignment from its MCU.h, so PsNee wiring guides match pin for pin. PB5 stays RESET, so the chip remains ISP-programmable. Pin numbers are for the 8-pin PDIP; check the datasheet for SOIC. Source: PsNee MCU.h L453-L530, include/port/registers.h, ATtiny25/45/85 datasheet 2586Q.

DIP pinPortSignalDirectionConnect toSource
1PB5RESET-leave as resetinclude/port/registers.h, ATtiny25/45/85 datasheet 2586Q
2PB3INDICATORout, optionalstatus LED through a 1 kΩ resistor, or leave offinclude/port/registers.h, PsNee PSNee.ino L64
3PB4WFCKin and outstatic gate, or PU-22+ live carrierinclude/port/registers.h, PsNee MCU.h L453-L530
4GNDGND-console groundATtiny25/45/85 datasheet 2586Q
5PB0SQCKinSUBQ serial clockinclude/port/registers.h, PsNee MCU.h L453-L530
6PB1SUBQinSUBQ serial datainclude/port/registers.h, PsNee MCU.h L453-L530
7PB2DATAout, drive-low or high-ZSCEx injection into the mechaconinclude/port/registers.h, src/port.S
8VCCVCC-console supply, measure firstATtiny25/45/85 datasheet 2586Q

Console tap points

The photos below label SQCK, SUBQ, DATA, WFCK, VCC and GND on each board; solder each wire where they are marked. The PU-18 photo shows the underside. Leave AX, DX and RESET unconnected: they belong to PsNee's boot-ROM patch. Source: assets/psnee.

PU-7 board with the SQCK, SUBQ, DATA, WFCK, VCC and GND points labeled, from PsNee
PU-7. Photo from PsNee
PU-8 board, later revision, with the SQCK, SUBQ, DATA, WFCK, VCC and GND points labeled, from PsNee
PU-8, 1-658-467-22. Photo from PsNee
PU-8 board, earlier revision, with the SQCK, SUBQ, DATA, WFCK, VCC and GND points labeled, from PsNee
PU-8, 1-658-467-12. Photo from PsNee
PU-18 board with the SQCK, SUBQ, DATA, WFCK, VCC and GND points labeled, from PsNee
PU-18. Photo from PsNee
PU-20 board with the SQCK, SUBQ, DATA, WFCK, VCC and GND points labeled, from PsNee
PU-20. Photo from PsNee
PU-22 board with the SQCK, SUBQ, DATA, WFCK, VCC and GND points labeled, from PsNee
PU-22. Photo from PsNee
PU-23 board with the SQCK, SUBQ, DATA, WFCK, VCC and GND points labeled, from PsNee
PU-23. Photo from PsNee
PM-41 board with the SQCK, SUBQ, DATA, WFCK, VCC and GND points labeled, from PsNee
PM-41. Photo from PsNee
PM-41(2) board with the SQCK, SUBQ, DATA, WFCK, VCC and GND points labeled, from PsNee
PM-41(2). Photo from PsNee

These nine photos come from PsNee V9.0 by kalymos and its contributors, released into the public domain under the Unlicense, and are copied here resized.

Board familySCPH eraDATA injection pointWFCK roleSource
PU-7, PU-8, PU-18, PU-201000-750xdigital NRZ output of the wobble ASIC into the mechaconstatic gateassets/psnee, PsNee PSNee.ino L372-L406
PU-22, PU-237500-900xCD-processor tracking line, WFCK as a fake carrier, three-wire-plus-linklive clock, sync requiredassets/psnee, PsNee PSNee.ino L372-L406
PM-41, PM-41(2)PSone 100-103same tracking-line carrier method; float the chip I/O when idle on PM-41(2)live clockassets/psnee, quade.co PsNee guide

With no clock or lid wire, the chip can sit anywhere the four signal wires reach. Keep those wires short: long wires pick up noise, a known cause of Mayumi V4 failures (quade.co). Remove the clock and lid wires of an install made for an earlier release before flashing this firmware, because pin 2 is now PB3, the indicator output, and would drive against a console clock left there. Source: src/trim.c, src/loop.c, quade.co Mayumi V4 guide, include/port/registers.h.

Board

One optional board holds the chip in a DIP-8 socket, so it comes out for reflashing. The console wires land on six 4 by 4 mm top-side pads with no holes, 6 mm apart along the bottom edge: WFCK, SQCK, SUBQ, DATA, VCC and GND. Every part is through-hole. The socket, a 100 nF decoupling capacitor, a 220 µF bulk capacitor and a 10 kΩ RESET pull-up are always fitted, the pull-up because the chip's internal one is weak against console noise. Three headers are optional and marked "opt" on the board, so a build fits only what it uses. The board is a KiCad 10 project with its schematic, layout and design rules, 35.6 × 34.3 mm. Source: hardware/openscex.kicad_sch, hardware/openscex.kicad_pcb.

PartFitPurposeSource
U1, DIP-8 socketalwaysholds the ATtiny85hardware/openscex.kicad_sch
C1, 100 nF ceramicalwaysanswers the chip's fast current edges at its supply pinshardware/openscex.kicad_sch
C2, 220 µF electrolyticalwaysholds the supply up through pulsed loads on J2, such as an active buzzer, whose pulses would otherwise dip it through the thin supply wire; 11.2 mm tall, it may be laid flat on its leadshardware/openscex.kicad_sch, Panasonic FC series
R1, 10 kΩalwaysholds RESET highhardware/openscex.kicad_sch
J1, keyed 2×3 ISP headeroptionalreflashing the chiphardware/openscex.kicad_sch
J2, keyed JST XH 3-pin headeroptionalpin 1 GND, pin 2 VCC, pin 3 PB3 (SIG on the board), for a cable to the indicator of your choice: an LED with its resistor, an active buzzer, or a board of your ownhardware/openscex.kicad_sch, JST XH datasheet
J3, 6-pin headeroptionalthe trace image's serial line, for an FTDI TTL-232R cablehardware/openscex.kicad_sch
The board, 3D view
The board, 3D view
The board, top
The board, top
The board, bottom, the ground plane
The board, bottom, the ground plane

The board is symmetric about its vertical centre line: the chip, the serial header and the row of wire pads sit on it, and the ISP header on the left is balanced by the indicator header, R1 and C1 on the right. It has two layers laid out against noise. Every track is as wide as its route allows, 1.0 mm on this board; tracks run on top where they can, so the bottom stays a ground plane under the console lines; both layers are ground-poured and stitched with 0.6 mm vias; every bend is a tangent arc of at most 45 degrees; different nets keep 1 mm apart and 0.8 mm from pads. Source: hardware/openscex.kicad_pcb, hardware/openscex.kicad_dru.

The ISP header follows the AVR 6-pin layout: 1 MISO on SUBQ, 2 VCC, 3 SCK on DATA, 4 MOSI on SQCK, 5 RESET, 6 GND. Its keyed shroud keeps 1.5 mm clear of every other part. Reflash with the board out of the console or its wires unsoldered; in place, the programmer would feed the console's supply and fight the console's drivers on SQCK and SUBQ. Source: hardware/openscex.kicad_sch, ATtiny25/45/85 datasheet 2586Q.

J2 is a keyed JST XH header, so its plug goes in one way only. Its pins follow the RC servo order, GND, VCC, signal: VCC in the middle never meets a ground wire, so even a plug forced in reversed powers nothing backwards. VCC powers whatever the cable leads to, such as a buzzer module or a board of your own; a plain LED needs only GND and SIG. VCC is the console's 3.5 V rail, so a cable must never join it to GND. Source: hardware/openscex.kicad_sch, JST XH datasheet.

The serial header J3 takes the 6-way socket of an FTDI TTL-232R cable: pin 1 is GND, pin 5 the cable's RXD input, and pins 2, 3, 4 and 6 stay open, so the cable's 5 V output never reaches the console. It stands at the top edge with 2 mm clear around it, so the socket meets no other part. J2 and J3 both carry PB3, and the image flashed decides what PB3 is, so plug only the cable that matches it: the indicator cable for debug or final, the serial cable for trace. Source: hardware/openscex.kicad_sch, hardware/openscex.kicad_pcb, TTL-232R datasheet.

The board has a bill of materials, whose Fit column names the optional parts, and a Gerber and drill archive ready for a board house, both exported with KiCad 10.0.6. Teardrops are drawn into the board, so the archive needs no refill in KiCad. Source: hardware/openscex-bom.csv, hardware/fab/openscex-gerbers.zip.

Safety

  • Measure the voltage at every tap point before wiring. Every board, PU-7 to PM-41(2), runs most of its chips from +3.5 V, and that is the chip's supply; the board's +5 V rail feeds a few parts such as the AV multi out, the audio amplifier and the regulator for the sound and CD supply, and PsNee warns against wiring the chip to it. Source: psx-spx pinouts.md, voltage summary, PsNee changelog.
  • Opening a console and soldering to the CD subsystem can destroy it. Build at your own risk. Source: quade.co PS1 modchip guide.

Status indicator

PB3 drives an optional indicator, usually an LED, the chip's only diagnostic channel. It shows the current stage, whether each disc passed its region check, and which wire to check when something fails. It never delays or gates a feature and keeps no history, so every code except the two boot codes describes the present. Source: src/indicator.c.

PartChoiceSource
LEDa 3 mm or 5 mm red, orange, yellow or green LED, forward voltage about 2 V; not blue or white, whose 3 V forward voltage leaves almost nothing across the resistor on the console's 3.5 V supplyKingbright WP7113ID datasheet, ATtiny25/45/85 datasheet 2586Q
Resistor1 kΩ, any wattage: about 1.5 mA at the console's 3.5 V, well under the pin's 40 mA absolute maximumATtiny25/45/85 datasheet 2586Q
LED wiringpin 2 (PB3), or pin 3 of the board's header J2, to the resistor, the resistor to the LED anode (long leg), the cathode (flat side) to ground, or pin 1 of J2; on the board the LED and its resistor ride on the cable, so the LED can sit anywhere in the caseinclude/port/registers.h, hardware/openscex.kicad_sch

Two builds drive the pin. debug, the default, shows everything below; final, built with make PROFILE=final and released as the -final images, stays quiet. Flash debug to install and troubleshoot, then final to play. Only the pin differs: the simulator checks edge by edge that both send the same strings at the same times. Source: include/pscu/indicator.h, tests/sim/sim_profile.c.

StagedebugfinalSource
Board detectionlit for about 0.4 s after power-ondarksrc/engine.c
Board foundone 300 ms blink for a static-gate board (PU-18, PU-20), two for a WFCK-carrier board (PU-22 and later)one 60 ms blink for a static-gate board, two for a WFCK-carrier boardsrc/indicator.c
Waiting for a disca 40 ms blip every 2 sdarksrc/indicator.c
Injectinga 90 to 181 ms flash per region stringdarksrc/engine.c
Resulta code shown three times, then darkan accepted disc gives one 60 ms blink, a refused one code 2 twice; then darksrc/indicator.c
Playdark, with a 40 ms tick each time OSCCAL moves or the calibration is writtendarksrc/indicator.c, src/run.c

Codes are long 700 ms flashes, 300 ms apart, with a 2 s pause before the code repeats. In final, codes 3 and 4 play once per disc, and code 7 repeats every 30 s while it holds. Source: src/indicator.c.

CodeMeaningCheckSource
1the console accepted the region stringnothing, the disc playssrc/indicator.c, src/loop.c
2strings were sent and the console never reached the program areaDATA and WFCK wiring, and that the build's region matches the discsrc/indicator.c, src/loop.c
3no SUBQ frame for 5 s after power-on; shown for 15 s, then the heartbeatSQCK, SUBQ, power and ground; also shown with no disc insrc/indicator.c, src/loop.c
4frames arrive but no region check for 20 s; repeats while it holdsSUBQ; also normal with an audio CDsrc/indicator.c, src/loop.c
5the watchdog reset the chip, shown once at the next bootthe supply and ground: a dip or noise stopped the main loopsrc/indicator.c, src/port_chip.S
6the board differs from the one the calibration stored, shown once at boot; code 5 takes prioritythe WFCK wire, which is intermittent, unless the chip moved to another consolesrc/indicator.c, src/calib.c
7the supply measured under about 2.75 V, or the reading failed; no string is sent while it holds, and it outranks codes 3 and 4VCC and ground at the tap points, which must measure 3.3 V or moresrc/indicator.c, src/supply.c

Serial trace

make PROFILE=trace builds an image that turns pin 2 (PB3) into a serial line instead of the indicator, at 115200 baud, 8N1. It carries everything needed to find a fault without an oscilloscope: a boot report, every SUBQ frame the chip captured, and a line for every decision it took, each with its time since power-on. The lines go out in the gap after each frame, at most one decision per gap, so DATA sends the same strings with the same bit timing as the other two images, each string starting at most one frame later, which the simulator checks edge by edge. Source: src/trace.c, src/run.c, tests/sim/sim_trace.c, tests/sim/sim_profile.c.

To connect it on the board, plug a TTL-232R cable onto the serial header J3, black wire on GND, with no indicator cable on J2. On a bare chip, wire pin 2 to the RX input of a USB serial adapter and its ground to the console's ground. The line swings between 0 and the console's 3.5 V logic supply, which a 3.3 V adapter such as the TTL-232R-3V3 reads. Start the capture before powering the console, so the boot report is in it. Source: hardware/openscex.kicad_sch, TTL-232R datasheet, psx-spx pinouts.md, voltage summary.

sh
# Linux; on macOS the port is /dev/cu.usbserial-*, and stty takes -f
stty -F /dev/ttyUSB0 115200 raw && cat /dev/ttyUSB0 | tee capture.txt
python3 tools/serial_trace.py capture.txt

Each frame line is the 12 SUBQ bytes in hex; a failed capture reads as all F. Every other line starts with @ and the time, such as @1.460 OPEN 0A 0A, in seconds and milliseconds since power-on. Source: src/trace.c.

LineMeaningSource
BOOT US 1A2Bthe region built in and the first four hex digits of the commit; a trailing V means the Video CD filtersrc/trace.c, Makefile
RST PWR, RST WDTstarted from power-on, or restarted by the watchdog after the main loop stalledsrc/trace.c, src/port_chip.S
BRD W Dthe board: W WFCK carrier or G static gate; then D detected, K kept from the record after a watchdog reset, or C detected and not the board recordedsrc/trace.c, src/calib.c
OSC 50>4EOSCCAL from its factory value to the value applied, and later each trim step against the 75 Hz frame ratesrc/trace.c, src/trim.c
REC W 0C P -2the calibration record: board, start in hex, F frozen or P still probing, and the trim; at boot as read from EEPROM, later each time it is learnedsrc/trace.c, src/calib.c
SUP OK 3303mV, SUP LOW 2601mVthe supply at boot and each time it crosses the 2.75 V guard; below it no string is sent and no record writtensrc/trace.c, src/supply.c
DISC, GONEvalid SUBQ frames again, or none for 1.5 ssrc/trace.c, src/inject.c
OPEN 0A 0Athe window opened: the SUBQ counter and the start it reached, in hexsrc/trace.c, src/loop.c
HOLD 03 0A 3303mVthe window held without sending: under the supply guard, or just below the start after it openedsrc/trace.c, src/loop.c
SHUT P 00the window shut: P a program-area frame, G the disc left, D the counter fell backsrc/trace.c, src/loop.c
SCEX 3 Wstring 3 of at most 16 in this opening, and the board mode it was sent insrc/trace.c, src/engine.c
PASS, FAILthe console went on to the program area after the strings, or did not in timesrc/trace.c, src/loop.c
MISS 0B 0Cthe lead-in ended before the window opened: how far the counter got and the start it needed; the start moves earlier for the next discsrc/trace.c, src/calib.c
SAVE OK, SAVE NO 2601mVthe record written to EEPROM, or refused for a low supply and kept to try againsrc/trace.c, src/engine.c
LOST 2events dropped because more came in a row than the four-event queue holdssrc/trace.c, include/pscu/trace.h

tools/serial_trace.py reads a saved capture, or the terminal piped into it, and prints the timeline in words, with each frame's place on the disc and its CRC checked and runs of the same frame folded into one line. It ends with a diagnosis that names the likely fault and what to check: no frame or every capture failed points to the SQCK and SUBQ wires, many bad CRCs to noise on them, watchdog restarts to a supply dip or noise that stopped the main loop, repeated power-on starts or a low supply to the power tap, a lead-in that never opens the window to the learned start, strings without PASS to the DATA and WFCK wires or a region mismatch, and PASS with a game that still stops to an anti-mod check or a boot-ROM region check. --summary prints the diagnosis alone. Source: tools/serial_trace.py, tests/serial_trace/test_serial_trace.py.

How it works

mermaid
graph LR
    subgraph Console
        CD[CD subsystem]
        WF[WFCK]
        MECH[Mechacon]
    end
    subgraph ATtiny85
        CAP[SUBQ capture]
        DET[Region-check detector]
        ST[Stealth state machine]
        INJ[SCEx injector]
        IND[Status indicator]
        CAL[Per-console calibration]
    end
    CD -->|SQCK, SUBQ| CAP
    CAP --> DET --> ST --> INJ
    ST --> IND
    ST <-->|start| CAL
    WF -->|gate or carrier| INJ
    INJ -->|DATA| MECH

What's included

FeatureDetailSource
SCEx injection44-bit LSB-first region string; the static-gate method on PU-7 to PU-20, with WFCK held low for each string as in PsNee and Mayumi V4, and the WFCK-carrier method on PU-22 and latersrc/inject.c, src/port.S, PsNee PSNee.ino L53
Board auto-detectWFCK at boot selects gate or carrier mode, so one build fits every family. A carrier needs 25 unbroken WFCK edges, each within 360 µs of the last, as MultiMode 3 and Mayumi V4 require. On a gate board WFCK is watched again for 13.1 ms before each string, so a late-starting carrier is never held low. After a watchdog reset the board recorded at boot is kept, so a stalled carrier is not driven as a gatesrc/board_mode.c, src/engine.c, src/calib.c, PsNee PSNee.ino L372-L406
Supply guardbefore every string the chip measures its supply against its 1.1 V bandgap; below about 2.75 V, or on an impossible reading, it sends nothing and shows code 7. A brief dip pauses a burst without restarting its count, and a window kept shut teaches the calibration nothingsrc/supply.c, src/port_chip.S, ATtiny25/45/85 datasheet 2586Q
Oscillator trimtimes the console's 75 Hz SUBQ frames in the lead-in and in play, and steps OSCCAL up to 4 notches per batch, one per write, until within 1%, never more than 16 notches from the factory value; the trim is stored in EEPROM and applied at bootsrc/trim.c, src/run.c, ATtiny25/45/85 datasheet 2586Q
Disc swapsno lid wire: 1.5 s without a valid SUBQ frame, which only a stopped drive gives, re-arms the chip; seeks and rereads keep producing frames. The SUBQ counter stops 8 frames past the start, so the window closes within about half a second after a lid opens, even on a refused disc. Every disc may get the full 16-string capsrc/inject.c, src/loop.c, include/pscu/loop.h
Stealthinjects only inside the SUBQ region-check window, capped per arming, 5 frames (67 ms) between strings as in PsNee and Mayumi V4; otherwise DATA is high-Z and the indicator off. Once the console reads the program area no string starts; one already under way finishes. A later lead-in read, such as the anti-mod v2 TOC re-read, is served again under the same cap. A count that dips just under the start keeps the cap and gap; only a drop 8 frames below it re-armssrc/inject.c, src/loop.c, include/pscu/config.h, PsNee PSNee.ino L53, psx-spx cdromformat.md, anti-modchip
Single configured regionemits only REGION, never all threesrc/region.c
Adaptive timingon WFCK-carrier boards each injected bit lasts 30 WFCK periods, following the console's clock at single and double speed; on static-gate boards the trimmed oscillator times itsrc/port.S, src/trim.c
Self-recoveryeach SUBQ capture realigns on the gap between frames and gives up after 30 ms without an edge, or once its bits outlast 16 ms, longer than a frame, so a floating SQCK shows code 3 instead of restarting the chip; a carrier bit cell that outlasts 8 ms ends the string and releases DATA, so a WFCK that slows or stops mid-injection never holds it, and the watchdog restarts a main loop that stops for 0.5 s. No pin needs a pull-up: SQCK, SUBQ and WFCK are console lines, DATA is the console's line while released, and PB3 is an outputsrc/engine.c, src/port.S, include/port/registers.h
Frame integritya SUBQ frame counts only when its CRC-16 matches, so a corrupted or misaligned frame cannot arm a string, mark a disc present or pass for the program areasrc/subq.c, ECMA-130, Q-channel CRC
Status indicatoran optional LED shows boot stages, each disc's result and live faults; the firmware never waits on it and works with nothing fittedsrc/indicator.c
In-field diagnosticsthe codes name the failing stage, from a SUBQ line that never clocks to one that never shows a region check; no programmer neededsrc/indicator.c, src/loop.c
Per-console calibrationlearns this console's start point and its own oscillator rate in a six-byte EEPROM record; a missing or damaged record falls back to the defaultssrc/calib.c
Closed-loop confirmationafter injecting, the chip waits for a program-area frame (a real track number), which the mechacon allows only after accepting the string, and reports the resultsrc/inject.c, src/loop.c
Verification100% host line and branch coverage, a simavr console model with fast and slow oscillators, mutation testing, reproducible builds, and a console bench that plays one simulated console into this firmware, PsNee, Mayumi V4 and MM3. This one must accept wherever they do, keep its bit cell in their range and send no more outside the region check. The bench executes every reachable instruction; each unreachable one carries its reason in the sourcetests/host, tests/sim, tools/mutate.py, tools/bench, CONTRIBUTING.md

Per-console calibration

The chip learns how this console reads the region string and keeps the result in a six-byte EEPROM record, so later discs spend less time with DATA driven. Learned values only fall back toward the defaults, so a lost, damaged or foreign record costs stealth, never a disc. Source: src/calib.c.

ValueLearned fromEffectSource
Start pointeach accepted disc moves the start 2 lead-in frames (27 ms) later, up to 20 frames; a jp build keeps the default, as PsNee warns against a later trigger on Japanese consolesstrings start closer to the region check; a refusal, or a lead-in read that ends before the start, steps back 2 frames and ends the probesrc/calib.c, PsNee PSNee.ino L48
Boardthe board detected at boota different board shows code 6 once and restarts the start pointsrc/calib.c, src/indicator.c
Oscillatorsingle-speed frame spacing, 75 Hz from the console's crystal, in the lead-in and in play, only while the supply check passes, since the oscillator's rate follows its supplyeach 64-frame batch moves OSCCAL one notch per percent of error beyond a 1% dead band, rounded up, at most 4, one notch per write; the trim belongs to the chip, so a board change keeps itsrc/trim.c, src/run.c

The chip writes only changed bytes, only at boot, after a disc's check resolves, when the oscillator trim moves, or once, just before the first string, when a WFCK carrier appears after power-on, and otherwise never while the stealth window is open or held, and a write that cannot finish is kept and retried, so a settled console with a settled trim writes nothing; cell endurance is 100,000 writes. A check byte makes a record cut short by power-off read as the defaults. Reflashing erases the record, because both fuse sets leave EESAVE unprogrammed (datasheet Table 20-4, high fuse bit 3). The 2-frame step and the 20-frame bound are not yet tuned on a console. The trim targets the datasheet's 1% after user calibration, against 10% from the factory (Table 21-2). Source: src/calib.c, src/run.c, src/port_chip.S, ATtiny25/45/85 datasheet 2586Q.

Configuration

One source builds every variant; the knobs are passed to make. Source: Makefile.

KnobValuesDefaultSelectsSource
REGIONjp, us, euusthe one region string the chip emitsMakefile, src/region.c
PROFILEdebug, final, tracedebugwhat pin 2 does: debug lights every stage, string and fault; final blinks briefly at power-on and on an accepted disc, and shows each fault once; trace makes it a 115200-baud serial line carrying a boot report, each SUBQ frame and each decision the chip takes; DATA is the same in all threeMakefile, src/run.c, src/trace.c, include/pscu/indicator.h
VCD_FILTERoff, onoffon for the SCPH-5903 only: injection arms on a game's lead-in TOC, never on a Video CD's, as PsNee V9.0's SCPH-5903 filter doesMakefile, src/subq.c, PsNee PSNee.ino L456-L490

A non-default region, filter or profile tags the artifact name, for example openscex-modchip-attiny85-jp.hex, openscex-modchip-attiny85-jp-vcd.hex or openscex-modchip-attiny85-final.hex. Source: Makefile.

Against other chips

CapabilityopenscexPsNee V9Mayumi V4MM3Source
Stealth triggerSUBQ check window, closed by the program areaSUBQ decodesense line plus lid linesame program as Mayumi V4src/loop.c, PsNee PSNee.ino L525-L551, quade.co Mayumi V4 guide, quade.co MM3 guide
Disc-swap detectionSUBQ silence while the drive is stoppedSUBQ counter decaylid linelid linesrc/run.c, PsNee PSNee.ino L525-L551, quade.co Mayumi V4 guide, quade.co MM3 guide
Clockinternal, trimmed against SUBQinternalconsoleinternal RCsrc/trim.c, quade.co PsNee guide, quade.co Mayumi V4 guide, quade.co MM3 guide
Boot-ROM BIOS patchno, use a patched BIOSyes, ATmega buildsnonoAGENTS.md, PsNee PSNee.ino L33-L38, quade.co Mayumi V4 guide, quade.co MM3 guide
BoardsPU-7 to PM-41(2)PU-7 to PM-41(2)PU-18 and laterPU-7 and latersrc/board_mode.c, quade.co PsNee guide, quade.co Mayumi V4 guide, quade.co MM3 guide
Diagnosticsindicator stage and result codesserial debugnonenonesrc/indicator.c, quade.co PsNee guide
Per-console learningstart point and oscillator trimnonenonenonesrc/calib.c, quade.co PsNee guide
Tests and static analysishost, simavr, mutation, MISRA, console benchnonenonenoneCONTRIBUTING.md, .github/workflows/ci.yml
Field record2 boards, earlier firmwareyearsdecadesdecadescompatibility reports, quade.co PS1 modchip guide

In the console bench

The console bench plays one simulated console into every chip it can build or load. Each check models an anti-mod test or a console fault and holds only if it holds in every scenario for a board the chip targets; a chip that targets none of those boards reads n/a. These are simulation results, not console results; CONTRIBUTING.md describes the model and its sources.

Propertyopenscexpsnee-attiny85psnee-atmega328pmayumi-v4mm3-12c508aold-crow-12c508old-crow-12c508-v54fold-crow-16c84old-crow-16c54modavr-attiny13ubernee-atmega328ponechip-12c508aliss-at90s1200Source
Silent in the anti-mod v1 probepasspasspassfailfailfailfailfailfailfailpasspassfailpsx-spx cdromformat.md, anti-modchip
Re-authenticates in the anti-mod v2 re-readpasspasspassfailfailpasspasspasspasspasspassfailpasstonyhax docs/ap_v2.c, aprip readme, APv2
Silent in the anti-mod v2 probepasspasspassfailfailfailfailfailfailfailpasspassfailtonyhax docs/ap_v2.c, psx-spx cdromformat.md, anti-modchip
No string once the game runspasspasspassfailfailfailfailfailfailfailfailfailfailpsx-spx cdromdrive.md, 19h,04h
Console pins float while not injectingpassfailfailfailfailfailfailfailfailfailfailfailfailquade.co PsNee guide, bench runners
Accepts after SQCK sticks lowpasspasspasspasspassn/an/an/an/an/apassfailn/atools/bench/edge_scenarios.py
Recovers from a WFCK stall, WFCK never drivenpasspasspasspasspassn/an/an/an/an/afailpassn/atools/bench/edge_scenarios.py
Rearms for a second discpasspasspasspasspassn/an/an/an/an/apasspassn/atools/bench/scenarios.py
  • Silent in the anti-mod v1 probe: the game counts SCEx strings while playing the program area, where an original disc has none; any string it receives whole there means a modchip. Source: psx-spx cdromformat.md, anti-modchip.
  • Re-authenticates in the anti-mod v2 re-read: ReadTOC clears the licensed status, and GetID fails on a copy unless a full string arrives while the lead-in is read again. Source: tonyhax docs/ap_v2.c, aprip readme, APv2.
  • Silent in the anti-mod v2 probe: after the re-read the game seeks to the middle of the disc and counts SCEx strings again. Source: tonyhax docs/ap_v2.c, psx-spx cdromformat.md, anti-modchip.
  • No string once the game runs: the region check reads the lead-in; a string while the program area is read is what a detector looks for. Source: psx-spx cdromdrive.md, 19h,04h.
  • Console pins float while not injecting: a line driven or pulled up between strings loads the console's own signal, the PM-41(2) pickup noise, and is visible to anything watching it; checked on DATA, the gate and every input. Source: quade.co PsNee guide, bench runners.
  • Accepts after SQCK sticks low: a clock that stops mid-frame must not hang the chip past the check. Source: tools/bench/edge_scenarios.py.
  • Recovers from a WFCK stall, WFCK never driven: on a carrier board WFCK is the CD DSP's output; driving it fights the console. Source: tools/bench/edge_scenarios.py.
  • Rearms for a second disc: a multi-disc game asks for the region again after a swap. Source: tools/bench/scenarios.py.

Tested on hardware

Combinations that booted out-of-region discs on a real console with the console-clock firmware of 2026-10-05. Source: compatibility reports.

Board familyConsoleClockInjectionStatusSource
PU-18fat, NTSC-U/CconsoleSCExbootedcompatibility reports
PM-41PSoneconsoleSCExbootedcompatibility reports

That firmware predates the lid-wire-free swap detection, the trimmed oscillator, program-area silence, re-read handling and the bounded SUBQ waits; the current firmware has not yet run on a console. Untested on hardware: PU-7, PU-8, PU-20, PU-22, PU-23, PM-41(2), and PAL or NTSC-J on any family. Source: compatibility reports, AGENTS.md.

To add your console, open a compatibility report. A disc with anti-mod protection, APv1 or APv2, tests the stealth window hardest: it boots only if the chip stays silent once the console reads the game. The form asks for one; the tonyhax international anti-piracy table lists 57 APv1 and 200 APv2 discs. Source: tonyhax international anti-piracy table, src/inject.c.

Versioning

Releases follow Semantic Versioning on the 0.x line, so a minor release can break compatibility. v0.3.0 to v0.8.0 are ATtiny84 builds, and releases up to v0.2.0 are ATtiny85 builds of an earlier four-wire design. Each release is built from a commit whose CI passed, and whose platform mutation run passed when the push changed what it covers, plus its changelog entry, and the release job refuses a tag that changes anything else; notes are on the releases page.

Support

NeedWhere
Bug reportbug template
Result on your consolecompatibility report
Security reportsecurity policy, reported privately
Contributingcontribution guide
Code of conductcode of conduct

License

MIT for the firmware, the documentation and the board designs in hardware/.

The footprints in hardware/openscex.pretty start from KiCad's library footprints and keep their licence, CC-BY-SA-4.0 with the KiCad libraries exception: a board made with them carries no obligation. Source: REUSE.toml.

Parts

13
QtyValueFootprintReferences
1100nFCapacitor_THT:C_Rect_L4.0mm_W2.5mm_P2.50mmC1
110kResistor_THT:R_Axial_DIN0204_L3.6mm_D1.6mm_P5.08mm_HorizontalR1
1220uFCapacitor_THT:CP_Radial_D6.3mm_P2.50mmC2
1ATtiny85-20PUPackage_DIP:DIP-8_W7.62mm_SocketU1
1DATATestPoint:TestPoint_Pad_4.0x4.0mmTP4
1GNDTestPoint:TestPoint_Pad_4.0x4.0mmTP6
1INDConnector_JST:JST_XH_B3B-XH-A_1x03_P2.50mm_VerticalJ2
1ISPopenscex:IDC-Header_2x03_P2.54mm_Vertical_Wurth_61200621621J1
1SERIALConnector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_VerticalJ3
1SQCKTestPoint:TestPoint_Pad_4.0x4.0mmTP2
1SUBQTestPoint:TestPoint_Pad_4.0x4.0mmTP3
1VCCTestPoint:TestPoint_Pad_4.0x4.0mmTP5
1WFCKTestPoint:TestPoint_Pad_4.0x4.0mmTP1

Questions

How big is the Openscex PCB?

It measures 35.6 × 34.3 mm and has 2 copper layers, 1.6 mm thick.

What parts does the Openscex use?

13 parts, built around the ATTINY85-20PU. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Openscex KiCad files?

From the gufranco/openscex-modchip repository on GitHub, at the commit this page was made from: the board is hardware/openscex.kicad_pcb and its schematic hardware/openscex.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Openscex design in my own project?

Its repository publishes it under the MIT licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).

Similar boards

  • 2 layers30.7 × 76.1 mmRepeater v2, the printed circuit board in 3D

    Repeater v2

    plusvic/simplebus2-intercom

    General description of the Simplebus2 protocol used by some Comelit's intercoms. Hardware for retransmitting Simplebus2 messages over UART.

    17 parts95CC0-1.0
  • 2 layers101.5 × 54 mmSimkarte, the printed circuit board in 3D

    Simkarte

    osmocom/osmocom-analog

    Software Implementation of analog cellular telephony systems like NMT, TACS, IMTS, A/B/C-Netz; mirror of https://gitea.osmocom.org/cellular-infrastructure/osmocom-analog

    15 parts32GPL-3.0
  • 2 layers76.2 × 50.8 mmJerboa-8, the printed circuit board in 3D

    Jerboa-8

    strickyak/jerboa-attiny85-mod-synth

    Modular Synthesizer using an ATtiny85 chip for each module.

    23 parts31MIT
  • 2 layersCastVolumeKnob, the printed circuit board in 3D

    CastVolumeKnob

    WebmasterTD/CastVolumeKnob

    Hardware Volume Knob for Google Cast devices

    18 parts25
  • 2 layers55.9 × 55.9 mmPrintplaat, the printed circuit board in 3D

    Printplaat

    floriaanpost/wonderfoon

    Een nieuwe implementatie van de wonderfoon

    14 parts24
  • 2 layers71.1 × 40.6 mmTiny blinky, the printed circuit board in 3D

    Tiny blinky

    Hack-a-Day/Remoticon_KiCad_Blender

    support files for the 2020 Remoticon KiCad to Blender > PCB renders workshop

    21 parts22
  • 2 layers242 × 60 mmRoothubs, the printed circuit board in 3D

    Roothubs

    gkiefer/home2l

    The Home2Ls - Smart Tools for a Private Home

    178 parts21