1
0
mirror of https://github.com/kalymos/PsNee.git synced 2026-03-02 01:50:20 +00:00

Update PsNee.ino

This commit is contained in:
kalymos
2017-06-03 11:58:52 +02:00
committed by GitHub
parent 05519eba34
commit f2d6083e2d

407
PsNee.ino
View File

@@ -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 <Flash.h> // 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 <Flash.h>
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<<SUBQ_SDI_BITN)
#else
// todo: attiny
#endif
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
unsigned int timeout_clock_low_counter = 0;
unsigned int timeout_clock_high_counter = 0;
for (int i = 0; i<8; i++) {
byte bitbuf = 0; // SUBQ bit storage
// 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. abort and reset if the clock times out.
// waste/count cycles, reset on timeout
timeout_clock_low_counter++;
if (timeout_clock_low_counter > 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)