1
0
mirror of https://github.com/kalymos/PsNee.git synced 2026-03-09 12:30:41 +00:00

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>
This commit is contained in:
kalymos
2025-12-26 20:03:28 +01:00
parent 87a7b9d314
commit 1074336cdb
2 changed files with 192 additions and 104 deletions

View File

@@ -22,7 +22,7 @@
//#define SCPH_xxx1 // NTSC U/C | America.
//#define SCPH_xxx2 // PAL | Europ.
//#define SCPH_xxx3 // NTSC J | Asia.
//#define SCPH_5903 // NTSC J | Asia VCD:
// Models that require a BIOS patch.
@@ -44,10 +44,10 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
SCPH model number // Data pin | 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_100 // DX - D0 | AX - A7 | | 4.3j - CRC F2AF798B
//#define SCPH_7500_9000 // DX - D0 | AX - A7 | | 4.0j - CRC EC541CD0
//#define SCPH_7000 // DX - D0 | AX - A7 | | 4.0j - CRC EC541CD0 Enables hardware support for disabling BIOS patching.
#define SCPH_5500 // DX - D0 | AX - A5 | | 3.0j - CRC FF3EEB8C
//#define SCPH_5500 // DX - D0 | AX - A5 | | 3.0j - CRC FF3EEB8C
//#define SCPH_3500_5000 // DX - D0 | AX - A5 | AX - A4 | 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
@@ -128,6 +128,21 @@ volatile bool wfck_mode = 0;
volatile bool Flag_Switch = 0;
// Définition du type pour le pointeur de fonction
typedef void (*ConsoleLogicPtr)(uint8_t, uint8_t*);
////ConsoleLogicPtr currentLogic = logic_Standard; // Default
// Variables de contrôle globales
volatile ConsoleLogicPtr currentLogic;
uint8_t scbuf[12] = { 0 };
uint8_t hysteresis = 0;
uint16_t timeout_clock_counter = 0;
//Initializing values for region code injection timing
#define DELAY_BETWEEN_BITS 4000 // 250 bits/s (microseconds) (ATtiny 8Mhz works from 3950 to 4100) PU-23 PU-22 MAX 4250 MIN 3850
#define DELAY_BETWEEN_INJECTIONS 90 // The sweet spot is around 80~100. For all observed models, the worst minimum time seen is 72, and it works well up to 250.
/*------------------------------------------------------------------------------------------------
Code section
------------------------------------------------------------------------------------------------*/
@@ -185,6 +200,157 @@ void board_detection(){
}
}
//******************************************************************************************************************
// Reads a complete 12-byte SUBQ transmission from the CD drive.
// Uses clock-edge synchronization and includes a safety timeout for malformatted streams.
//******************************************************************************************************************
void captureSubQ(void) {
//uint8_t bitpos = 0;
uint8_t scpos = 0;
uint8_t bitbuf = 0;
GLOBAL_INTERRUPT_DISABLE; // Start critical section
do {
bitbuf = 0;
for (uint8_t i = 0; i < 8; i++) {
while (PIN_SQCK_READ != 0); // Wait for clock LOWE
while (PIN_SQCK_READ == 0); // Wait for clock HIGH
bitbuf >>= 1; // Décale d'abord
if (PIN_SUBQ_READ) bitbuf |= 0x80; // Insère le bit par le haut
}
scbuf[scpos++] = bitbuf;
} while (scpos < 12);
// do {
// for (bitpos = 0; bitpos < 8; bitpos++) {
// while (PIN_SQCK_READ != 0) { // Wait for clock LOW
// timeout_clock_counter++;
// if (timeout_clock_counter > 1000) {
// scpos = 0; timeout_clock_counter = 0; bitbuf = 0; bitpos = 0;
// continue;
// }
// }
// while (PIN_SQCK_READ == 0); // Wait for clock HIGH
// if (PIN_SUBQ_READ) {
// bitbuf |= (1 << bitpos);
// }
// timeout_clock_counter = 0;
// }
// scbuf[scpos] = bitbuf;
// scpos++;
// bitbuf = 0;
// } while (scpos < 12);
GLOBAL_INTERRUPT_ENABLE; // End critical section
}
/**************************************************************************************
* Processes sector data for the SCPH-5903 (Dual-interface PS1) to differentiate
* between PlayStation games and Video CDs (VCD).
*
* This heuristic uses an 'hysteresis' counter to stabilize disc detection:
* - Increases when a PSX Lead-In or valid game sector is identified.
* - Remains neutral/ignores VCD-specific Lead-In patterns.
* - Decreases (fades out) when the data does not match known patterns.
*
* isDataSector Boolean flag indicating if the current sector contains data.
**************************************************************************************/
void logic_SCPH_5903(uint8_t isDataSector) {
// Identify VCD Lead-In: Specific SCBUF patterns (0xA0/A1/A2) with sub-mode 0x02
bool isVcdLeadIn = isDataSector && scbuf[1] == 0x00 && scbuf[6] == 0x00 &&
(scbuf[2] == 0xA0 || scbuf[2] == 0xA1 || scbuf[2] == 0xA2) &&
(scbuf[3] == 0x02);
// Identify PSX Lead-In: Same SCBUF patterns but different sub-mode (!= 0x02)
bool isPsxLeadIn = isDataSector && scbuf[1] == 0x00 && scbuf[6] == 0x00 &&
(scbuf[2] == 0xA0 || scbuf[2] == 0xA1 || scbuf[2] == 0xA2) &&
(scbuf[3] != 0x02);
if (isPsxLeadIn) {
hysteresis++;
}
else if (hysteresis > 0 && !isVcdLeadIn &&
((scbuf[0] == 0x01 || isDataSector) && scbuf[1] == 0x00 && scbuf[6] == 0x00)) {
hysteresis++; // Maintain/Increase confidence for valid non-VCD sectors
}
else if (hysteresis > 0) {
hysteresis--; // Patterns stop matching
}
}
/******************************************************************************************
* Heuristic logic for standard PlayStation hardware (Non-VCD models).
*
* This function monitors disc sectors to identify genuine PlayStation discs:
* 1. Checks for specific Lead-In markers (Point A0, A1, A2 or Track 01).
* 2. Uses an incrementing 'hysteresis' counter to confirm disc validity.
* 3. Includes a 'fade-out' mechanism to reduce the counter if valid patterns are lost,
* effectively filtering out noise or read errors.
*
* isDataSector Boolean flag: true if the current sector is a data sector.
******************************************************************************************/
void logic_Standard(uint8_t isDataSector) {
// Detect specific Lead-In patterns
if ((isDataSector && scbuf[1] == 0x00 && scbuf[6] == 0x00) &&
(scbuf[2] == 0xA0 || scbuf[2] == 0xA1 || scbuf[2] == 0xA2 ||
(scbuf[2] == 0x01 && (scbuf[3] >= 0x98 || scbuf[3] <= 0x02)))) {
hysteresis++;
}
// Maintain confidence if general valid sector markers are found
else if (hysteresis > 0 &&
((scbuf[0] == 0x01 || isDataSector) && scbuf[1] == 0x00 && scbuf[6] == 0x00)) {
hysteresis++;
}
else if (hysteresis > 0) {
hysteresis--;
}
}
//******************************************************************************************************************
// Triggers the region code injection if the hysteresis threshold is reached.
//******************************************************************************************************************
void performInjectionSequence() {
if (hysteresis >= HYSTERESIS_MAX) {
hysteresis = 11; // Reset to 11 for faster re-injection if head stays in TOC
#ifdef LED_RUN
PIN_LED_ON;
#endif
PIN_DATA_OUTPUT; PIN_DATA_CLEAR;
if (!wfck_mode) { PIN_WFCK_OUTPUT; PIN_WFCK_CLEAR; }
_delay_ms(DELAY_BETWEEN_INJECTIONS);
for (uint8_t scex = 0; scex < 2; scex++) {
inject_SCEX(region[scex]);
}
if (!wfck_mode) { PIN_WFCK_INPUT; }
PIN_DATA_INPUT;
#ifdef LED_RUN
PIN_LED_OFF;
#endif
#if defined(PSNEE_DEBUG_SERIAL_MONITOR)
Debug_Inject();
#endif
}
}
/*****************************************************************************************
Function: inject_SCEX
@@ -231,9 +397,6 @@ void inject_SCEX(const char region) {
// repetitive conditional checks inside the high-timing-sensitive loop.
const uint8_t* ByteSet = (region == 'e') ? SCEEData : (region == 'a') ? SCEAData : SCEIData;
//Initializing values for region code injection timing
#define DELAY_BETWEEN_BITS 4000 // 250 bits/s (microseconds) (ATtiny 8Mhz works from 3950 to 4100) PU-23 PU-22 MAX 4250 MIN 3850
#define DELAY_BETWEEN_INJECTIONS 90 // The sweet spot is around 80~100. For all observed models, the worst minimum time seen is 72, and it works well up to 250.
// for (uint8_t bit_counter = 0; bit_counter < 44; bit_counter++) {
// // Check if the current bit is 0
@@ -303,6 +466,8 @@ void inject_SCEX(const char region) {
void Init() {
#if defined(ATmega328_168) || defined(ATmega32U4_16U4) || defined(ATtiny85_45_25)
// Optimization test in progress...
A;
B;
D;
@@ -338,15 +503,6 @@ void Init() {
}
int main() {
uint8_t hysteresis = 0;
uint8_t scbuf[12] = { 0 }; // SUBQ bit storage
uint16_t timeout_clock_counter = 0;
uint8_t bitbuf = 0;
uint8_t bitpos = 0;
uint8_t scpos = 0; // scbuf position
// uint16_t lows = 0;
// uint8_t preset = 0;
// uint32_t totalSamples = 400000;
Init();
@@ -372,50 +528,20 @@ int main() {
Debug_Log(lows, wfck_mode);
#endif
#ifdef SCPH_5903
currentLogic = logic_SCPH_5903;
#else
currentLogic = logic_Standard;
#endif
while (1) {
_delay_ms(1); /* Start with a small delay, which can be necessary
in cases where the MCU loops too quickly and picks up the laster SUBQ trailing end*/
GLOBAL_INTERRUPT_DISABLE; // start critical section
// Capture 8 bits for 12 runs > complete SUBQ transmission
do {
for (bitpos = 0; bitpos < 8; bitpos++) {
while (PIN_SQCK_READ != 0) // wait for clock to go low
{
timeout_clock_counter++;
// a timeout resets the 12 byte stream in case the PSX sends malformatted clock pulses, as happens on bootup
if (timeout_clock_counter > 1000) {
scpos = 0;
timeout_clock_counter = 0;
bitbuf = 0;
bitpos = 0;
continue;
}
}
// Wait for clock to go high
while (PIN_SQCK_READ == 0);
if (PIN_SUBQ_READ) // If clock pin high
{
bitbuf |= 1 << bitpos; // Set the bit at position bitpos in the bitbuf to 1. Using OR combined with a bit shift
}
timeout_clock_counter = 0; // no problem with this bit
}
scbuf[scpos] = bitbuf; // One byte done
scpos++;
bitbuf = 0;
}
while (scpos < 12); // Repeat for all 12 bytes
GLOBAL_INTERRUPT_ENABLE; // End critical section
captureSubQ();
#if defined(PSNEE_DEBUG_SERIAL_MONITOR)
@@ -433,62 +559,22 @@ int main() {
//This variable initialization macro is to replace (0x41) with a filter that will check that only the three most significant bits are correct. 0x001xxxxx
uint8_t isDataSector = (((scbuf[0] & 0x40) == 0x40) && (((scbuf[0] & 0x10) == 0) && ((scbuf[0] & 0x80) == 0)));
// 2. Execute selected logic through function pointer
currentLogic(isDataSector, scbuf);
if (
(isDataSector && scbuf[1] == 0x00 && scbuf[6] == 0x00) && // [0] = 41 means psx game disk. the other 2 checks are garbage protection
(scbuf[2] == 0xA0 || scbuf[2] == 0xA1 || scbuf[2] == 0xA2 || // if [2] = A0, A1, A2 ..
(scbuf[2] == 0x01 && (scbuf[3] >= 0x98 || scbuf[3] <= 0x02))) // .. or = 01 but then [3] is either > 98 or < 02
) {
hysteresis++;
}
// This CD has the wobble into CD-DA space. (started at 0x41, then went into 0x01)
else if (hysteresis > 0 && ((scbuf[0] == 0x01 || isDataSector) && (scbuf[1] == 0x00 /*|| scbuf[1] == 0x01*/) && scbuf[6] == 0x00)) {
hysteresis++;
}
// None of the above. Initial detection was noise. Decrease the counter.
else if (hysteresis > 0) {
hysteresis--;
}
// hysteresis value "optimized" using very worn but working drive on ATmega328 @ 16Mhz
// should be fine on other MCUs and speeds, as the PSX dictates SUBQ rate
if (hysteresis >= HYSTERESIS_MAX) {
// If the read head is still here after injection, resending should be quick.
// Hysteresis naturally goes to 0 otherwise (the read head moved).
hysteresis = 11;
#ifdef LED_RUN
PIN_LED_ON;
#endif
/*-------------------------------------------------------------------------------
Executes the region code injection sequence.
-------------------------------------------------------------------------------*/
PIN_DATA_OUTPUT;
PIN_DATA_CLEAR;
if (!wfck_mode) // If wfck_mode is fals (oldmode)
{
PIN_WFCK_OUTPUT;
PIN_WFCK_CLEAR;
}
_delay_ms(DELAY_BETWEEN_INJECTIONS); // HC-05 waits for a bit of silence (pin low) before it begins decoding.
// inject symbols now. 2 x 3 runs seems optimal to cover all boards
for (uint8_t scex = 0; scex < 2; scex++) {
inject_SCEX(region[scex]);
}
if (!wfck_mode) // Set WFCK pin input
{
PIN_WFCK_INPUT;
}
PIN_DATA_INPUT;
// 3. Check hysteresis and inject if necessary
performInjectionSequence();
#ifdef LED_RUN
PIN_LED_OFF;
@@ -498,6 +584,6 @@ int main() {
Debug_Inject();
#endif
}
//}
}
}

View File

@@ -124,7 +124,9 @@ const char region[3] = {'a', 'a', 'a'};
const char region[3] = {'e', 'e', 'e'};
#endif
#if defined(SCPH_100) || defined(SCPH_7500_9000) || defined(SCPH_7000) || defined(SCPH_5500) || defined(SCPH_3500_5000) || defined(SCPH_3000) || defined(SCPH_1000) || defined(SCPH_xxx3) // NTSC J | Asia.
#if defined(SCPH_100) || defined(SCPH_7500_9000) || defined(SCPH_7000) || \
defined(SCPH_5500) || defined(SCPH_3500_5000) || defined(SCPH_3000) || \
defined(SCPH_1000) || defined(SCPH_xxx3) || defined(SCPH_5903) // NTSC J | Asia.
const char region[3] = {'i', 'i', 'i'};
#endif
@@ -192,13 +194,13 @@ void Debug_Inject(){ // Confirmation of region code injection
#if !defined(SCPH_xxx3) && \
!defined(SCPH_102) && !defined(SCPH_101) && !defined(SCPH_100) && !defined(SCPH_7500_9000) && \
!defined(SCPH_7000) && !defined(SCPH_5500) && !defined(SCPH_3500_5000) && !defined(SCPH_3000) && \
!defined(SCPH_1000) && \
!defined(SCPH_1000) && !defined(SCPH_5903) &&\
!defined(SCPH_xxx1) && !defined(SCPH_xxx2)
#error "Console not selected! Please uncoment #define with SCPH model number."
#elif !defined(SCPH_xxx3) ^ \
defined(SCPH_102) ^ defined(SCPH_101) ^ defined(SCPH_100) ^ defined(SCPH_7500_9000) ^ \
defined(SCPH_7000) ^ defined(SCPH_5500) ^ defined(SCPH_3500_5000) ^ defined(SCPH_3000) ^ \
defined(SCPH_1000) ^ defined(SCPH_xxxx) ^ \
defined(SCPH_1000) ^ defined(SCPH_xxxx) ^ defined(SCPH_5903) ^ \
defined(SCPH_xxx1) ^ defined(SCPH_xxx2)
#error "May be selected only one console! Please check #define with SCPH model number."
#endif