mirror of
https://github.com/kalymos/PsNee.git
synced 2026-03-03 18:26:10 +00:00
Update PsNee.ino
This commit is contained in:
293
PsNee.ino
293
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 <Flash.h>
|
||||
#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<<SUBQ_SDI_BITN)
|
||||
#else
|
||||
// todo: attiny
|
||||
#endif
|
||||
|
||||
void loop()
|
||||
{
|
||||
// Hardware SPI interface requires a constant reset (for some reason. Find out why?)
|
||||
// This toggles the SPI enable bit
|
||||
SPCR = SPCR & 0b10111111;
|
||||
SPCR = SPCR | 0b01000000;
|
||||
// When not doing this regularly, the captured data gets misaligned with the PSX SCLK. Weird.
|
||||
unsigned int timeout_clock_low_counter = 0;
|
||||
unsigned int timeout_clock_high_counter = 0;
|
||||
|
||||
while(!(SPSR & (1<<SPIF))); // wait for a byte
|
||||
for (int i = 0; i<8; i++) {
|
||||
do {
|
||||
// waste/count cycles. abort and reset if the clock times out.
|
||||
timeout_clock_low_counter++;
|
||||
if (timeout_clock_low_counter > 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user