diff --git a/PsNee.ino b/PsNee.ino index 5951bce..c4cd2d0 100644 --- a/PsNee.ino +++ b/PsNee.ino @@ -17,7 +17,7 @@ // P P P P P P // P P P P P P // P P P P P P -// PPPPPPPPPPPP P P PPPPPPPPPPP PPPPPPPPPPP VERSION 6! +// PPPPPPPPPPPP P P PPPPPPPPPPP PPPPPPPPPPP VERSION 7! //Update 15th of May 2017 //PSNee now watches the subchannel data and looks at the position information contained within. @@ -27,127 +27,49 @@ //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). -//Note: Once this is installed in a PSX, mind the Pin13 LED that many Arduino boards have. Do not upload new sketches while the PSX is on! -//(If the PSX is on while uploading a sketch (making the LED blink), a voltage will be fed back into the SCLK pin on the HC-05 in the PSX. -//This didn't break my PSX in testing but it does stun the chip and halt CD operation. I'm thinking of a better method to do this but for now I need Arduino pin13..) -//Very much recommended to install a 3.3V chip! //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. - - -//-------------------------------------------------- -// General info! -//-------------------------------------------------- -//PLAYSTATION 1 SECURITY - HOW IT DOES IT'S THING: -//Sony didn't really go through great lenghts to protect it's precious Playstation -//from running unauthorised software; the main security is based on a simple ASCII -//string of text that is read from a part of an original Playstation disc that cannot -//be reproduced by an ordinary PC CD burner. -//As most of you will know, a CD is basically a very long rolled up (carrier) string in which very -//little pits and ehm... little not-pits are embedded that represent the data stored on the disc. -//The nifty Sony engineers did not use the pits and stuff to store the security checks for -//Playstation discs but went crazy with the rolled up carrier string. In an ordinary CD, the -//string is rolled up so that the spacing between the tracks is as equal as possible. If that -//is not the case, the laser itself needs to move a bit to keep track of the track and -//reliably read the data off the disc. -//If you wonder how the laser knows when it follows the track optimally: four photodiodes, light -//intensity measurement, difference measurements, servo. There. -//To the point: the Sony engineers decidedly "fumbled up" the track of sector 4 on a Playstation -//disc (the track was modulated in nerd-speak) so that the error correction circuit outputs a -//recognisable signal, as the laser needs to be corrected to follow the track optimally. -//This output signal actually is a 250bps serial bitstream (with 1 start bit and 2 stop bits) which -//in plain ASCII says SCEA (Sony Computer Entertainment of America), SCEE (Sony Computer Entertainment -//of Europe) or SCEI (Sony Computer Entertainment of Japan), depending on the region of the disc inserted. -//The security thus functions not only as copy protection, but also as region protection. -//The text string from the disc is compared with the text string that is embedded in the Playstation -//hardware. When these text strings are the same, the disc is interpreted to be authentic and from -//the correct region. Bingo! - -//HOW THE MODCHIP TRICKS THE PLAYSTATION: -//The modchip isn't all that of a complicated device: clever reverse engineers found the point on the -//Playstation motherboard that carried the text string from the disc and found a way to temporarily block -//this signal (by grounding an input of an op-amp buffer) to be able to inject the signal from the modchip -//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... -//Early modchips applied the text strings as long as power was applied to them, whereby later Playstation -//software could detect whether a modchip was installed. This is circumvented in this application by idling the -//modchip after about 25 seconds. The text strings are only tranmitted again when the CD lid is opened and closed -//again, to enable playing multi-disc games. This is also called a stealth modchip in marketing-speak. - - -//-------------------------------------------------- -// 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) - +// todo //-------------------------------------------------- // Pinouts! //-------------------------------------------------- //FOR ARDUINO UNO (WITH ATMEGA328): -// - Arduino pin 8 = data = ATMega pin 14 -// - Arduino pin 9 = gate = ATMega pin 15 -// - Arduino pin 10 = lid = ATMega pin 16 -// - Arduino pin 11 = biosA18 = ATMega pin 17 -// - Arduino pin 12 = biosD2 = ATMega pin 18 +// - 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: -// - Arduino pin 0 = data = ATTiny pin 5 -// - Arduino pin 1 = gate = ATTiny pin 6 -// - Arduino pin 2 = lid = ATTiny pin 7 -// - Arduino pin 3 = biosA18 = ATTiny pin 2 -// - Arduino pin 4 = biosD2 = ATTiny pin 3 -//-------------------------------------------------- -// Includes! -//-------------------------------------------------- #include -#include "SPI.h" -byte buf [12]; // We will be capturing PSX "SUBQ" packets, and there are 12 of them per sector. -int pos; // buffer position -int wobbleCounter; // if treshold reached, output SCEX string -//#define DEBUG +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 with SPI additions! -//#define ATTINY //Make that "#define ARDUINO_UNO" if you want to compile for Arduino Uno instead of ATTiny25/45/85 -#define ARDUINO_UNO //Make that "#define ARDUINO_UNO" if you want to compile for Arduino Uno instead of ATTiny25/45/85 +// 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 lid = 10; //The pin that gets connected to the internal CD lid signal; active high -int biosA18 = 11; //Address 18; Only used in SCPH-102 PAL mode // ToDo: rearrange. This is the MOSI pin for SPI. +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 @@ -166,17 +88,16 @@ int delay_between_bits = 4; int delay_between_injections = 68; #endif -//-------------------------------------------------- -// Global variables! -//-------------------------------------------------- -//None, just like it should be! // Sorry, couldn't resist -//-------------------------------------------------- -// Seperate functions! -//-------------------------------------------------- -void NTSC_fix() -{ - //needs rework, new pin assigments +#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; @@ -291,7 +212,7 @@ void inject_multiple_times(int number_of_injection_cycles) void inject_playstation() { - //NTSC_fix(); //needs rework, new pin assigments + //NTSC_fix(); delay(6900); pinMode(data, OUTPUT); @@ -310,98 +231,138 @@ void inject_playstation() pinMode(data, INPUT); } -//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //-------------------------------------------------- -// Setup function - execution starts here! +// Setup //-------------------------------------------------- void setup() { // Arduino docs say all INPUT pins are high impedence by default. Let's be explicit! - pinMode(data, INPUT); - pinMode(gate, INPUT); + 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 - // We just want passive sampling of the SPI data. Never output anything nor pull lines high / low. - pinMode(MOSI, INPUT); // Arduino pin 11 - pinMode(SCK, INPUT); // Arduino pin 13 - pinMode(MISO, INPUT); // just for safety - pinMode(SS, INPUT); // just for safety + scpos = 0; + bitpos = 0; + bitbuf = 0; - SPCR = 0b01101100; // LSBFIRST, SPI_MODE3, SPI_CLOCK_DIV4, SPI enabled, Slave mode, CPOL = 1, CPHA = 1 + Serial.begin (115200); + Serial.print("Start "); - //inject_playstation(); // not required when watching subchannel data -#ifdef DEBUG - Serial.begin (115200); // debugging - Serial.print("SPCR setup to "); - Serial.println(SPCR, HEX); +#ifdef ARDUINO_UNO + // 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; #endif - pos = 0; - wobbleCounter = 0; } -//---------------------------------------------------------------- -// Loop function - executes after the initial injection cycle -//---------------------------------------------------------------- +#ifdef ARDUINO_UNO + #define SUBQ_SDI_BITN 2 + #define SUBQ_SDI_BIT (1< TIMEOUT_CLOCK_LOW){ + bitbuf = 0; + bitpos = 0; + scpos = 0; + return; + } + } + while ((PIND & SUBQ_SDI_BIT)); // wait for clock to go low + timeout_clock_low_counter = 0; + + // clock is stable. sample the bit. + bool sample = (PINB & SUBQ_SDI_BIT); + 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; + } - byte c = SPDR; - if (c == 0x41) { - buf[0] = c; // Is this a psx data cd? if so, align buffer start to this! - pos = 1; + // 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 (pos == 0) return; // 0x41 byte not yet found > loop - buf[pos] = c; - pos++; - - if(pos == 12) // 12 "packets" make up the SUBQ for one sector. pos is 12 because it got incremented once more. - { - pos = 0; -#ifdef DEBUG - for (int i = 0; i<12;i++){ // print the buffer - Serial.print(buf[i], HEX); + if (scpos == 0){ // catch stray packets and realign. + bitpos = 0; + bitbuf = 0; + return; + } + + 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. + for (int i = 0; i<12;i++) { + Serial.print(scbuf[i], HEX); Serial.print(" "); } Serial.println(""); -#endif - // check if this is the wobble area - if (buf[1] == 0x00 && ( buf[2] == 0xA2 || buf[2] == 0xA1 || buf[2] == 0xA0 ) ) { // 0x41, 0x00, 0xA0 etc appears to be the wobble area - //looks like it! - wobbleCounter++; - } - else if (wobbleCounter > 0){ - wobbleCounter--; - } } + else return; + + scpos = 0; - if (wobbleCounter >= 3) { - // Read head is in wobble area. Inject SCEX. -#ifdef DEBUG + // 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 + (scbuf[2] == 0xA0 || scbuf[2] == 0xA1 || scbuf[2] == 0xA2) ){ // lead in / wobble area is marked by 0xA0, 0xA1, 0xA2 + Serial.println("Inject!"); -#endif + pinMode(gate, OUTPUT); digitalWrite(gate, 0); - - // loop_counter is a tweak point. More than 6 can trip antimod detection. 2 works. 1 is outputting once per sector in theory, doesn't work. + + // 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) { inject_SCEI(); //inject_SCEA(); //inject_SCEE(); } - + pinMode(gate, INPUT); pinMode(data, INPUT); - - wobbleCounter = 0; } }