From f2d6083e2d9fcd3a68568a24100dd5cdff1f74fb Mon Sep 17 00:00:00 2001 From: kalymos Date: Sat, 3 Jun 2017 11:58:52 +0200 Subject: [PATCH] Update PsNee.ino --- PsNee.ino | 407 +++++++++++++++++++++--------------------------------- 1 file changed, 154 insertions(+), 253 deletions(-) diff --git a/PsNee.ino b/PsNee.ino index c4cd2d0..f6279ab 100644 --- a/PsNee.ino +++ b/PsNee.ino @@ -1,146 +1,58 @@ -// PPPPPPPPPPPPPPPP P P -// P P PP P -// P P P P P -// P P P P P -// P P P P P -// P P P P P -// P P P P P -// PPPPPPPPPPPPPPPP PPPPPPPPPPP P P P PPPPPPPPPPP PPPPPPPPPPP -// P P P P P P P -// P P P P P P P -// P P P P P P P -// P P P P P P P -// P PPPPPPPPPPPPPP P PP PPPPPPP PPPPPPP -// P P P P P P -// P P P P P P -// P P P P P P -// P P P P P P -// P P P P P P -// P P P P P P -// PPPPPPPPPPPP P P PPPPPPPPPPP PPPPPPPPPPP VERSION 7! +// 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 -//Update 15th of May 2017 -//PSNee now watches the subchannel data and looks at the position information contained within. -//This allows deterministic SCEX injections. It knows (almost) exactly when to inject the SCEX string. -//Therefore it is now a stealth modchip :) -//Required connections: GND, VCC, data, gate, SQCL, SUBQ -//No more need to watch the PSX reset or lid open signals or any other typical modchip points (like "sync") -//WIP! Only tested on PU-18 board. Should work fine on PU-7, PU-8, PU-18 and PU-20. -//Will need adaption for PU-22 to PU-41 (SCPH-750x, 900x and PSOne). +// PAL PU-41 support isn't implemented here yet. Use PsNee v6 for them. -//UPDATED AT MAY 14 2016, CODED BY THE FRIENDLY FRIETMAN :-) +// 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. -//PsNee, an open source stealth modchip for the Sony Playstation 1, usable on -//all platforms supported by Arduino, preferably ATTiny. Finally something modern! +//#define PU22_MODE -// - Only AVR PORTB is used for compatibility reasons (almost all the AVR chips available have PORTB) -// todo +#include // requires Arduino Flash library installed -//-------------------------------------------------- -// Pinouts! -//-------------------------------------------------- -//FOR ARDUINO UNO (WITH ATMEGA328): -// - Arduino pin 2 = spiclock = ATMega pin 4 -// - Arduino pin 8 = data = ATMega pin 14 -// - Arduino pin 9 = gate = ATMega pin 15 -// - Arduino pin 10 = spidata = ATMega pin 16 -// - Arduino pin 11 = biosA18 = ATMega pin 17 -// - Arduino pin 12 = biosD2 = ATMega pin 18 - -//FOR ATTINY25/45/85: - -#include - -byte scbuf [12]; // We will be capturing PSX "SUBQ" packets, and there are 12 of them per sector. -byte scpos; // buffer position -byte bitbuf; // SUBQ bits get stored in here as they fly in -byte bitpos; // bitbuf index - -//-------------------------------------------------- -// Arduino selection! -//-------------------------------------------------- -// ATTINY untested yet! -//#define ATTINY -#define ARDUINO_UNO - -#ifdef ARDUINO_UNO //Pins -int spiclock = 2; // PD2 on ATmega328 -int spidata = 10; // PB2 on ATmega328 -int data = 8; //The pin that outputs the SCEE SCEA SCEI string -int gate = 9; //The pin that outputs the SCEE SCEA SCEI string -int biosA18 = 11; //Address 18; Only used in SCPH-102 PAL mode -int biosD2 = 12; //Data 2; Only used in SCPH-102 PAL mode -int delay_ntsc = 2350; -int delay_between_bits = 4; // 250 bits/s -int delay_between_injections = 74; // delay for this time while keeping data line pulled low -#endif +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) -#ifdef ATTINY -//Pins -int data = 0; //The pin that outputs the SCEE SCEA SCEI string -int gate = 1; -int lid = 2; //The pin that gets connected to the internal CD lid signal; active high -int biosA18 = 3; //Only used in SCPH-102 PAL mode -int biosD2 = 4; //Only used in SCPH-102 PAL mode -int delay_ntsc = 2400; -int delay_between_bits = 4; -int delay_between_injections = 68; -#endif - -#if F_CPU == 8000000 - #define TIMEOUT_CLOCK_LOW 24 // minimum 18 - #define TIMEOUT_CLOCK_HIGH 6 // minimum 3 -#elif F_CPU == 16000000 - #define TIMEOUT_CLOCK_LOW 72 // minimum 54 - #define TIMEOUT_CLOCK_HIGH 14 // minimum 7 -#endif - -void NTSC_fix() //needs rework, new pin assigments -{ - - //Make sure all pins are inputs - DDRB = 0x00; - - //Wait until just before the pulse on BIOS A18 arrives - delay(delay_ntsc); - - //...And wait here until it actually happened - while(!(PINB & B00001000)) - { - ; //Wait - } - delayMicroseconds(12); - PORTB = B00000000; - DDRB = B00010000; - delayMicroseconds(5); - DDRB = 0x00; -} +// 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 +// 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 - int bit_counter; - - for (bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1) + for (byte bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1) { if (SCEEData[bit_counter] == 0) - { - pinMode(data, OUTPUT); - digitalWrite(data, 0); - delay(delay_between_bits); + { + bitClear(PORTB,0); // pull data low + delayMicroseconds(delay_between_bits); } else { - pinMode(data, INPUT); //We make the data pin high-impedance to let the pull-up of the Playstation motherboard make a 1 - delay(delay_between_bits); + 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, OUTPUT); - digitalWrite(data, 0); + bitClear(PORTB,0); // pull data low delay(delay_between_injections); } @@ -149,25 +61,29 @@ 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 - int bit_counter; - - for (bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1) + for (byte bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1) { if (SCEAData[bit_counter] == 0) - { - pinMode(data, OUTPUT); - digitalWrite(data, 0); - delay(delay_between_bits); + { + bitClear(PORTB,0); // pull data low + delayMicroseconds(delay_between_bits); } else { - pinMode(data, INPUT); //We make the data pin high-impedance to let the pull-up of the Playstation motherboard make a 1 - delay(delay_between_bits); + 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, OUTPUT); - digitalWrite(data, 0); + bitClear(PORTB,0); // pull data low delay(delay_between_injections); } @@ -176,82 +92,50 @@ 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 - int bit_counter; - - for (bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1) + for (byte bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1) { if (SCEIData[bit_counter] == 0) - { - pinMode(data, OUTPUT); - digitalWrite(data, 0); - delay(delay_between_bits); + { + bitClear(PORTB,0); // pull data low + delayMicroseconds(delay_between_bits); } else { - pinMode(data, INPUT); //We make the data pin high-impedance to let the pull-up of the Playstation motherboard make a 1 - delay(delay_between_bits); + 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, OUTPUT); - digitalWrite(data, 0); + bitClear(PORTB,0); // pull data low delay(delay_between_injections); } -void inject_multiple_times(int number_of_injection_cycles) -{ - int cycle_counter; - - for(cycle_counter = 0; cycle_counter < number_of_injection_cycles; cycle_counter = cycle_counter + 1) - { - inject_SCEE(); - inject_SCEA(); - inject_SCEI(); - } -} - -void inject_playstation() -{ - //NTSC_fix(); - - delay(6900); - pinMode(data, OUTPUT); - pinMode(gate, OUTPUT); - digitalWrite(data, 0); - digitalWrite(gate, 0); - - for (int loop_counter = 0; loop_counter < 235; loop_counter = loop_counter + 1) - { - inject_SCEI(); - //inject_SCEA(); - //inject_SCEE(); - } - - pinMode(gate, INPUT); - pinMode(data, INPUT); -} - //-------------------------------------------------- // Setup //-------------------------------------------------- void setup() { - // Arduino docs say all INPUT pins are high impedence by default. Let's be explicit! - pinMode(data, INPUT); // Arduino pin 8 - pinMode(gate, INPUT); // Arduino pin 9 - pinMode(spidata, INPUT); // spi data in Arduino pin 10 - pinMode(spiclock, INPUT); // spi clock Arduino pin 2 - - scpos = 0; - bitpos = 0; - bitbuf = 0; + 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.print("Start "); + Serial.println("Start "); -#ifdef ARDUINO_UNO // Power saving - // Disable the ADC by setting the ADEN bit (bit 7) of the // ADCSRA register to zero. ADCSRA = ADCSRA & B01111111; @@ -261,108 +145,125 @@ void setup() // Disable digital input buffers on all analog input pins // by setting bits 0-5 of the DIDR0 register to one. DIDR0 = DIDR0 | B00111111; -#endif } -#ifdef ARDUINO_UNO - #define SUBQ_SDI_BITN 2 - #define SUBQ_SDI_BIT (1< TIMEOUT_CLOCK_LOW){ - bitbuf = 0; - bitpos = 0; + if (timeout_clock_low_counter > TIMEOUT_CLOCK){ scpos = 0; + num_resets++; return; } } - while ((PIND & SUBQ_SDI_BIT)); // wait for clock to go low - timeout_clock_low_counter = 0; + while (bitRead(PINB, 3)); // wait for clock to go low - // clock is stable. sample the bit. - bool sample = (PINB & SUBQ_SDI_BIT); + // waste a few cpu cycles > better readings in tests + __asm__("nop\n\t"); + __asm__("nop\n\t"); + + // sample the bit. + bool sample = bitRead(PINB, 2); bitbuf |= sample << bitpos; - bitpos++; do { - // waste/count cycles. abort and reset if the clock times out. - timeout_clock_high_counter++; - if (timeout_clock_high_counter > TIMEOUT_CLOCK_HIGH){ - bitbuf = 0; - bitpos = 0; - scpos = 0; - return; - } - } - while (!(PIND & SUBQ_SDI_BIT)); // wait for clock to go high - timeout_clock_high_counter = 0; - } - - // Align the 12 byte buffer to 0x41, which is the start bit for a PSX game disc. - // This serves following purposes: - // - removes need for a start condition signal / extra wire - // - always "rights the ship", for those start conditions where the clock is unstable (ie: autofocus, etc) - // - it's pointless to unlock a music cd. - if (bitbuf == 0x41){ - scbuf[0] = bitbuf; - scpos = 1; // we are aligned. From now on catch the remaining 11 bytes for the full SUBQ readout for this sector. - bitpos = 0; - bitbuf = 0; - return; - } - - if (scpos == 0){ // catch stray packets and realign. - bitpos = 0; - bitbuf = 0; - return; + // 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; - bitpos = 0; - bitbuf = 0; 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.println(""); + Serial.print(" resets: "); + Serial.println(num_resets); + num_resets = 0; + scpos = 0; } else return; - - scpos = 0; // check if this is the wobble area - if ( scbuf[0] == 0x41 && scbuf[1] == 0x00 && scbuf[6] == 0x00 && // 0x41 = psx game, the 0x00 checks make sure (reasonably) that this is a valid 12 packet stream + // 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!"); - Serial.println("Inject!"); + pinMode(data, OUTPUT); // prepare for SCEX injection - pinMode(gate, OUTPUT); - digitalWrite(gate, 0); - - // loop_counter is a tweak point. More than 6 can trip antimod detection. 2 works. 1 would require different timing. - for (int loop_counter = 0; loop_counter < 3; loop_counter = loop_counter + 1) + 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(gate, INPUT); - pinMode(data, INPUT); + pinMode(data, INPUT); // high-z the data line, we're done } + +// keep catching SUBQ packets forever } + + +// Old readme! + +//UPDATED AT MAY 14 2016, CODED BY THE FRIENDLY FRIETMAN :-) + +//PsNee, an open source stealth modchip for the Sony Playstation 1, usable on +//all platforms supported by Arduino, preferably ATTiny. Finally something modern! + + +//-------------------------------------------------- +// 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)