1
0
mirror of https://github.com/kalymos/PsNee.git synced 2026-04-18 16:57:44 +00:00

Merge pull request #8 from kalymos/psxdev.net

Psxdev.net
This commit is contained in:
kalymos
2017-07-26 15:56:10 +02:00
committed by GitHub
3 changed files with 548 additions and 351 deletions

727
PsNee.ino
View File

@@ -1,328 +1,413 @@
//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// 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 6!
//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)
//--------------------------------------------------
// 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
//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>
//--------------------------------------------------
// Arduino selection!
//--------------------------------------------------
#define ARDUINO_UNO //Make that "#define ARDUINO_UNO" if you want to compile for Arduino Uno instead of ATTiny25/45/85
#ifdef ARDUINO_UNO
//Pins
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; //Only used in SCPH-102 PAL mode
int biosD2 = 12; //Only used in SCPH-102 PAL mode
int delay_ntsc = 2350;
int delay_between_bits = 4;
int delay_between_injections = 74;
#endif
#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
//--------------------------------------------------
// Global variables!
//--------------------------------------------------
//None, just like it should be!
//--------------------------------------------------
// Seperate functions!
//--------------------------------------------------
void NTSC_fix()
{
//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;
}
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)
{
if (SCEEData[bit_counter] == 0)
{
pinMode(data, OUTPUT);
digitalWrite(data, 0);
delay(delay_between_bits);
}
else
// PsNee / psxdev.net version
// For Arduino and ATtiny
//
// Quick start: Select your hardware via the #defines, compile + upload the code, install in PSX.
// There are some pictures in the development thread ( http://www.psxdev.net/forum/viewtopic.php?f=47&t=1262&start=120 )
//
// Arduinos:
// - Arduino Pro Mini @8Mhz and @16Mhz (supported, tested)
// - Arduino Uno @8Mhz and @16Mhz (supported, tested)
// - Arduino Pro Micro has a different pin assignment and needs some easy porting. (ToDo)
// - Use #define ARDUINO_BOARD
// ATtiny:
// - ATtiny85: Should work the same as ATtiny45 (supported, untested)
// - ATtiny45: LFUSE 0xE2 HFUSE 0xDF > internal oscillator, full 8Mhz speed (supported, tested)
// - ATtiny25: Should work the same as ATtiny45 but doesn't have enough Flash nor RAM for PSNEEDEBUG (supported, untested)
// - Use #define ATTINY_X5
//
// Some extra libraries might be required, depending on the board / chip used.
// PAL PM-41 is supported with #define APPLY_PSONE_PAL_BIOS_PATCH
//For now the BIOS_PATCH it only supports Arduino boards (ATmega chips).
//Also, the Arduino must either be powered on first or have no bootloader present (flashed using SPI) since I expect a signal ~1 second after power on.
// This code defaults to multi-region, meaning it will unlock PAL, NTSC-U and NTSC-J machines.
// You can optimize boot times for your console further. See "// inject symbols now" in the main loop.
//+-------------------------------------------------------------------------------------------+
//| Choose your hardware! |
//+-------------------------------------------------------------------------------------------+
// 2 main branches available:
// - ATmega based > easy to use, fast and nice features for development
// - ATtiny based > less features, internal clock has 10% variation
//#define ARDUINO_BOARD
//#define ATTINY_X5
//#define APPLY_PSONE_PAL_BIOS_PATCH
//#define PSNEEDEBUG
#include <avr/pgmspace.h>
#if defined(ARDUINO_BOARD)
// board pins (Do not change. Changing pins requires adjustments to MCU I/O definitions)
#if defined(APPLY_PSONE_PAL_BIOS_PATCH)
#define BIOS_A18 4 // connect to PSOne BIOS A18 (pin 31 on that chip)
#define BIOS_D2 5 // connect to PSOne BIOS D2 (pin 15 on that chip)
#endif
#define sqck 6 // connect to PSX HC-05 SQCK pin
#define subq 7 // connect to PSX HC-05 SUBQ pin
#define data 8 // connect to point 6 in old modchip diagrams
#define gate_wfck 9 // connect to point 5 in old modchip diagrams
// MCU I/O definitions
#define SUBQPORT PIND // MCU port for the 2 SUBQ sampling inputs
#define SQCKBIT 6 // PD6 "SQCK" < Mechacon pin 26 (PU-7 and early PU-8 Mechacons: pin 41)
#define SUBQBIT 7 // PD7 "SUBQ" < Mechacon pin 24 (PU-7 and early PU-8 Mechacons: pin 39)
#define GATEWFCKPORT PINB // MCU port for the gate input (used for WFCK)
#define DATAPORT PORTB // MCU port for the gate input (used for WFCK)
#define GATEWFCKBIT 1 // PB1
#define DATABIT 0 // PB0
#if defined(APPLY_PSONE_PAL_BIOS_PATCH)
#define BIOSPATCHPORTIN PIND
#define BIOSPATCHPORTOUT PORTD
#define BIOSPATCHDDR DDRD
#define BIOS_A18_BIT 4
#define BIOS_D2_BIT 5
#endif
#elif defined(ATTINY_X5) // ATtiny 25/45/85
// extras
#define USINGSOFTWARESERIAL
// board pins (Do not change. Changing pins requires adjustments to MCU I/O definitions)
#define sqck 0
#define subq 1
#define data 2
#define gate_wfck 4
#define debugtx 3
// MCU I/O definitions
#define SUBQPORT PINB
#define SQCKBIT 0
#define SUBQBIT 1
#define GATEWFCKPORT PINB
#define DATAPORT PORTB
#define GATEWFCKBIT 4
#define DATABIT 2
#if defined(APPLY_PSONE_PAL_BIOS_PATCH)
#error "ATtiny does not support PAL PSOne patch yet!"
#endif
#else
#error "Select a board!"
#endif
#if defined(PSNEEDEBUG) && defined(USINGSOFTWARESERIAL)
#include <SoftwareSerial.h>
SoftwareSerial mySerial(-1, 3); // RX, TX. (RX -1 = off)
#define DEBUG_PRINT(x) mySerial.print(x)
#define DEBUG_PRINTHEX(x) mySerial.print(x, HEX)
#define DEBUG_PRINTLN(x) mySerial.println(x)
#define DEBUG_FLUSH mySerial.flush()
#elif defined(PSNEEDEBUG) && !defined(USINGSOFTWARESERIAL)
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTHEX(x) Serial.print(x, HEX)
#define DEBUG_PRINTLN(x) Serial.println(x)
#define DEBUG_FLUSH Serial.flush()
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTHEX(x)
#define DEBUG_PRINTLN(x)
#define DEBUG_FLUSH
#endif
#define NOP __asm__ __volatile__ ("nop\n\t")
// Setup() detects which (of 2) injection methods this PSX board requires, then stores it in pu22mode.
boolean pu22mode;
//Timing
const int delay_between_bits = 4000; // 250 bits/s (microseconds) (ATtiny 8Mhz works from 3950 to 4100)
const int delay_between_injections = 90; // 72 in oldcrow. PU-22+ work best with 80 to 100 (milliseconds)
// borrowed from AttyNee. Bitmagic to get to the SCEX strings stored in flash (because Harvard architecture)
bool readBit(int index, const unsigned char *ByteSet)
{
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);
int byte_index = index >> 3;
byte bits = pgm_read_byte(&(ByteSet[byte_index]));
int bit_index = index & 0x7; // same as (index - byte_index<<3) or (index%8)
byte mask = 1 << bit_index;
return (0 != (bits & mask));
}
}
pinMode(data, OUTPUT);
digitalWrite(data, 0);
delay(delay_between_injections);
}
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)
{
if (SCEAData[bit_counter] == 0)
{
pinMode(data, OUTPUT);
digitalWrite(data, 0);
delay(delay_between_bits);
}
else
void inject_SCEX(char region)
{
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);
}
}
pinMode(data, OUTPUT);
digitalWrite(data, 0);
delay(delay_between_injections);
}
void inject_SCEI()
{
//SCEE-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)
{
if (SCEIData[bit_counter] == 0)
{
//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};
//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,0,1,1,1,0,1,0,0};
//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,0,1,1,1,0,1,0,0};
static const PROGMEM unsigned char SCEEData[] = {0b01011001, 0b11001001, 0b01001011, 0b01011101, 0b11101010, 0b00000010};
static const PROGMEM unsigned char SCEAData[] = {0b01011001, 0b11001001, 0b01001011, 0b01011101, 0b11111010, 0b00000010};
static const PROGMEM unsigned char SCEIData[] = {0b01011001, 0b11001001, 0b01001011, 0b01011101, 0b11011010, 0b00000010};
// pinMode(data, OUTPUT) is used more than it has to be but that's fine.
for (byte bit_counter = 0; bit_counter < 44; bit_counter++)
{
if (readBit(bit_counter, region == 'e' ? SCEEData : region == 'a' ? SCEAData : SCEIData) == 0)
{
pinMode(data, OUTPUT);
bitClear(GATEWFCKPORT,DATABIT); // data low
delayMicroseconds(delay_between_bits);
}
else
{
if (pu22mode) {
pinMode(data, OUTPUT);
unsigned long now = micros();
do {
bool wfck_sample = bitRead(GATEWFCKPORT, GATEWFCKBIT);
bitWrite(DATAPORT,DATABIT,wfck_sample); // output wfck signal on data pin
}
while ((micros() - now) < delay_between_bits);
}
else { // PU-18 or lower mode
pinMode(data, INPUT);
delayMicroseconds(delay_between_bits);
}
}
}
pinMode(data, OUTPUT);
digitalWrite(data, 0);
delay(delay_between_bits);
bitClear(GATEWFCKPORT,DATABIT); // pull data low
delay(delay_between_injections);
}
else
void NTSC_fix(){
#if defined(APPLY_PSONE_PAL_BIOS_PATCH)
pinMode(BIOS_A18, INPUT);
pinMode(BIOS_D2, INPUT);
delay(100); // this is right after SQCK appeared. wait a little to avoid noise
while (!bitRead(BIOSPATCHPORTIN, BIOS_A18_BIT))
{
; //wait for stage 1 A18 pulse
}
delay(1350); //wait through stage 1 of A18 activity
noInterrupts(); // start critical section
while (!bitRead(BIOSPATCHPORTIN, BIOS_A18_BIT))
{
; //wait for priming A18 pulse
}
delayMicroseconds(17); // max 17us for 16Mhz ATmega (maximize this when tuning!)
bitClear(BIOSPATCHPORTOUT, BIOS_D2_BIT); // store a low
bitSet(BIOSPATCHDDR, BIOS_D2_BIT); // D2 = output. drags line low now
delayMicroseconds(3); // min 2us for 16Mhz ATmega, 8Mhz requires 3us (minimize this when tuning, after maximizing first us delay!)
bitClear(DDRD, BIOS_D2_BIT); // D2 = input / high-z
interrupts(); // end critical section
// not necessary but I want to make sure these pins are now high-z again
pinMode(BIOS_A18, INPUT);
pinMode(BIOS_D2, INPUT);
#endif
}
//--------------------------------------------------
// Setup
//--------------------------------------------------
void setup()
{
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);
pinMode(data, INPUT);
pinMode(gate_wfck, INPUT);
pinMode(subq, INPUT); // PSX subchannel bits
pinMode(sqck, INPUT); // PSX subchannel clock
#if defined(PSNEEDEBUG) && defined(USINGSOFTWARESERIAL)
pinMode(debugtx, OUTPUT); // software serial tx pin
mySerial.begin(115200); // 13,82 bytes in 12ms, max for softwareserial. (expected data: ~13 bytes / 12ms)
#elif defined(PSNEEDEBUG) && !defined(USINGSOFTWARESERIAL)
Serial.begin(500000); // 60 bytes in 12ms (expected data: ~26 bytes / 12ms)
DEBUG_PRINT("MCU frequency: "); DEBUG_PRINT(F_CPU); DEBUG_PRINTLN(" Hz");
DEBUG_PRINTLN("Waiting for SQCK..");
#endif
#if defined(ARDUINO_BOARD)
pinMode(LED_BUILTIN, OUTPUT); // Blink on injection / debug.
digitalWrite(LED_BUILTIN, HIGH); // mark begin of setup
#endif
// wait for console power on and stable signals
while (!digitalRead(sqck));
while (!digitalRead(gate_wfck));
// if enabled: patches PAL PSOne consoles so they start all region games
NTSC_fix();
// Board detection
//
// GATE: __----------------------- // this is a PU-7 .. PU-20 board!
//
// WFCK: __-_-_-_-_-_-_-_-_-_-_-_- // this is a PU-22 or newer board!
unsigned int highs, lows = 0;
unsigned long now = millis();
do {
if (digitalRead(gate_wfck) == 1) highs++;
if (digitalRead(gate_wfck) == 0) lows++;
delayMicroseconds(200); // good for ~5000 reads in 1s
}
while ((millis() - now) < 1000); // sample 1s
// typical readouts
// PU-22: highs: 2449 lows: 2377
if (lows > 100) {
pu22mode = 1;
}
else {
pu22mode = 0;
}
#ifdef ATTINY_X5
DEBUG_PRINT("m "); DEBUG_PRINTLN(pu22mode);
#else
DEBUG_PRINT("highs: "); DEBUG_PRINT(highs); DEBUG_PRINT(" lows: "); DEBUG_PRINTLN(lows);
DEBUG_PRINT("pu22mode: "); DEBUG_PRINTLN(pu22mode);
// 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
#if defined(ARDUINO_BOARD)
digitalWrite(LED_BUILTIN, LOW); // setup complete
#endif
DEBUG_FLUSH; // empty serial transmit buffer
}
}
pinMode(data, OUTPUT);
digitalWrite(data, 0);
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()
{
//Variables
int loop_counter;
//Code
NTSC_fix();
delay(6900);
digitalWrite(data, 0);
pinMode(data, OUTPUT);
delay(100);
pinMode(gate, OUTPUT);
digitalWrite(gate, 0);
for (loop_counter = 0; loop_counter < 25; loop_counter = loop_counter + 1)
{
inject_SCEE();
}
pinMode(gate, INPUT);
pinMode(data, INPUT);
delay(11000);
pinMode(gate, OUTPUT);
digitalWrite(gate, 0);
for (loop_counter = 0; loop_counter < 60; loop_counter = loop_counter + 1)
{
inject_SCEE();
}
pinMode(gate, INPUT);
pinMode(data, INPUT);
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//--------------------------------------------------
// Setup function - execution starts here!
//--------------------------------------------------
void setup()
{
inject_playstation();
}
//----------------------------------------------------------------
// Loop function - executes after the initial injection cycle
//----------------------------------------------------------------
void loop()
{
if(lid == 0)
{
while(lid != 1); //Wait until the lid is closed again (after being opened) to initiate a new injection cycle
inject_playstation();
}
}
void loop()
{
static byte scbuf [12] = { 0 }; // We will be capturing PSX "SUBQ" packets, there are 12 bytes per valid read.
static unsigned int timeout_clock_counter = 0;
static byte bitbuf = 0; // SUBQ bit storage
static bool sample = 0;
static byte bitpos = 0;
byte scpos = 0; // scbuf position
noInterrupts(); // start critical section
start:
// Capture 8 bits for 12 runs > complete SUBQ transmission
bitpos = 0;
for (; bitpos<8; bitpos++) {
while (bitRead(SUBQPORT, SQCKBIT) == 1){
// wait for clock to go low..
// a timeout resets the 12 byte stream in case the PSX sends malformatted clock pulses, as happens on bootup
timeout_clock_counter++;
if (timeout_clock_counter > 1000){
scpos = 0; // reset SUBQ packet stream
timeout_clock_counter = 0;
bitbuf = 0;
goto start;
}
}
// wait for clock to go high..
while ((bitRead(SUBQPORT, SQCKBIT)) == 0);
sample = bitRead(SUBQPORT, SUBQBIT);
bitbuf |= sample << bitpos;
timeout_clock_counter = 0; // no problem with this bit
}
// one byte done
scbuf[scpos] = bitbuf;
scpos++;
bitbuf = 0;
// repeat for all 12 bytes
if (scpos < 12){
goto start;
}
interrupts(); // end critical section
// log SUBQ packets. We only have 12ms to get the logs written out. Slower MCUs get less formatting.
#ifdef ATTINY_X5
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++) {
if (scbuf[i] < 0x10) {DEBUG_PRINT("0");} // padding
DEBUG_PRINTHEX(scbuf[i]);
}
DEBUG_PRINTLN("");
}
#else
if (!(scbuf[0] == 0 && scbuf[1] == 0 && scbuf[2] == 0 && scbuf[3] == 0)){
for (int i = 0; i<12;i++) {
if (scbuf[i] < 0x10) {DEBUG_PRINT("0");} // padding
DEBUG_PRINTHEX(scbuf[i]);
DEBUG_PRINT(" ");
}
DEBUG_PRINTLN("");
}
#endif
// check if read head is in wobble area
// We only want to unlock game discs (0x41) and only if the read head is in the outer TOC area.
// We want to see a TOC sector repeatedly before injecting (helps with timing and marginal lasers).
// All this logic is because we don't know if the HC-05 is actually processing a getSCEX() command.
// Hysteresis is used because older drives exhibit more variation in read head positioning.
// While the laser lens moves to correct for the error, they can pick up a few TOC sectors.
static byte hysteresis = 0;
if (
(scbuf[0] == 0x41 && scbuf[1] == 0x00 && scbuf[6] == 0x00) && // [0] = 41 means psx game disk. the other 2 checks are garbage protection
(scbuf[2] == 0xA0 || scbuf[2] == 0xA1 || scbuf[2] == 0xA2 || // if [2] = A0, A1, A2 ..
(scbuf[2] == 0x01 && (scbuf[3] >= 0x98 || scbuf[3] <= 0x02) ) ) // .. or = 01 but then [3] is either > 98 or < 02
) {
hysteresis++;
}
else if ( hysteresis > 0 &&
((scbuf[0] == 0x01 || scbuf[0] == 0x41) && (scbuf[1] == 0x00 /*|| scbuf[1] == 0x01*/) && scbuf[6] == 0x00)
) { // This CD has the wobble into CD-DA space. (started at 0x41, then went into 0x01)
hysteresis++;
}
else if (hysteresis > 0) {
hysteresis--; // None of the above. Initial detection was noise. Decrease the counter.
}
// hysteresis value "optimized" using very worn but working drive on ATmega328 @ 16Mhz
// should be fine on other MCUs and speeds, as the PSX dictates SUBQ rate
if (hysteresis >= 14){
// If the read head is still here after injection, resending should be quick.
// Hysteresis naturally goes to 0 otherwise (the read head moved).
hysteresis = 11;
#ifdef ATTINY_X5
DEBUG_PRINTLN("!");
#else
DEBUG_PRINTLN("INJECT!INJECT!INJECT!INJECT!INJECT!INJECT!");
#endif
#if defined(ARDUINO_BOARD)
digitalWrite(LED_BUILTIN, HIGH);
#endif
pinMode(data, OUTPUT);
digitalWrite(data, 0); // pull data low
if (!pu22mode){
pinMode(gate_wfck, OUTPUT);
digitalWrite(gate_wfck, 0);
}
// HC-05 waits for a bit of silence (pin low) before it begins decoding.
delay(delay_between_injections);
// inject symbols now. 2 x 3 runs seems optimal to cover all boards
for (byte loop_counter = 0; loop_counter < 2; loop_counter++)
{
inject_SCEX('e'); // e = SCEE, a = SCEA, i = SCEI
inject_SCEX('a'); // injects all 3 regions by default
inject_SCEX('i'); // optimize boot time by sending only your console region letter (all 3 times per loop)
}
if (!pu22mode){
pinMode(gate_wfck, INPUT); // high-z the line, we're done
}
pinMode(data, INPUT); // high-z the line, we're done
#if defined(ARDUINO_BOARD)
digitalWrite(LED_BUILTIN, LOW);
#endif
}
// keep catching SUBQ packets forever
}

View File

@@ -19,29 +19,66 @@
P P P P P P
P P P P P P
P PPPPPPPPPPPP P P PPPPPPPPPPP PPPPPPPPPPP
--------------------------------------------------
This version is from the forum
https://assemblergames.com/threads/psnee-a-stealth-modchip-for-all-ps1-models.57907/
And is developed by TheFrietMan
---------------------------------------
This version is from
http://www.psxdev.net/forum/viewtopic.php?f=47&t=1262&start=40
Is developed by the psxdev team
-------------------------------------------------
This PsNee version is meant for Arduino boards.
--------------------------------------------------
PsNee, an open source stealth modchip for the Sony Playstation 1, usable on
all platforms supported by Arduino, preferably ATTiny. Finally something modern!
- Arduino Pro Mini @8Mhz and @16Mhz (supported, tested)
- Arduino Uno @8Mhz and @16Mhz (supported, tested)
- Arduino Pro Micro has a different pin assignment and needs some easy porting. (ToDo)
- ATtiny85: Should work the same as ATtiny45 (supported, untested)
- ATtiny45: LFUSE 0xE2 HFUSE 0xDF > internal oscillator, full 8Mhz speed (supported, tested)
- ATtiny25: Should work the same as ATtiny45 but doesn't have enough Flash nor RAM for PSNEEDEBUG (supported, untested)
Some extra libraries might be required, depending on the board / chip used.
PAL PM-41 support isn't implemented yet. (ToDo)
This code defaults to multi-region, meaning it will unlock PAL, NTSC-U and NTSC-J machines.
You can optimize boot times for your console further. See "// inject symbols now" in the main loop.
----------------------------------------------------------------------
BIOS patch for PM-41
For now it only supports Arduino boards (ATmega chips).
Also, the Arduino must either be powered on first or have no bootloader present (flashed using SPI) since I expect a signal ~1 second after power on.
8Mhz boards are also supported.
--------------------------------------------------
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.
-------------------------------------------------------------------
Choose your hardware!
You must uncomment the line that corresponds to your cips.
2 main branches available:
- ATmega based > easy to use, fast and nice features for development
- ATtiny based > less features, internal clock has 10% variation
This code is multi-region, meaning it will unlock PAL, NTSC-U and NTSC-J machines.
-----------------------------------------------------
Pin assignments
PSNee psxdev PlayStation
Arduino Atinny name ps pin Name
--------------------------------------------------
pin-vin = VCC = 3.5v 3.5v = supply
3 = debugtx
pin-9 = 4 = gate_wfck IC732.Pin-5 = WFCK
pin-8 = 2 = data IC732.Pin-42 = CEO
pin-7 = 1 = subq IC304.Pin-24 = SUBQ
pin-6 = 0 = sqck IC304.Pin-26 = SQCK
pin 5 = BIOS D2 IC102.Pin-15 = D2
pin 4 = BIOS A18 Ic102.Pin-31 = A18
Pin-Gnd = GND = gnd GND = gnd
------------------------------------------------
General info!
--------------------------------------------------
PLAYSTATION 1 SECURITY - HOW IT DOES IT'S THING:
@@ -68,16 +105,6 @@ The security thus functions not only as copy protection, but also as region prot
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.

View File

@@ -53,5 +53,90 @@ A lot!
- 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)
-------------------------------------------------------------
A minor detail: The lid detection is missing its digitalRead() ;p
psxdev
-------------------------------------------------------------
Update 7th of May 2017
Branched and tweaked for use with the Position 0 switch on a PSX laser.
(Requires a bit of sticky tape at the point where the switch touches the laser assembly.)
This allows deterministic SCEX injections, without relying on timing. Also gets rid of connection wires for LID and RESET.
WIP!
psxdev
--------------------------------------------------------------
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).
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!
psxdev
-------------------------------------------------------------
Update 27th of May 2017
This version is compatible with 8Mhz and 16Mhz ATmega328 / Arduino boards.
It uses polling to grab the SUBQ packets. This works better than relying on interrupts.
I even have a few cycles to spare on a 8Mhz chip!
psxdev
------------------------------------------------------------
Update 31st May 2017
- supports all motherboard versions except PU-41 (PAL) (will get to it!)
- WFCK modulated injection method for PU-22 and up, just like the last multimode 3 chips
- minimized CD controller interference: PsNee only ever speaks when it has to (also: full stealth)
- not relying on BIOS delays: perfect boot disregarding extension cards etc
- might not be bug free! I'm just one guy and testing on a dozen consoles takes time ;)
psxdev
------------------------------------------------------------
Update 4 June 2017
- unified SCEX injection function / easier to read code
- PU-22+ now work without the WFCK wire (but depends on tight timings, tested on 8 and 16Mhz mcu)
- interrupts disabled while sampling SUBQ > much better performance capturing all events correctly
- now blinks the built-in LED on injections for debugging
psxdev
-------------------------------------------------------------
Updat 29 June 2017
- final modchip function I / Os: SQCK, SCLK, data, gate_wfck
- hysteresis for injections, fixes anti-mod occasionally triggering when using worn drives
- optimized injection timing for multi-region, multi BIOS versions (Sony added more protection checks over time)
- first attempt to make it more portable to other Arduino variants
- auto console detection works reliably, with and without Arduino bootloader present
- pin assignments changed for practical / installation reasons (ICSP capability, wire routing)
- so many changes, it surely contains all new bugs ;)
Pin assignments are finalized. We can start producing final installation images / help!
As always, I appreciate code reviews and bug fixes. I'm sorry some of it got so messy ;p
psxdev
---------------------------------------------------------------
Update 9 July 2017
Sure. Consider it work in progress quality ;)
Main changes:
- figured out ATtiny pin assignments :p
- RAM use reduced by only storing the "SCE" part of the license string once. Using a somewhat farfetched method. Maybe someone can make it nicer, without the recursion and over engineering? :p
- tried getting debug prints but it doesn't look like it'll happen (on ATtiny45), too little RAM > the chip crashes
- SUBQ sampling timing reverted to sample while clock is low, instead of right after it goes high
--------------------------------------------------------------
Update: 10 July 2017
- finished porting to ATtinyX5 (25,45,85 although the 25 has too little resources. for now.)
- store the licensing symbols in flash again, frees a lot of RAM
- bit retrieval code lifted from AttyNee (Nice work guys!)
- extra RAM allows SoftwareSerial debugging prints on an ATtiny45!
- nicer intro readme ;p
--------------------------------------------------------------
Update: 16 July 2017
-The BIOS patch works!
For now it only supports Arduino boards (ATmega chips).
Also, the Arduino must either be powered on first or have no bootloader present (flashed using SPI) since I expect a signal ~1 second after power on.
8Mhz boards are also supported.