diff --git a/PsNee.ino b/PsNee.ino index f6279ab..333c0fd 100644 --- a/PsNee.ino +++ b/PsNee.ino @@ -1,269 +1,250 @@ -// This PsNee version is meant for Arduino boards. -// 16Mhz and 8Mhz variants are supported. "Pro Micro" etc supported and recommended -// ATtinys should be able to do this as well; requires a bit of porting and testing + // This PsNee version is meant for Arduino boards. + // 16Mhz and 8Mhz variants are supported. "Pro Micro" etc supported and recommended + // ATtinys should be able to do this as well; requires a bit of porting and testing -// PAL PU-41 support isn't implemented here yet. Use PsNee v6 for them. + // PAL PU-41 support isn't implemented here yet. Use PsNee v6 for them. -// Uncomment the correct inject_SCEI(), inject_SCEA(), inject_SCEE() in loop(), depending on your console region. -// Uncomment #define PU22_MODE for PU-22, PU-23, PU-41 mainboards. + // Choose the correct inject_SCEX() for your console region. + // e = Europe / PAL + // a = North America / NTSC-U + // i = Japan / NTSC-J -//#define PU22_MODE + // Uncomment #define PU22_MODE for PU-22, PU-23, PU-41 mainboards. -#include // requires Arduino Flash library installed + #define PU22_MODE -//Pins -int data = 8; // Arduino pin 8, ATmega PB0 injects SCEX string. point 6 in old modchip Diagrams -int spidata = 10; // Arduino pin 10, ATmega PB2 "SUBQ" Mechacon pin 24 (PU-7 and early PU-8 Mechacons: pin 39) -int spiclock = 11; // Arduino pin 11, ATmega PB3 "SQCK" Mechacon pin 26 (PU-7 and early PU-8 Mechacons: pin 41) -int wfck = 12; // Arduino pin 12, ATmega PB4 point 5 in old modchip Diagrams -//Timing -int delay_between_bits = 4000; // 250 bits/s (microseconds) -int delay_between_injections = 74; // 74 original, 72 in oldcrow (milliseconds) + //Pins + const int data = 8; // Arduino pin 8, ATmega PB0 injects SCEX string. point 6 in old modchip Diagrams + const int SUBQ = 10; // Arduino pin 10, ATmega PB2 "SUBQ" Mechacon pin 24 (PU-7 and early PU-8 Mechacons: pin 39) + const int SQCK = 11; // Arduino pin 11, ATmega PB3 "SQCK" Mechacon pin 26 (PU-7 and early PU-8 Mechacons: pin 41) + //Timing + const int delay_between_bits = 4000; // 250 bits/s (microseconds) + const int delay_between_injections = 74; // 74 original, 72 in oldcrow (milliseconds) -// clock pulse timeout for sampling of the SUBQ packets: All PSX will transmit 12 packets of 8 bit / 1 byte each, once CD reading is stable. -// If the pulses take too much time, we drop the entire 12 packet stream and wait for a better chance. 10000 is a good value. -#define TIMEOUT_CLOCK 10000 + // for PU-22 mode: specific delay times for the high bit injection. It depends on the MCU speed. + #if F_CPU == 16000000 + const byte gate_high = 21; + const byte gate_low = 23; + #else + const byte gate_high = 20; + const byte gate_low = 20; + #endif -// ToDo: merge into 1 function -void inject_SCEE() -{ - //SCEE-array // Start Data Stop - FLASH_ARRAY (boolean, SCEEData, 1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0); //SCEE: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01011101 00 44 bits total - - for (byte bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1) - { - if (SCEEData[bit_counter] == 0) + // SQCK (SUBQ clock) sampling timeout: All PSX will transmit 12 packets of 8 bit / 1 byte each, once CD reading is stable. + // If the pulses take too much time, we drop the byte and wait for a better chance. 1000 is a good value. + const int sampling_timeout = 1000; + + + //SCEE: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01011101 00 + //SCEA: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01111101 00 + //SCEI: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01101101 00 + const boolean SCEEData[44] = {1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0}; //SCEE + const boolean SCEAData[44] = {1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,1,1,1,1,0,1,0,0}; //SCEA + const boolean SCEIData[44] = {1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,1,0,1,1,0,1,0,0}; //SCEI + + void inject_SCEX(char region) { - bitClear(PORTB,0); // pull data low - delayMicroseconds(delay_between_bits); - } - else - { - unsigned long now = micros(); - do { -#ifdef PU22_MODE - bool wfck_sample = bitRead(PINB, 4); - bitWrite(PORTB,0,wfck_sample); // output wfck signal on data pin -#else - bitSet(PORTB,0); // drag data pin high -#endif + const boolean *SCEXData; + switch (region){ + case 'e': SCEXData = SCEEData; break; + case 'a': SCEXData = SCEAData; break; + case 'i': SCEXData = SCEIData; break; } - while ((micros() - now) < delay_between_bits); // range: 3900us - 4200us - } - } - bitClear(PORTB,0); // pull data low - delay(delay_between_injections); -} + static const bool high_low = 1; -void inject_SCEA() -{ - //SCEE-array // Start Data Stop - FLASH_ARRAY (boolean, SCEAData, 1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,1,1,1,1,0,1,0,0); //SCEA: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01111101 00 - - for (byte bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1) - { - if (SCEAData[bit_counter] == 0) - { - bitClear(PORTB,0); // pull data low - delayMicroseconds(delay_between_bits); - } - else - { - unsigned long now = micros(); - do { -#ifdef PU22_MODE - bool wfck_sample = bitRead(PINB, 4); - bitWrite(PORTB,0,wfck_sample); // output wfck signal on data pin -#else - bitSet(PORTB,0); // drag data pin high -#endif + digitalWrite(LED_BUILTIN, HIGH); // this is Arduino Pin 13 / PB5 + + for (byte bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1) + { + if (*(SCEXData+bit_counter) == 0) + { + bitClear(PORTB,0); // pull data low + delayMicroseconds(delay_between_bits); + } + else + { + unsigned long now = micros(); + do { + #ifdef PU22_MODE + bitWrite(PORTB,0,high_low); // output wfck like signal on data pin + delayMicroseconds(gate_high); + bitWrite(PORTB,0,!high_low); + delayMicroseconds(gate_low); + #else + bitSet(PORTB,0); // drag data pin high + #endif + } + while ((micros() - now) < delay_between_bits); + //Serial.println((micros() - now)); + } } - while ((micros() - now) < delay_between_bits); // range: 3900us - 4200us - } - } - - bitClear(PORTB,0); // pull data low - delay(delay_between_injections); -} - -void inject_SCEI() -{ - //SCEI-array // Start Data Stop - FLASH_ARRAY (boolean, SCEIData, 1,0,0,1,1,0,1,0,1,0,0,1,0,0,1,1,1,1,0,1,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,1,0,1,1,0,1,0,0); //SCEI: 1 00110101 00, 1 00111101 00, 1 01011101 00, 1 01101101 00 - - for (byte bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1) - { - if (SCEIData[bit_counter] == 0) - { bitClear(PORTB,0); // pull data low - delayMicroseconds(delay_between_bits); + delay(delay_between_injections); + + digitalWrite(LED_BUILTIN, LOW); } - else + + //-------------------------------------------------- + // Setup + //-------------------------------------------------- + void setup() { - unsigned long now = micros(); - do { -#ifdef PU22_MODE - bool wfck_sample = bitRead(PINB, 4); - bitWrite(PORTB,0,wfck_sample); // output wfck signal on data pin -#else - bitSet(PORTB,0); // drag data pin high -#endif - } - while ((micros() - now) < delay_between_bits); // range: 3900us - 4200us + pinMode(data, INPUT); // Arduino pin 8, ATmega PB0 + pinMode(SUBQ, INPUT); // spi data in Arduino pin 10, ATmega PB2 + pinMode(SQCK, INPUT); // spi clock Arduino pin 11, ATmega PB3 + pinMode(LED_BUILTIN, OUTPUT); // Blink on injection / debug. + Serial.begin (1000000); + Serial.println("Start "); + + // Power saving + // Disable the ADC by setting the ADEN bit (bit 7) of the + // ADCSRA register to zero. + ADCSRA = ADCSRA & B01111111; + // Disable the analog comparator by setting the ACD bit + // (bit 7) of the ACSR register to one. + ACSR = B10000000; + // Disable digital input buffers on all analog input pins + // by setting bits 0-5 of the DIDR0 register to one. + DIDR0 = DIDR0 | B00111111; } - } - bitClear(PORTB,0); // pull data low - delay(delay_between_injections); -} + void loop() + { + static unsigned int num_resets = 0; // debug / testing + static byte scbuf [12] = { 0 }; // We will be capturing PSX "SUBQ" packets, there are 12 bytes per valid read. + static byte scpos = 0; // scbuf position -//-------------------------------------------------- -// Setup -//-------------------------------------------------- -void setup() -{ - pinMode(data, INPUT); // Arduino pin 8, ATmega PB0 - pinMode(spidata, INPUT); // spi data in Arduino pin 10, ATmega PB2 - pinMode(spiclock, INPUT); // spi clock Arduino pin 11, ATmega PB3 - - // PU-22+ mode: Input the sync signal here (point 5 in old modchip diagrams). - // The signal will be used in SCEX injections, blocking license strings from original discs. - // Leave this input unconnected for PU-7, PU-8, PU-18, PU-20 mainboards. - pinMode(wfck, INPUT); // Arduino pin 12, ATmega PB4 - - Serial.begin (115200); - Serial.println("Start "); - - // Power saving - // Disable the ADC by setting the ADEN bit (bit 7) of the - // ADCSRA register to zero. - ADCSRA = ADCSRA & B01111111; - // Disable the analog comparator by setting the ACD bit - // (bit 7) of the ACSR register to one. - ACSR = B10000000; - // Disable digital input buffers on all analog input pins - // by setting bits 0-5 of the DIDR0 register to one. - DIDR0 = DIDR0 | B00111111; -} + static unsigned int timeout_clock_counter = 0; + static byte bitbuf = 0; // SUBQ bit storage + static bool sample = 0; -void loop() -{ - static unsigned int num_resets = 0; // debug / testing - static byte scbuf [12] = { 0 }; // We will be capturing PSX "SUBQ" packets, there are 12 bytes per valid read. - static byte scpos = 0; // scbuf position + // Capture 8 bits per loop run. + // unstable clock, bootup, reset and disc changes are ignored + noInterrupts(); // start critical section - unsigned int timeout_clock_low_counter = 0; - byte bitbuf = 0; // SUBQ bit storage + // yes, a goto jump label. This is to avoid a return out of critical code with interrupts disabled. + // It prevents bad behaviour, for example running the Arduino Serial Event routine without interrupts. + // Using a function makes shared variables messier. + // We really want to have an 8 bit packet before doing anything else. + timedout: - // Try to capture 8 bits per loop run. - // unstable clock, bootup, reset and disc changes are ignored - // The console will output consistent SUBQ data eventually. - for (byte bitpos = 0; bitpos<8; bitpos++) { - do { - // waste/count cycles, reset on timeout - timeout_clock_low_counter++; - if (timeout_clock_low_counter > TIMEOUT_CLOCK){ - scpos = 0; - num_resets++; + for (byte bitpos = 0; bitpos<8; bitpos++) { + do { + // nothing, reset on timeout + timeout_clock_counter++; + if (timeout_clock_counter > sampling_timeout){ + scpos = 0; // reset SUBQ packet stream + timeout_clock_counter = 0; + num_resets++; + bitpos = 0; + goto timedout; + } + } + while (bitRead(PINB, 3) == 1); // wait for clock to go low + + #if F_CPU == 16000000 // wait a few cpu cycles > better readings in tests + __asm__("nop\n\t"); __asm__("nop\n\t"); __asm__("nop\n\t"); + #endif + + // sample the bit. + sample = bitRead(PINB, 2); + bitbuf |= sample << bitpos; + + do { + // nothing + } while ((bitRead(PINB, 3)) == 0); // Note: Even if sampling is bad, it will not get stuck here. There will be clock pulses eventually. + + timeout_clock_counter = 0; // This bit came through fine. + } + + scbuf[scpos] = bitbuf; + scpos++; + bitbuf = 0; + + if (scpos < 12){ return; } + + interrupts(); // end critical section + + // logging. + if (!(scbuf[0] == 0 && scbuf[1] == 0 && scbuf[2] == 0 && scbuf[3] == 0)){ // a bad sector read is all 0 except for the CRC fields. Don't log it. + for (int i = 0; i<12;i++) { + Serial.print(scbuf[i], HEX); + Serial.print(" "); + } + Serial.print(" resets: "); + Serial.println(num_resets); + } + + num_resets = 0; + scpos = 0; + + // check if this is the wobble area + // 3 bytes would be enough to recognize it. The extra checks just ensure this isn't a garbage reading. + if ( (scbuf[0] == 0x41 && scbuf[1] == 0x00 && scbuf[6] == 0x00) && // 0x41 = psx game, beginning of the disc, sanity check + ((scbuf[2] == 0xA0 || scbuf[2] == 0xA1 || scbuf[2] == 0xA2) || + (scbuf[2] > 0x00 && scbuf[2] <= 0x99)) ){ // lead in / wobble area + + Serial.println("INJECT!"); + + pinMode(data, OUTPUT); // prepare for SCEX injection + + bitClear(PORTB,0); // pull data low + + // HC-05 is waiting for a bit of silence (pin Low) before it begins decoding. + // minimum 66ms required on SCPH-7000 + // minimum 79ms required on SCPH-7502 + delay(82); + + for (int loop_counter = 0; loop_counter < 2; loop_counter++) + { + inject_SCEX('e'); // e = SCEE, a = SCEA, i = SCEI + } + + pinMode(data, INPUT); // high-z the data line, we're done + } + // keep catching SUBQ packets forever } - while (bitRead(PINB, 3)); // wait for clock to go low - // waste a few cpu cycles > better readings in tests - __asm__("nop\n\t"); - __asm__("nop\n\t"); + // Old readme! - // sample the bit. - bool sample = bitRead(PINB, 2); - bitbuf |= sample << bitpos; + //UPDATED AT MAY 14 2016, CODED BY THE FRIENDLY FRIETMAN :-) - do { - // waste cycles - } while (!(bitRead(PINB, 3))); // Note: Even if sampling is bad, it will not get stuck here. There will be clock pulses eventually. - - timeout_clock_low_counter = 0; // This bit came through fine. - } - - scbuf[scpos] = bitbuf; - scpos++; - - if (scpos == 12){ - // end of time critical section. We now have all 12 subchannel packets. It will be 13.3ms until the next ones. - // print out some debug stats if a serial terminal is connected - for (int i = 0; i<12;i++) { - Serial.print(scbuf[i], HEX); - Serial.print(" "); - } - Serial.print(" resets: "); - Serial.println(num_resets); - num_resets = 0; - scpos = 0; - } - else return; - - // check if this is the wobble area - // 3 bytes would be enough to recognize it. The extra checks just ensure this isn't a garbage reading. - if ( scbuf[0] == 0x41 && scbuf[1] == 0x00 && scbuf[6] == 0x00 && // 0x41 = psx game, beginning of the disc, sanity check - (scbuf[2] == 0xA0 || scbuf[2] == 0xA1 || scbuf[2] == 0xA2) ){ // lead in / wobble area is marked by 0xA0, 0xA1, 0xA2 - - Serial.println("INJECT!"); - - pinMode(data, OUTPUT); // prepare for SCEX injection - - bitClear(PORTB,0); // pull data low - delay(74); // HC-05 is waiting for a bit of silence (pin Low) before it begins decoding. (66 min required on 7000 series) - - for (int loop_counter = 0; loop_counter < 2; loop_counter++) // 1 "loop" would be sufficient from my limited testing - { - inject_SCEI(); - //inject_SCEA(); - //inject_SCEE(); - } - - pinMode(data, INPUT); // high-z the data line, we're done - } - -// keep catching SUBQ packets forever -} + //PsNee, an open source stealth modchip for the Sony Playstation 1, usable on + //all platforms supported by Arduino, preferably ATTiny. Finally something modern! -// Old readme! + //-------------------------------------------------- + // TL;DR + //-------------------------------------------------- + //Look for the "Arduino selection!" section and verify the target platform. Hook up your target device and hit Upload! + //BEWARE: when using ATTiny45, make sure the proper device is selected (Extra=>Board=>ATTiny45 (internal 8MHz clock)) + //and the proper fuses are burnt (use Extra=>Burn bootloader for this), otherwise PsNee will malfunction. A tutorial on + //uploading Arduino code via an Arduino Uno to an ATTiny device: http://highlowtech.org/?p=1695 + //Look at the pinout for your device and hook PsNee up to the points on your Playstation. -//UPDATED AT MAY 14 2016, CODED BY THE FRIENDLY FRIETMAN :-) + //The modchip injects after about 1500ms the text strings SCEE SCEA SCEI on the motherboard point and stops + //with this after about 25 seconds. Because all the possible valid region options are outputted on the + //motherboard the Playstation gets a bit confused and simply accepts the inserted disc as authentic; after all, + //one of the codes was the same as that of the Playstation hardware... -//PsNee, an open source stealth modchip for the Sony Playstation 1, usable on -//all platforms supported by Arduino, preferably ATTiny. Finally something modern! + //-------------------------------------------------- + // New in this version! + //-------------------------------------------------- + //A lot! + // - The PAL SCPH-102 NTSC BIOS-patch works flawlessly! For speed reasons this is implemented in bare + // AVR C. It is functionally identical to the OneChip modchip, this modchip firmware was disassembled, + // documented (available on request, but written in Dutch...) and analyzed with a logic analyzer to + // make sure PsNee works just as well. + // - The code now is segmented in functions which make the program a lot more maintable and readable + // - Timing is perfected, all discs (both backups and originals of PAL and NTSC games) now work in the + // PAL SCPH-102 test machine + // - It was found out that the gate signal doesn't havbe to be hooked up to a PAL SCPH-102 Playstation + // to circumvent the copy protection. This is not tested on other Playstation models so the signal still + // is available + // - The /xlat signal is no longer required to time the PAL SCPH-102 NTSC BIOS-patch + // - Only AVR PORTB is used for compatibility reasons (almost all the AVR chips available have PORTB) -//-------------------------------------------------- -// TL;DR -//-------------------------------------------------- -//Look for the "Arduino selection!" section and verify the target platform. Hook up your target device and hit Upload! -//BEWARE: when using ATTiny45, make sure the proper device is selected (Extra=>Board=>ATTiny45 (internal 8MHz clock)) -//and the proper fuses are burnt (use Extra=>Burn bootloader for this), otherwise PsNee will malfunction. A tutorial on -//uploading Arduino code via an Arduino Uno to an ATTiny device: http://highlowtech.org/?p=1695 -//Look at the pinout for your device and hook PsNee up to the points on your Playstation. - -//The modchip injects after about 1500ms the text strings SCEE SCEA SCEI on the motherboard point and stops -//with this after about 25 seconds. Because all the possible valid region options are outputted on the -//motherboard the Playstation gets a bit confused and simply accepts the inserted disc as authentic; after all, -//one of the codes was the same as that of the Playstation hardware... - -//-------------------------------------------------- -// New in this version! -//-------------------------------------------------- -//A lot! -// - The PAL SCPH-102 NTSC BIOS-patch works flawlessly! For speed reasons this is implemented in bare -// AVR C. It is functionally identical to the OneChip modchip, this modchip firmware was disassembled, -// documented (available on request, but written in Dutch...) and analyzed with a logic analyzer to -// make sure PsNee works just as well. -// - The code now is segmented in functions which make the program a lot more maintable and readable -// - Timing is perfected, all discs (both backups and originals of PAL and NTSC games) now work in the -// PAL SCPH-102 test machine -// - It was found out that the gate signal doesn't havbe to be hooked up to a PAL SCPH-102 Playstation -// to circumvent the copy protection. This is not tested on other Playstation models so the signal still -// is available -// - The /xlat signal is no longer required to time the PAL SCPH-102 NTSC BIOS-patch -// - Only AVR PORTB is used for compatibility reasons (almost all the AVR chips available have PORTB)