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:
288
PSNee/PSNee.ino
288
PSNee/PSNee.ino
@@ -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
|
||||
|
||||
}
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user