1
0
mirror of https://github.com/kalymos/PsNee.git synced 2026-05-09 08:37:11 +00:00

finalize PerformInjectionSequence and refactor logic

- Optimized SCEx injection algorithm for PU-22+ (WFCK sync).
- Renamed hysteresis variables to REQUEST_INJECT_TRIGGER/GAP for clarity.
- Refactored internal comments for better technical documentation.
- Improved pin state recovery (High-Z) after injection for bus stability.
This commit is contained in:
kalymos
2026-04-05 20:24:04 +02:00
parent d3dbebf70d
commit 12b599291d
3 changed files with 139 additions and 147 deletions

View File

@@ -348,6 +348,8 @@
#define PIN_SQCK_INPUT DDRD &= ~(1 << DDD7) // SQCK Clock (PD7)
#define PIN_SUBQ_INPUT DDRE &= ~(1 << DDE6) // SUBQ Data (PE6)
// Configure lines as outputs (for injection/override)
#define PIN_DATA_OUTPUT DDRB |= (1 << DDB4)
#define PIN_WFCK_OUTPUT DDRB |= (1 << DDB5)
@@ -411,6 +413,8 @@
#define PIN_AY_INTERRUPT_RISING EICRA |= (1 << ISC01) | (1 << ISC00)
#define PIN_AY_INTERRUPT_VECTOR INT0_vect
#define PIN_AY_INTERRUPT_CLEAR EIFR |= (1 << INTF0)
#define PIN_AY_INTERRUPT_FALLING EICRA = (EICRA & ~((1 << ISC01) | (1 << ISC00))) | (1 << ISC01) // Configure INT1 for falling edge trigger
#endif
// Hardware Bypass Switch (On-the-fly deactivation)

View File

@@ -42,9 +42,27 @@
* Options
*******************************************************************************************************************/
#define LED_RUN // Turns on the LED when injections occur.
// D13 for Arduino, ATtiny add a led between PB3 (pin 2) and gnd with a 1k resistor in series,
// ATmega32U4 (Pro Micro) add a led between PB6 (pin 10) and gnd with a 1k resistor in series.
#define REQUEST_INJECT_TRIGGER 15 // Now coupled with REQUEST_INJECT_GAP; allows for higher trigger
/*
* TRIGGER CALIBRATION:
* - Lower values (<15): Possible, but not beneficial.
* - Higher values (20-30): Possible for older or weak CD-ROM laser units.
*/
#define REQUEST_INJECT_GAP 4 // 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 DEBUG_SERIAL_MONITOR // Enables serial monitor output.
@@ -106,14 +124,10 @@
#include "MCU.h"
#include "settings.h"
#define HYSTERESIS_MAX 25 // Now coupled with post-injection reset; allows for higher
// initial accumulation targets without the alignment drift
// (desync) previously affecting SCPH-100x models.
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 hysteresis = 0;
uint8_t request_counter = 0;
#if defined(DEBUG_SERIAL_MONITOR)
uint16_t global_window = 0; // Stores the remaining cycles from the detection window
@@ -325,9 +339,9 @@ uint8_t hysteresis = 0;
* the behavior of the WFCK signal.
*
* SIGNAL CHARACTERISTICS:
* - Legacy Boards (PU-7 to PU-18): WFCK acts as a static GATE signal.
* - Legacy Boards (PU-7 to PU-20): WFCK acts as a static GATE signal.
* It remains HIGH.
* - Modern Boards (PU-20 or newer): WFCK is an oscillating clock signal
* - Modern Boards (PU-22 or newer): WFCK is an oscillating clock signal
* (Frequency-based).
*
*
@@ -387,40 +401,41 @@ void BoardDetection() {
* FUNCTION : CaptureSUBQ
*
* DESCRIPTION :
* Captures a complete 12-byte SUBQ frame from the CD controller.
* 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;
// Clear current byte to avoid garbage data
*bufferPtr = 0;
while (bitsToRead--) {
/**
* PHASE 1: BIT SYNCHRONIZATION (SQCK)
* Wait for falling edge then sample on the RISING EDGE of 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 & LSB RECONSTRUCTION
* Shift right to move previous bits toward LSB and inject new bit at 0x80.
* 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).
*/
*bufferPtr >>= 1;
currentByte >>= 1;
if (PIN_SUBQ_READ) {
*bufferPtr |= 0x80;
currentByte |= 0x80;
}
}
// Advance pointer for the next byte
bufferPtr++;
// Store reconstructed byte and advance pointer
*bufferPtr++ = currentByte;
} while (--bytesRemaining); // Efficient countdown for AVR binary size
@@ -438,7 +453,7 @@ void CaptureSUBQ(void) {
*
* 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 'hysteresis' for valid PlayStation TOC (A0-A2)
* 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.
@@ -464,17 +479,17 @@ void CaptureSUBQ(void) {
* 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) ||
(hysteresis > 0 && (SUBQBuffer[0] == 0x01 || isDataSector)) )
(request_counter > 0 && (SUBQBuffer[0] == 0x01 || isDataSector)) )
{
hysteresis++; // Direct increment: faster on 16MHz AVR
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 (hysteresis > 0) {
hysteresis--; // Direct decrement: saves CPU cycles
if (request_counter > 0) {
request_counter--; // Direct decrement: saves CPU cycles
}
}
#else
@@ -484,7 +499,7 @@ void CaptureSUBQ(void) {
*
* DESCRIPTION :
* Parses and filters the raw serial data stream from the SUBQ pin.
* Increments a hit counter (hysteresis) when specific patterns are identified
* 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).
@@ -515,17 +530,17 @@ void CaptureSUBQ(void) {
*/
if ( (isDataSector && (SUBQBuffer[2] >= 0xA0 || (SUBQBuffer[2] == 0x01 && ( (uint8_t)(SUBQBuffer[3] - 0x03) >= 0xF5)))) ||
(hysteresis > 0 && (SUBQBuffer[0] == 0x01 || isDataSector)) )
(request_counter > 0 && (SUBQBuffer[0] == 0x01 || isDataSector)) )
{
hysteresis++; // Direct increment: saves CPU cycles
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 (hysteresis > 0) {
hysteresis--; // Direct decrement: faster on 16MHz AVR
if (request_counter > 0) {
request_counter--; // Direct decrement: faster on 16MHz AVR
}
}
@@ -539,8 +554,11 @@ void CaptureSUBQ(void) {
*
* 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 Modulation (PU-22+): Modchip modulates the DATA signal in
* sync with the console's real WFCK clock (7.3 kHz or 14.6 kHz).
*
* 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
@@ -548,124 +566,93 @@ void CaptureSUBQ(void) {
*********************************************************************************************/
void PerformInjectionSequence(uint8_t injectSCEx) {
/*
Security strings (44-bit SCEx) for the three main regions:
0: NTSC-J | SCEI | Sony Computer Entertainment Inc
1: NTSC-U/C | SCEA | Sony Computer Entertainment America
2: PAL | SCEE | Sony Computer Entertainment Europe
Stored in 6 bytes (48 bits); only the first 44 bits are used during injection.
0x59, 0xC9, 0x4B, 0x5D, 0x9A, 0x02 | SCEW | Worldwide / Net Yaroze
*/
static const uint8_t allRegionsSCEx[3][6] = {
{ 0x59, 0xC9, 0x4B, 0x5D, 0xDA, 0x02 }, // SCEI
{ 0x59, 0xC9, 0x4B, 0x5D, 0xFA, 0x02 }, // SCEA
{ 0x59, 0xC9, 0x4B, 0x5D, 0xEA, 0x02 } // SCEE
{ 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; // 4000us is the standard bit timing for SCEx signal (approx. 250 bps)
const uint8_t isWfck = wfck_mode; // Cache wfck_mode to save CPU cycles during the bit loop
/**
* HYSTERESIS COUPLING:
* Resets hysteresis close to MAX after injection. This allows for a wider
* tuning range of HYSTERESIS_MAX without drifting out of synchronization
* on SCPH-100x models.
*/
hysteresis = (HYSTERESIS_MAX - 6);
const uint16_t BIT_DELAY = 4000;
#ifdef LED_RUN
PIN_LED_ON;
#endif
PIN_DATA_OUTPUT;
PIN_DATA_CLEAR;
PIN_DATA_OUTPUT;
PIN_DATA_CLEAR;
// Legacy boards require the chip to drive the simulated WFCK/Gate
if (!isWfck) {
PIN_WFCK_OUTPUT;
PIN_WFCK_CLEAR;
if (!wfck_mode) { // Legacy timing mode
PIN_WFCK_OUTPUT;
PIN_WFCK_CLEAR;
}
// Loop through the selected region(s)
for (uint8_t i = 0; i < 3; i++) {
uint8_t regionIndex = (injectSCEx == 3) ? i : injectSCEx;
const uint8_t* bytePtr = allRegionsSCEx[regionIndex];
uint8_t currentByte = *bytePtr++;
uint8_t bitIdx = 0;
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;
// Process the 44-bit SCEx stream
for (uint8_t totalBits = 44; totalBits > 0; totalBits--) {
uint8_t currentBit = currentByte & 0x01;
currentByte >>= 1;
bitIdx++;
// Reload next byte every 8 bits (except the last partial one)
if (bitIdx == 8 && totalBits > 4) {
currentByte = *bytePtr++;
bitIdx = 0;
}
// 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 (currentBit == 0) {
// BIT 0: Pull DATA low-
PIN_DATA_CLEAR;
if (!isWfck) PIN_DATA_OUTPUT;
_delay_us(BIT_DELAY);
} else {
// BIT 1: Handle based on board generation
if (!isWfck) {
// Legacy Mode: Set DATA to High-Z (floating)
PIN_DATA_INPUT;
_delay_us(BIT_DELAY);
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 {
/*
WFCK Modulation Loop: Syncs to 7.3kHz or 14.6kHz.
Follows hardware edges to stay bit-perfect with the console.
*/
uint8_t count = 30;
while (count--) {
while (PIN_WFCK_READ); // Wait for Falling Edge
PIN_DATA_CLEAR;
while (!PIN_WFCK_READ); // Wait for Rising Edge
PIN_DATA_SET;
}
/* 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_OUTPUT;
PIN_DATA_CLEAR;
if (!isWfck) {
PIN_WFCK_INPUT;
PIN_DATA_INPUT;
if (!wfck_mode) // Set WFCK pin input
{
PIN_WFCK_INPUT;
}
break;
break; // Stop immediately after the first region injection
}
// Inter-region delay for Universal mode
// Inter-region delay for Universel (3)
PIN_DATA_OUTPUT;
PIN_DATA_CLEAR;
_delay_ms(180);
_delay_ms(90);
}
// Restore pins to Input/High-Z to avoid signal interference after injection
if (!isWfck) {
PIN_WFCK_INPUT;
PIN_DATA_INPUT;
if (!wfck_mode) // Set WFCK pin input
{
PIN_WFCK_INPUT;
}
#ifdef LED_RUN
PIN_LED_OFF;
PIN_LED_OFF;
#endif
#if defined(DEBUG_SERIAL_MONITOR)
InjectLog();
InjectLog();
#endif
}
@@ -723,19 +710,27 @@ int main() {
while (1) {
// Timing Sync: Prevent reading the tail end of the previous SUBQ packet
_delay_ms(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.
// Data Acquisition: Capture 12-byte SUBQ channel stream into buffer
CaptureSUBQ();
// Confidence Filtering: Accumulate hits toward HYSTERESIS_MAX
// Control byte masking (0xD0) and TOC pattern matching are handled inside the filter.
/**
* 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]);
// Execution: Trigger SCEx injection once confidence (hysteresis) is reached
if (hysteresis >= HYSTERESIS_MAX) {
PerformInjectionSequence(INJECT_SCEx);
/**
* 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;

View File

@@ -77,7 +77,7 @@
#define CONFIRM_COUNTER_TARGET 9
#define PULSE_COUNT 59
#define BIT_OFFSET_CYCLES 45
#define OVERRIDE_CYCLES 3
#define OVERRIDE_CYCLES 4
#define CONFIRM_COUNTER_TARGET_2 206
#define PULSE_COUNT_2 42
#define BIT_OFFSET_2_CYCLES 48
@@ -92,7 +92,7 @@
#define SILENCE_THRESHOLD 1100
#define CONFIRM_COUNTER_TARGET 9
#define PULSE_COUNT 91
#define BIT_OFFSET_CYCLES 45
#define BIT_OFFSET_CYCLES 43
#define OVERRIDE_CYCLES 3
#define CONFIRM_COUNTER_TARGET_2 222
#define PULSE_COUNT_2 70
@@ -215,7 +215,7 @@
------------------------------------------------------------------------------------------------*/
#if defined(DEBUG_SERIAL_MONITOR)
extern uint8_t hysteresis;
extern uint8_t request_counter;
#if defined(IS_32U4_FAMILY)
#define DEBUG_OUT Serial1
@@ -259,18 +259,8 @@ void CaptureSUBQLog(uint8_t *dataBuffer) {
return;
}
// --- ERROR REPORTING ---
// If errors occurred before this valid read, report the total count
if (errorCount > 0) {
#if defined(IS_ATTINY_FAMILY)
mySerial.print(F(" [Err x")); mySerial.print(errorCount); mySerial.println(F("]"));
#else
DEBUG_OUT.print(F(" [Missed sectors: ")); DEBUG_OUT.print(errorCount); DEBUG_OUT.println(F("]"));
#endif
errorCount = 0; // Reset counter after reporting
}
// --- DATA & HYSTERESIS DISPLAY ---
// --- DATA & request_counter DISPLAY ---
#if defined(IS_ATTINY_FAMILY)
// Compact hex output for ATtiny to maintain 12ms timing
for (uint8_t i = 0; i < 12; i++) {
@@ -278,9 +268,9 @@ void CaptureSUBQLog(uint8_t *dataBuffer) {
if (val < 0x10) mySerial.print("0");
mySerial.print(val, HEX);
}
// Append global hysteresis on the same line
// Append global request_counter on the same line
mySerial.print(F(" h:"));
mySerial.println(hysteresis);
mySerial.println(request_counter);
#else
// Formatted hex output for ATmega
for (uint8_t i = 0; i < 12; i++) {
@@ -289,9 +279,9 @@ void CaptureSUBQLog(uint8_t *dataBuffer) {
DEBUG_OUT.print(val, HEX);
DEBUG_OUT.print(" ");
}
// Append global hysteresis on the same line
// Append global request_counter on the same line
DEBUG_OUT.print(F("| Hyst: "));
DEBUG_OUT.println(hysteresis);
DEBUG_OUT.println(request_counter);
#endif
}
@@ -311,6 +301,9 @@ void InjectLog(){
/*------------------------------------------------------------------------------------------------
Compilation message
-----------------------------------------------------------------------------------------------*/
#if (REQUEST_INJECT_GAP >= REQUEST_INJECT_TRIGGER)
#error "Critical Error: REQUEST_INJECT_GAP must be smaller than REQUEST_INJECT_TRIGGER!"
#endif
#ifdef LED_RUN
#pragma message "Feature: Status LED ENABLED"