1
0
mirror of https://github.com/kalymos/PsNee.git synced 2026-05-10 17:09:44 +00:00
Files
kalymos.PsNee/PSNee/PSNee.ino
kalymos 0d2290aa68 Merge V9.0 into Main (#111)
* MUC-MCU

Fixed the MUC-MCU file name.
Fixed a bug that prevented additional MCUs from being compiled.
Modified the clock registers for ATtiny 48-88s; the TCCROB register is missing in these MUCs.

* Too many things to summarize

Refactoring of the setting file, to be able to have settings specifying the HYSTERESIS_MAX variable.
Renamed precompilation variable in BIOS_patching file.
Changing console selection in .ino file

* Renaming files to make updating easier

* Update README.md

* logo 8.7

* optimization on regin injection

* Ad bios patch 102A, and correct some images.

* SCPH_102A bios patch WIP

* Cleaning the selection area

Redesign of the selection area, and addition of the SCPH_hyma mode equivalent to the legacy mode with the difference of a hysteresis of 25

* Layout modification

* clining

* ad debug mode

* debug mesga

* creation of debug functions, and cleaning

* Update PSNee.ino

* serial debug cleaning

* added compilation message

* some comment cleaning

* removal of the TIMER

removal of the ISR(CTC_TIMER_VECTOR) function, and all its dependencies.

- Timer_Start
- Timer_Stop

modification
- Board detection
- inject_SCEX
- BIOS_PATCH
- MCU
- Setting

* Update PSNee.ino

Implementing a dedicated board_detection function with optimized logic.
Remodeling the inject_SCEX function.

* timer optimization

* Breaking down the main function into smaller modules.

Modular breakdown: Separated main logic into
 - captureSubQ,
 - logic_SCPH_5903,
 - logic_Standard,
 - performInjectionSequence.

Added support for SCPH-5903 by RepairBox

Co-Authored-By: RepairBox <33960601+danielfergisz@users.noreply.github.com>

* Optimization

performInjectionSequence(): Deep refactor with a cleaner.
captureSubQ(): Faster bit-banging and improved timing accuracy.
board_detection(): Optimized logic .
General Cleanup: Fixed all warnings and minimized memory footprint.

* Optimization

Optimization of the performInjectionSequence function
BIOS patch verification (work in progress)
and general code cleanup

* BIOS patch obtimisation

* opti patch bios 3500

* opti bios patch SCPH-1000

* opti BIOS patch SCPH-3000, SCPH-100.

* refactor: optimize BIOS patching sequence with refined timing nomenclature

Refined the BIOS patching and standardized
timing nomenclature for better technical clarity.

Changes:
- Ad technical documentation within the code.
- Standardized timing variables for better clarity:
    - BOOT_OFFSET: Replaces initial checkpoint.
    - FOLLOWUP_OFFSET: Replaces initial checkpoint2.
    - PULSE_COUNT: Replaces trigger .
    - BIT_OFFSET: Replaces hold .
    - OVERRIDE: Replaces patching.

* test refactor: optimize PS1 patch for SCPH-100 using zero-latency polling

Ported the BIOS patch from ISR to manual polling to achieve cycle-accurate
precision for 33.86MHz bus timing.

* test neo BIOS patch

* Refactor pulse counting logic for jitter reduction

* BIOS patch simplification

Removal of ISR and reduction of code in the BIOS patch, for improved portability and robustness.

For now it is only available for the SCPH-100 and 102.

* BIOS patche neo for SCPH-9000_7500 SCPH-7000 SCPH-5500

* BIOS patch neo SCPH-5000 SCPH-3500

* BIOS patche neo SCPH-3000 SCPH-1000

I finally completed the value conversion for the BIOS patch without IRS

* refactor: optimize core detection and injection logic for performance and size

- board_detection: Added early exit for newer boards and tightened the sampling loop to reduce CPU overhead.
- captureSubQ: Simplified clock-edge synchronization and localized buffers to improve register allocation.
- performInjectionSequence: Replaced bit-calculation math with a streamlined byte-shift approach. Refined WFCK modulation to ensure zero-jitter phase locking.
- logic_Standard/5903: Refactored pattern matching into a decision tree. Factorized redundant buffer checks (scbuf[1]/[6]) and replaced multiple equality tests with range comparisons.

Optimization results:
- Reduced Flash memory footprint (smaller binary size).
- Improved execution speed by removing redundant logical operations.
- Increased signal stability during the critical injection phase.

* PSNee v9.0 Update

New PSNeeCore v2: Completely rewritten core engine for all supported MCUs.
Auto-MCU Detection: Manual board selection removed. Automatically detects ATmega (328/168/128/PB), 32U4, and ATtiny families at compile time.
Deep Init Optimization: Hard-shutdown of unused internal modules (ADC, DAC, Timers) to minimize power draw and electrical noise.
Native PB Support: Full compatibility for 128PB and 328PB (PRR2 registers and extra Ports).

* PSNee v9.0 Update

New PSNeeCore v2: Completely rewritten core engine for all supported MCUs.
Auto-MCU Detection: Manual board selection removed. Automatically detects ATmega (328/168/128/PB), 32U4, and ATtiny families at compile time.
Deep Init Optimization: Hard-shutdown of unused internal modules (ADC, DAC, Timers) to minimize power draw and electrical noise.
Native PB Support: Full compatibility for 128PB and 328PB (PRR2 registers and extra Ports).
logo



---------

Co-authored-by: RepairBox <33960601+danielfergisz@users.noreply.github.com>
2026-04-19 19:31:47 +02:00

741 lines
28 KiB
C++

// PSNee-V9.0
/*********************************************************************************************************************
* CONSOLE MODEL SELECTION (SCPH Hardware Configuration)
*********************************************************************************************************************/
/*--------------------------------------------------------------------------------------------------------------------
* Models below do not require BIOS patching.
* Standard USB injection is supported.
*
* SCPH model number // region code | region
*--------------------------------------------------------------------------------------------------------------------*/
// #define SCPH_xxx1 // NTSC U/C | America.
// #define SCPH_xxx2 // PAL | Europ.
// #define SCPH_xxx3 // NTSC J | Asia.
// #define SCPH_xxxx // Universal
// #define SCPH_5903 // NTSC J | Asia VCD:
/*------------------------------------------------------------------------------------------------------------------
* WARNING: These models REQUIRE a BIOS patch.
*
* ISP programming is MANDATORY.
* The Arduino bootloader introduces a delay that prevents the BIOS patch injection.
* Using an ISP programmer eliminates this delay.
*
* Note: BIOS version is more critical than the SCPH number for patch success.
*-------------------------------------------------------------------------------------------------------------------
*
* // Data pin | Adres pin |
* SCPH model number // | 32-pin BIOS | 40-pin BIOS | BIOS version
*-------------------------------------------------------------------------------------------------------------------*/
// #define SCPH_102 // DX - D0 | AX - A7 | | 4.4e - CRC 0BAD7EA9, 4.5e -CRC 76B880E5
// #define SCPH_100 // DX - D0 | AX - A7 | | 4.3j - CRC F2AF798B
// #define SCPH_7000_7500_9000 // DX - D0 | AX - A7 | | 4.0j - CRC EC541CD0
// #define SCPH_3500_5000_5500 // DX - D0 | AX - A16 | AX - A15 | 3.0j - CRC FF3EEB8C, 2.2j - CRC 24FC7E17, 2.1j - CRC BC190209
// #define SCPH_3000 // DX - D5 | AX - A7, AY - A8 | AX - A6, AY - A7 | 1.1j - CRC 3539DEF6
// #define SCPH_1000 // DX - D5 | AX - A7, AY - A8 | AX - A6, AY - A7 | 1.0j - CRC 3B601FC8
/*******************************************************************************************************************
* Options
*******************************************************************************************************************/
#define REQUEST_INJECT_TRIGGER 10 // Now coupled with REQUEST_INJECT_GAP; allows for higher trigger
/*
* TRIGGER CALIBRATION:
* - Lower values (<5): Possible, but not beneficial.
* - The value of 11 for REQUEST_INJECT_TRIGGER must not be exceeded on Japanese models.
* This causes problems with disks that have anti-mod protection; it's less noticeable on other models.
* - Higher values (15-20-25-30): Possible for older or weak CD-ROM laser units.
*/
#define REQUEST_INJECT_GAP 5 // Stealth interval (must be 4-8 AND < REQUEST_INJECT_TRIGGER)
/*
* NOTE: REQUEST_INJECT_GAP defines the "cool-off" period between injections.
* - Optimal range: 4 to 8 (for natural CD timing & anti-mod bypass).
* - Constraint: Must ALWAYS be lower than REQUEST_INJECT_TRIGGER.
*/
#define LED_RUN // Enable visual feedback during injections.
/*
* LED CONNECTION GUIDE (1k ohm resistor recommended in series):
* - Arduino Uno/Nano: Uses onboard D13 LED.
* - ATtiny85/45: Connect LED between PB3 (Pin 2) and GND.
* - ATmega32U4 (Pro Micro): Connect LED between PB6 (Pin 10) and GND.
*/
//#define PATCH_SWITCHE // This allows the user to disable the BIOS patch on-the-fly.
/*
* This allows you to bypass the memory card blocking problems on the SCPH-7000.
* - Configure Pin D5 as Input.
* - Enable internal Pull-up.
* - Exit immediately the patch BIOS if the switch pulls the pin to GND
*/
// #define DEBUG_SERIAL_MONITOR // Enables serial monitor output.
/*
* Requires compilation with Arduino libs!
* For Arduino connect TXD and GND, for ATtiny PB3 (pin 2) and GND, to your serial card RXD and GND.
*
* For Arduino (Uno/Nano/Pro Mini):
* TX (Pin 1) -----> RX (Serial Card)
* GND -----> GND
*
* For ATtiny (25/45/85):
* Pin 2 (PB3) -----> RX (Serial Card)
* Pin 4 (GND) -----> GND
*
*/
/******************************************************************************************************************
* Summary of practical information. Fuses. Pinout
*******************************************************************************************************************
* Fuses
* MCU | High | Low | Extended
* --------------------------------------------------
* ATmega32U4 | DF | EE | D7
* ATtiny | DF | E2 | FF
*
* Pinout
* Arduino | PSNee |
* ---------------------------------------------------
* VCC | VCC |
* GND | GND |
* RST | RESET | Only for JAP_FAT
* D2 | BIOS AX | Only for Bios patch
* D3 | BIOS AY | Only for BIOS patch SCPH_1000, SCPH_3000
* D4 | BIOS DX | Only for Bios patch
* D5 | SWITCH | Optional for disabling Bios patch
* D6 | SQCK |
* D7 | SUBQ |
* D8 | DATA |
* D9 | WFCK |
* D13 ^ D10 | LED | D10 only for ATmega32U4
*
* ATtiny | PSNee | ISP |
* ---------------------------------------------------
* Pin1 | | RESET |
* Pin2 | LED ^ serial | | serial only for DEBUG_SERIAL_MONITOR
* Pin3 | WFCK | |
* Pin4 | GND | GND |
* Pin5 | SQCK | MOSI |
* Pin6 | SUBQ | MISO |
* Pin7 | DATA | SCK |
* Pin8 | VCC | VCC |
*******************************************************************************************************************/
/*******************************************************************************************************************
* pointer and variable section
*******************************************************************************************************************/
#include "MCU.h"
#include "settings.h"
uint8_t wfck_mode = 0; //Flag initializing for automatic console generation selection 0 = old, 1 = pu-22 end ++
uint8_t SUBQBuffer[12]; // Global buffer to store the 12-byte SUBQ channel data
uint8_t request_counter = 0;
#if defined(DEBUG_SERIAL_MONITOR)
uint16_t global_window = 0; // Stores the remaining cycles from the detection window
#endif
/*******************************************************************************************************************
* Code section
********************************************************************************************************************/
/****************************************************************************************
* FUNCTION : Bios_Patching()
*
* OPERATION : Real-time Data Bus (DX) override via Address Bus (AX / AY)
*
* KEY PHASES:
* 1. STABILIZATION & ALIGNMENT (AX):
* Synchronizes the execution pointer with the AX rising edge to establish
* a deterministic hardware timing reference.
*
* 2. SILENCE DETECTION (BOOT STAGE):
* Validates consecutive silent windows (SILENCE_THRESHOLD) to identify
* the specific boot stage before the target address call.
*
* 3. HARDWARE COUNTING & OVERDRIVE (AX):
* Engages INT0 to count AX pulses. On the final pulse, triggers a
* bit-aligned delay to force a custom state on the DX line.
*
* 4. SECONDARY SILENCE (GAP DETECTION):
* If PHASE_TWO_PATCH is active, monitors for a secondary silent gap
* (CONFIRM_COUNTER_TARGET_2) between patching windows.
*
* 5. SECONDARY OVERDRIVE (AY):
* Engages INT1 (AY) for the final injection stage, synchronizing the
* patch with the secondary memory address cycles.
*
* CRITICAL TIMING & TIMER-LESS ALIGNMENT:
* - DETERMINISTIC SILENCE: Uses cycle-accurate polling to filter boot jitter
* and PS1 initialization noise, replacing unstable hardware timers.
*
* - CYCLE STABILIZATION (16MHz LIMIT): Uses '__builtin_avr_delay_cycles' to
* prevent compiler reordering. At 16MHz, the CPU has zero margin; a single
* instruction displacement would break high-speed bus alignment.
*
**************************************************************************************/
#ifdef BIOS_PATCH
/**
* Shared state variables between ISRs and the main patching loop.
* Declared 'volatile' to prevent compiler optimization during busy-wait loops.
*/
volatile uint8_t impulse = 0; // Down-counter for physical address pulses
volatile uint8_t patch = 0; // Synchronization flag (0: Idle, 1: AX Done, 2: AY Done)
/**
* PHASE 3: Primary Interrupt Service Routine (AX)
* Triggered on rising edges to perform the real-time bus override.
*/
ISR(PIN_AX_INTERRUPT_VECTOR) {
if (--impulse == 0) {
// Precise bit-alignment delay within the memory cycle
__builtin_avr_delay_cycles(BIT_OFFSET_CYCLES);
#ifdef PHASE_TWO_PATCH
PIN_DX_SET; // Pre-drive high if required by specific logic
#endif
// DATA OVERDRIVE: Pull the DX bus to the custom state
PIN_DX_OUTPUT;
__builtin_avr_delay_cycles(OVERRIDE_CYCLES);
#ifdef PHASE_TWO_PATCH
PIN_DX_CLEAR;
#endif
// BUS RELEASE: Return DX to High-Z (Input) mode
PIN_DX_INPUT;
PIN_AX_INTERRUPT_DISABLE; // Stop tracking AX pulses
PIN_LED_OFF;
patch = 1; // Signal Phase 3 completion
}
}
#ifdef PHASE_TWO_PATCH
/**
* PHASE 5: Secondary Interrupt Service Routine (AY)
* Handles the second injection stage if multi-patching is active.
*/
ISR(PIN_AY_INTERRUPT_VECTOR) {
if (--impulse == 0) {
__builtin_avr_delay_cycles(BIT_OFFSET_2_CYCLES);
PIN_DX_OUTPUT;
__builtin_avr_delay_cycles(OVERRIDE_2_CYCLES);
PIN_DX_INPUT;
PIN_AY_INTERRUPT_DISABLE;
PIN_LED_OFF;
patch = 2; // Signal Phase 5 completion
}
}
#endif
void Bios_Patching(void) {
// --- HARDWARE BYPASS OPTION ---
#if defined(PATCH_SWITCHE)
PIN_SWITCH_INPUT; // Configure Pin D5 as Input
PIN_SWITCH_SET; // Enable internal Pull-up (D5 defaults to HIGH)
__builtin_avr_delay_cycles(10); // Short delay for voltage stabilization
/**
* Exit immediately if the switch pulls the pin to GND (Logic LOW).
* This allows the user to disable the BIOS patch on-the-fly.
*/
if (PIN_SWITCH_READ == 0) {
return;
}
#endif
uint8_t current_confirms = 0;
//uint16_t count;
patch = 0; // Reset sync flag
sei(); // Enable Global Interrupts
PIN_AX_INPUT; // Set AX to monitor mode
// --- PHASE 1: STABILIZATION & ALIGNMENT (AX) ---
// Establish a deterministic hardware timing reference.
if (PIN_AX_READ != 0) {
while (WAIT_AX_FALLING); // Wait if bus is busy
while (WAIT_AX_RISING); // Sync with next pulse start
} else {
while (WAIT_AX_RISING); // Sync with upcoming pulse
}
// --- PHASE 2: SILENCE DETECTION ---
// Validate the exact number of silence windows to identify the boot stage.
while (current_confirms < CONFIRM_COUNTER_TARGET) {
uint16_t count = SILENCE_THRESHOLD;
while (count > 0) {
if (PIN_AX_READ != 0) {
while (WAIT_AX_FALLING);
break; // Impulse detected: retry current silence block
}
#ifdef IS_32U4_FAMILY
__asm__ __volatile__ ("nop");
#endif
count--;
}
if (count == 0) {
current_confirms++; // Validated one silence window
}
}
// --- PHASE 3: LAUNCH HARDWARE COUNTING (AX) ---
impulse = PULSE_COUNT;
PIN_LED_ON;
PIN_AX_INTERRUPT_CLEAR;
PIN_AX_INTERRUPT_RISING; // Setup rising-edge trigger
PIN_AX_INTERRUPT_ENABLE; // Engage ISR
while (patch != 1);
// --- PHASE 4 & 5: SECONDARY PATCHING SEQUENCE ---
#ifdef PHASE_TWO_PATCH
PIN_AY_INPUT;
current_confirms = 0;
impulse = PULSE_COUNT_2;
// Monitor for the specific silent gap before the second patch window
while (current_confirms < CONFIRM_COUNTER_TARGET_2) {
uint16_t count = SILENCE_THRESHOLD;
while (count > 0) {
if (PIN_AX_READ != 0) {
while (WAIT_AX_FALLING);
break;
}
#ifdef IS_32U4_FAMILY
__asm__ __volatile__ ("nop");
#endif
count--;
}
if (count == 0) {
current_confirms++;
}
}
PIN_LED_ON;
PIN_AY_INTERRUPT_CLEAR;
PIN_AY_INTERRUPT_FALLING;
PIN_AY_INTERRUPT_ENABLE;
while (patch != 2); // Busy-wait for secondary ISR completion
return;
#endif
}
#endif
/*******************************************************************************************************************
* FUNCTION : BoardDetection
*
* DESCRIPTION :
* Distinguishes motherboard generations (PU-7 through PU-18) by analyzing
* the behavior of the WFCK signal.
*
* SIGNAL CHARACTERISTICS:
* - Legacy Boards (PU-7 to PU-20): WFCK acts as a static GATE signal.
* It remains HIGH.
* - Modern Boards (PU-22 or newer): WFCK is an oscillating clock signal
* (Frequency-based).
*
*
* WFCK: __----------------------- // CONTINUOUS (PU-7 .. PU-20)(GATE)
*
* WFCK: __-_-_-_-_-_-_-_-_-_-_-_- // FREQUENCY (PU-22 or newer)
*
*
* HISTORICAL CONTEXT:
* Traditionally, WFCK was referred to as the "GATE" signal. On early models,
* modchips functioned as a synchronized gate, pulling the signal LOW
* precisely when the region-lock data was being processed.
*
* FREQUENCY DATA:
* - Initial/Protection Phase: ~7.3 kHz.
* - Standard Data Reading: ~14.6 kHz.
*
*******************************************************************************************************************/
void BoardDetection() {
wfck_mode = 0; // Default: Legacy (GATE)
uint8_t pulse_hits = 25; // We need to see 25 oscillations to confirm FREQUENCY mode
uint16_t detectionWindow = 10000;
_delay_ms(300); // Wait for WFCK to stabilize (High on Legacy, Oscillation on Modern)
while (--detectionWindow) {
/**
* LOGIC BASED ON YOUR ANALYSIS:
* If WFCK is "CONTINUOUS" (Legacy), it stays HIGH. PIN_WFCK_READ will always be 1.
* If WFCK is "FREQUENCY" (Modern), it will hit 0 (LOW) periodically.
*/
if (!PIN_WFCK_READ) { // Detect a LOW state (only possible in FREQUENCY mode)
pulse_hits--; // Record one oscillation hit
if (pulse_hits == 0) {
wfck_mode = 1; // Confirmed: FREQUENCY mode (PU-22 or newer)
#if defined(DEBUG_SERIAL_MONITOR)
global_window = detectionWindow;
#endif
return; // Exit as soon as we are sure
}
/**
* SYNC: Wait for the signal to go HIGH again.
* This ensures we count each pulse of the "FREQUENCY" signal only once.
*/
while (!PIN_WFCK_READ && detectionWindow > 0) {
detectionWindow--;
}
}
}
// If the window expires without seeing enough LOW pulses, it remains wfck_mode = 0 (GATE)
}
/******************************************************************************************************************
* FUNCTION : CaptureSUBQ
*
* DESCRIPTION :
* Captures a complete 12-byte SUBQ frame.
* Synchronizes with the SQCK (SUBQ Clock) pin to shift in serial data.
* Implementation: Synchronous Serial, LSB first reconstruction.
******************************************************************************************************************/
void CaptureSUBQ(void) {
uint8_t bytesRemaining = 12; // Total frame size for a complete SUBQ
uint8_t* bufferPtr = SUBQBuffer;
do {
uint8_t currentByte = 0;
uint8_t bitsToRead = 8;
while (bitsToRead--) {
/**
* PHASE 1: BIT SYNCHRONIZATION (SQCK)
* The CD controller signals a new bit by toggling the Clock line.
* Data is sampled on the RISING EDGE of SQCK for maximum stability.
*/
while (PIN_SQCK_READ); // Wait for falling edge
while (!PIN_SQCK_READ); // Wait for rising edge
/**
* PHASE 2: BIT ACQUISITION & SHIFTING
* The PlayStation SUBQ bus transmits data LSB (Least Significant Bit) first.
* We shift the current byte right and inject the new bit into the MSB (0x80).
*/
currentByte >>= 1;
if (PIN_SUBQ_READ) {
currentByte |= 0x80;
}
}
// Store reconstructed byte and advance pointer
*bufferPtr++ = currentByte;
} while (--bytesRemaining); // Efficient countdown for AVR binary size
#if defined(DEBUG_SERIAL_MONITOR)
CaptureSUBQLog(SUBQBuffer);
#endif
}
/******************************************************************************************
* FUNCTION : Filter_SUBQ_Samples() [SCPH_5903 Dual-Interface Variant]
*
* DESCRIPTION :
* Parses and filters the raw serial data stream from the SUBQ pin specifically
* for the SCPH-5903 model to differentiate between Game Discs and Video CDs (VCD).
*
* 1. VCD EXCLUSION: Specifically filters out VCD Lead-In patterns (sub-mode 0x02)
* to prevent incorrect region injection on non-game media.
* 2. SUBQ HIT COUNTING: Increments 'request_counter' for valid PlayStation TOC (A0-A2)
* markers or active game tracking.
* 3. SIGNAL DECAY: Decrements the counter when SUBQ samples match VCD patterns
* or unknown data, ensuring stable disc identification.
*
* INPUT : isDataSector (bool) - Filtered flag based on raw sector control bits.
******************************************************************************************/
#ifdef SCPH_5903
void FilterSUBQSamples(uint8_t controlByte) {
// --- STEP 0: Data/TOC Validation ---
// Optimized mask (0xD0) to verify bit6 is SET while bit7 and bit4 are CLEARED.
uint8_t isDataSector = ((controlByte & 0xD0) == 0x40);
// --- STEP 1: SUBQ Frame Synchronization ---
// Fast filtering: ignore raw data if sync markers (index 1 and 6) are not 0x00.
if (SUBQBuffer[1] == 0x00 && SUBQBuffer[6] == 0x00) {
/**
* HIT INCREMENT CONDITIONS:
* A. VALID PSX LEAD-IN: Data sector AND Point A0-A2 range AND NOT VCD (sub-mode != 0x02).
* (uint8_t)(SUBQBuffer[2] - 0xA0) <= 2 is an optimized check for 0xA0, 0xA1, 0xA2.
* B. TRACKING MAINTENANCE: Keeps count if already synced and reading Mode 0x01 or Data.
*/
if ( (isDataSector && (uint8_t)(SUBQBuffer[2] - 0xA0) <= 2 && SUBQBuffer[3] != 0x02) ||
(request_counter > 0 && (SUBQBuffer[0] == 0x01 || isDataSector)) )
{
request_counter++; // Direct increment: faster on 16MHz AVR
return;
}
}
// --- STEP 2: Signal Decay / Pattern Mismatch ---
// Decrement the hit counter if no valid PSX pattern is detected in the SUBQ stream.
if (request_counter > 0) {
request_counter--; // Direct decrement: saves CPU cycles
}
}
#else
/******************************************************************************************
* FUNCTION : FilterSUBQSamples()
*
* DESCRIPTION :
* Parses and filters the raw serial data stream from the SUBQ pin.
* Increments a hit counter (request_counter) when specific patterns are identified
* in the SUBQ stream, confirming the laser is reading the region-check area.
*
* 1. RAW BUS FILTERING: Validates SUBQ framing by checking sync markers (index 1 & 6).
* 2. PATTERN MATCHING: Detects Lead-In TOC (A0-A2) or Track 01 at the spiral start.
* 3. SIGNAL DECAY: Decrements the counter if the current SUBQ sample does not
* match expected PlayStation protection patterns.
*
* INPUT : isDataSector (bool) - Filtered flag based on raw sector control bits.
******************************************************************************************/
void FilterSUBQSamples(uint8_t controlByte) {
// --- STEP 0: Data/TOC Validation ---
// Optimized mask (0xD0) to verify bit6 is SET while bit7 and bit4 are CLEARED.
uint8_t isDataSector = ((controlByte & 0xD0) == 0x40);
// --- STEP 1: SUBQ Frame Synchronization ---
// Ignore the raw bitstream unless sync markers (1 & 6) are 0x00.
if (SUBQBuffer[1] == 0x00 && SUBQBuffer[6] == 0x00) {
/*
* HIT INCREMENT CONDITIONS:
* A. LEAD-IN PATTERNS: Detects TOC markers (A0-A2) or Track 01 at the spiral start.
* The calculation (uint8_t)(SUBQBuffer[3] - 0x03) >= 0xF5 handles
* the 0x98 to 0x02 wrap-around near the TOC boundary.
* B. TRACKING LOCK: Maintains the counter if already synced and reading
* valid sectors (Audio or Data).
*/
if ( (isDataSector && (SUBQBuffer[2] >= 0xA0 || (SUBQBuffer[2] == 0x01 && ( (uint8_t)(SUBQBuffer[3] - 0x03) >= 0xF5)))) ||
(request_counter > 0 && (SUBQBuffer[0] == 0x01 || isDataSector)) )
{
request_counter++; // Direct increment: saves CPU cycles
return;
}
}
// --- STEP 2: Signal Decay / Missed Hits ---
// Reduce the hit counter if the current SUBQ sample fails validation.
if (request_counter > 0) {
request_counter--; // Direct decrement: faster on 16MHz AVR
}
}
#endif
/*********************************************************************************************
* FUNCTION : PerformInjectionSequence
*
* DESCRIPTION :
* Executes the SCEx injection sequence to bypass the CD-ROM regional lockout.
* Supports two hardware-specific injection methods:
*
* 1. Legacy Gate Mode (PU-7 to PU-20): Modchip acts as a logic gate to pull
* the signal down. WFCK is simulated by the chip if necessary.
*
* 2. WFCK SYNC MODE (PU-22 and later):
* Synchronizes data injection with the console's hardware WFCK clock.
* The signal is modulated on every clock edge to ensure
* alignment with the CD-ROM controller's internal timing.
*
* NOTE: WFCK frequency is approx. 7.3 kHz during initialization/region check,
* but doubles to 14.6 kHz during normal data reading. The modulation loop
* handles both speeds as it syncs directly to the signal edges.
*********************************************************************************************/
void PerformInjectionSequence(uint8_t injectSCEx) {
static const uint8_t allRegionsSCEx[3][6] = {
{ 0x59, 0xC9, 0x4B, 0x5D, 0xDA, 0x02 }, // SCEI (Jap)
{ 0x59, 0xC9, 0x4B, 0x5D, 0xFA, 0x02 }, // SCEA (USA)
{ 0x59, 0xC9, 0x4B, 0x5D, 0xEA, 0x02 } // SCEE (PAL)
};
#ifdef LED_RUN
PIN_LED_ON;
#endif
const uint16_t BIT_DELAY = 4000;
PIN_DATA_OUTPUT;
PIN_DATA_CLEAR;
if (!wfck_mode) { // Legacy timing mode
PIN_WFCK_OUTPUT;
PIN_WFCK_CLEAR;
}
for (uint8_t regionCycle = 0; regionCycle < 3; regionCycle++) {
// Select the current region index to inject (Use cycle index if Universal mode 3)
uint8_t regionIndex = (injectSCEx == 3) ? regionCycle : injectSCEx;
// 44-bit Loop (LSB first)
for (uint8_t bitPosition = 0; bitPosition < 44; bitPosition++) {
// Direct bit extraction from the array (Index >> 3 = Byte, Index & 0x07 = Bit)
uint8_t currentBit = (allRegionsSCEx[regionIndex][bitPosition >> 3] >> (bitPosition & 0x07)) & 0x01;
if (wfck_mode) {
/* METHOD 1: PULSE COUNTING (WFCK SYNC) */
for (uint8_t count = 30; count > 0; count--) {
// Wait for falling edge (HIGH to LOW)
while (PIN_WFCK_READ);
PIN_DATA_CLEAR; // Pulse start
// Wait for rising edge (LOW to HIGH)
while (!PIN_WFCK_READ);
if (currentBit) {
PIN_DATA_SET; // Modulate if Bit is 1
}
}
} else {
/* METHOD 2: TIME REFERENCE (FIXED DELAY) */
if (currentBit == 0) {
PIN_DATA_CLEAR;
PIN_DATA_OUTPUT;
} else {
// High-Z mode (Input) for Bit 1
PIN_DATA_INPUT;
}
_delay_us(BIT_DELAY);
}
}
// EXIT CONDITION: Stop after the first successful region unless Universal mode (3)
if (injectSCEx != 3) {
PIN_DATA_OUTPUT;
PIN_DATA_CLEAR;
if (!wfck_mode) // Set WFCK pin input
{
PIN_WFCK_INPUT;
}
break; // Stop immediately after the first region injection
}
// Inter-region delay for Universel (3)
PIN_DATA_OUTPUT;
PIN_DATA_CLEAR;
_delay_ms(90);
}
if (!wfck_mode) // Set WFCK pin input
{
PIN_WFCK_INPUT;
}
#ifdef LED_RUN
PIN_LED_OFF;
#endif
#if defined(DEBUG_SERIAL_MONITOR)
InjectLog();
#endif
}
void Init() {
#ifdef LED_RUN
PIN_LED_OUTPUT;
#endif
// --- Critical Boot Patching ---
#ifdef BIOS_PATCH
// #ifdef LED_RUN
// PIN_LED_ON;
// #endif
// Execute BIOS patching
Bios_Patching();
// #ifdef LED_RUN
// PIN_LED_OFF;
// #endif
#endif
PIN_SQCK_INPUT;
PIN_SUBQ_INPUT;
// --- Debug Interface Setup ---
#if defined(DEBUG_SERIAL_MONITOR) && defined(IS_ATTINY_FAMILY)
pinMode(debugtx, OUTPUT); // software serial tx pin
mySerial.begin(115200); // 13,82 bytes in 12ms, max for softwareserial
#elif defined(DEBUG_SERIAL_MONITOR) && defined(IS_32U4_FAMILY)
// On 32U4, Serial1 is the hardware UART on pins D0 (RX) and D1 (TX)
Serial1.begin(500000);
#elif defined(DEBUG_SERIAL_MONITOR) && defined(IS_328_168_FAMILY)
Serial.begin(500000); // Standard hardware UART for 328/168
#endif
// Identify board revision (PU-7 to PU-22+) to set correct injection timings
BoardDetection();
}
int main() {
Init();
#if defined(DEBUG_SERIAL_MONITOR)
// Display initial board detection results (Window remaining & WFCK mode)
BoardDetectionLog( global_window, wfck_mode, INJECT_SCEx);
#endif
while (1) {
_delay_ms(1); // Timing Sync: Prevent reading the tail end of the previous SUBQ packet
CaptureSUBQ(); // DATA ACQUISITION: Capture the 12-byte SUBQ stream.
/**
* REQUEST FILTERING: Analyze the SUBQ Control byte (SUBQBuffer[0]).
* If valid, 'request_counter' is incremented inside the filter.
* Masking (0xD0) and pattern matching are handled internally.
*/
FilterSUBQSamples(SUBQBuffer[0]);
/**
* INJECTION TRIGGER: Once enough valid requests are detected,
* trigger the SCEx injection sequence.
*/
if (request_counter >= REQUEST_INJECT_TRIGGER) {
PerformInjectionSequence(INJECT_SCEx); // Execute the SCEx injection burst
// STEALTH GAP: Mimics natural CD behavior to bypass anti-mod detection.
request_counter = (REQUEST_INJECT_TRIGGER - REQUEST_INJECT_GAP);
}
}
return 0;
}