PSNee further development

General information to do with the PlayStation 1 Hardware. Including modchips, pinouts, rare or obscure development equipment, etc.
rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » June 4th, 2017, 11:38 pm

New version:
- 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

Code: Select all

// 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

// PAL PU-41 support isn't implemented here yet. Use PsNee v6 for them.

// Choose the correct inject_SCEX() for your console region. 
// e = Europe / PAL
// a = North America / NTSC-U
// i = Japan / NTSC-J

// Uncomment #define PU22_MODE for PU-22, PU-23, PU-41 mainboards.

#define PU22_MODE

//Pins
const int data = 8;         // Arduino pin 8, ATmega PB0 injects SCEX string. point 6 in old modchip Diagrams
const int SUBQ = 10;     // Arduino pin 10, ATmega PB2 "SUBQ" Mechacon pin 24 (PU-7 and early PU-8 Mechacons: pin 39)
const int SQCK = 11;    // Arduino pin 11, ATmega PB3 "SQCK" Mechacon pin 26 (PU-7 and early PU-8 Mechacons: pin 41)
//Timing
const int delay_between_bits = 4000; // 250 bits/s (microseconds)
const int delay_between_injections = 74; // 74 original, 72 in oldcrow (milliseconds)

// for PU-22 mode: specific delay times for the high bit injection. It depends on the MCU speed.
#if F_CPU == 16000000
const byte gate_high = 21;
const byte gate_low = 23;
#else
const byte gate_high = 20;
const byte gate_low = 20;
#endif

// SQCK (SUBQ clock) sampling timeout: 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 byte and wait for a better chance. 1000 is a good value.
const int sampling_timeout = 1000;


//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}; //SCEE
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,1,1,1,1,0,1,0,0}; //SCEA
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,1,0,1,1,0,1,0,0}; //SCEI

void inject_SCEX(char region)
{
  const boolean *SCEXData;
  switch (region){
    case 'e': SCEXData = SCEEData; break;
    case 'a': SCEXData = SCEAData; break;
    case 'i': SCEXData = SCEIData; break;
  }

  static const bool high_low = 1;

  digitalWrite(LED_BUILTIN, HIGH); // this is Arduino Pin 13 / PB5

  for (byte bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1)
  {
    if (*(SCEXData+bit_counter) == 0)
    {
      bitClear(PORTB,0); // pull data low
      delayMicroseconds(delay_between_bits);
    }
    else
    {
      unsigned long now = micros();
      do {
#ifdef PU22_MODE
        bitWrite(PORTB,0,high_low); // output wfck like signal on data pin
        delayMicroseconds(gate_high);
        bitWrite(PORTB,0,!high_low);
        delayMicroseconds(gate_low);
#else
        bitSet(PORTB,0); // drag data pin high
#endif
      }
      while ((micros() - now) < delay_between_bits);
      //Serial.println((micros() - now));
    }
  }
  bitClear(PORTB,0); // pull data low
  delay(delay_between_injections);
  
  digitalWrite(LED_BUILTIN, LOW);
}

//--------------------------------------------------
//     Setup
//--------------------------------------------------
void setup()
{
  pinMode(data, INPUT); // Arduino pin 8, ATmega PB0
  pinMode(SUBQ, INPUT); // spi data in Arduino pin 10, ATmega PB2
  pinMode(SQCK, INPUT); // spi clock Arduino pin 11, ATmega PB3
  pinMode(LED_BUILTIN, OUTPUT); // Blink on injection / debug.
  Serial.begin (1000000);
  Serial.println("Start ");
  
  // 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;
}

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

  static unsigned int timeout_clock_counter = 0;
  static byte bitbuf = 0;         // SUBQ bit storage
  static bool sample = 0;

  // Capture 8 bits per loop run. 
  // unstable clock, bootup, reset and disc changes are ignored
  noInterrupts(); // start critical section

// yes, a goto jump label. This is to avoid a return out of critical code with interrupts disabled. 
// It prevents bad behaviour, for example running the Arduino Serial Event routine without interrupts.
// Using a function makes shared variables messier.
// We really want to have an 8 bit packet before doing anything else.
timedout:

  for (byte bitpos = 0; bitpos<8; bitpos++) {
    do {
      // nothing, reset on timeout
      timeout_clock_counter++;
      if (timeout_clock_counter > sampling_timeout){
        scpos = 0;  // reset SUBQ packet stream
        timeout_clock_counter = 0;
        num_resets++;
        bitpos = 0;
        goto timedout;
      }
    }
    while (bitRead(PINB, 3) == 1); // wait for clock to go low
    
#if F_CPU == 16000000 // wait a few cpu cycles > better readings in tests
    __asm__("nop\n\t"); __asm__("nop\n\t"); __asm__("nop\n\t");
#endif

    // sample the bit.
    sample = bitRead(PINB, 2);
    bitbuf |= sample << bitpos;

    do {
      // nothing
    } while ((bitRead(PINB, 3)) == 0); // Note: Even if sampling is bad, it will not get stuck here. There will be clock pulses eventually.

    timeout_clock_counter = 0; // This bit came through fine. 
  }
  
  scbuf[scpos] = bitbuf;
  scpos++;
  bitbuf = 0;

  if (scpos < 12){
    return;
  }

  interrupts(); // end critical section
  
  // logging. 
  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++) {
      Serial.print(scbuf[i], HEX);
      Serial.print(" ");
    }
    Serial.print(" resets:  ");
    Serial.println(num_resets);
  }

  num_resets = 0;
  scpos = 0;
  
  // check if this is the wobble area
  // 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) ||
    (scbuf[2] > 0x00 && scbuf[2] <= 0x99)) ){ // lead in / wobble area
    
    Serial.println("INJECT!");

    pinMode(data, OUTPUT); // prepare for SCEX injection

    bitClear(PORTB,0); // pull data low
    
    // HC-05 is waiting for a bit of silence (pin Low) before it begins decoding. 
	  // minimum 66ms required on SCPH-7000
	  // minimum 79ms required on SCPH-7502
	  delay(82); 
    
    for (int loop_counter = 0; loop_counter < 2; loop_counter++)
    {
       inject_SCEX('e'); // e = SCEE, a = SCEA, i = SCEI
    }

    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)


kalymnos77
Curious PSXDEV User
Curious PSXDEV User
Posts: 23
Joined: Jun 06, 2017

Post by kalymnos77 » June 6th, 2017, 11:35 pm

Hello.
To start handsome work :clap

I found this project a bit by chance, although I did not plan to use it soon, I found it atractive.
Looking a little more I realized that the original site of the project was down, and that the code and the information was a bit everywhere.
Seeing this I allowed myself to open a https://github.com/kalymos/PsNee, to aggregate different cod, and I follow allows to include your code in a branch that carries your name.
I also mention you in the readme and there indicates the page of this forum.
I know that I should have asked you before doing it, and I hope that it does not bother you too much :roll:
have a good day

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » June 7th, 2017, 12:14 am

Thanks, I'm glad that someone opted to curate this on Github!
I've sent you a pm with some details :)

likeabaus
Extreme PSXDEV User
Extreme PSXDEV User
Posts: 133
Joined: Jul 27, 2016

Post by likeabaus » June 7th, 2017, 4:39 am

Rama3 if you would like any additional mirrors, just say the word, i'd be more than happy to pitch in as far as hosting goes. I've been lurking in this thread since its inception and i'm pretty impressed with what everyone here is doing to keep the project alive :)

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » June 7th, 2017, 5:33 am

It'd be great if some people tested it so I get some feedback and more console / Arduino variants coverage!
Hosting the code is no problem, Github is great for this :)

Freezerburn26
What is PSXDEV?
What is PSXDEV?
Posts: 3
Joined: Jun 07, 2017

Post by Freezerburn26 » June 7th, 2017, 8:19 am

Are there install diagrams for these?
I'd love to test this with some ATTINY45-20PU.

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » June 8th, 2017, 12:32 am

I'm sorry, this code doesn't support ATtiny chips right now.
I would like to support them, as they have the smallest footprint of all the options, but it will take some effort and time.
For example, the SUBQ sampling has to be very fast. The injection stuff uses lots of RAM (only 256 Bytes on the ATtiny45!).
The timing for PU22+ motherboards has to be precise > oscillator controlled, while ATtiny use an internal resonator.
In short: running my code on these MCUs is probably quite tricky.

Maybe you have something with ATmega around? Something like:
http://www.ebay.de/itm/MINI-USB-Nano-V3 ... 2409084476

These cheap boards are perfectly capable. 5Mhz oscillator controlled and a full ATmega328. Super easy to program!

Freezerburn26
What is PSXDEV?
What is PSXDEV?
Posts: 3
Joined: Jun 07, 2017

Post by Freezerburn26 » June 8th, 2017, 2:17 am

Ahhhh, the github had seemed to be mentioning ATTINY45.

I went ahead and ordered some of these:
http://www.ebay.com/itm/391689479435

I am interested in testing this out on some PSOne NTSC - USA.
Is there a diagram for it? Is the github not the latest version to write?

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » June 8th, 2017, 2:54 am

I haven't tested it on a PSOne yet.
I only have them in PAL flavor and for those to work, I need to adapt the NTSC fix from PsNee.

It should work with your NTSC unit though.
Looking at board scans, it appears there are no convenient SUBQ and SCLK test points to solder to.
Give me a day to check this and come up with an install diagram.

User avatar
TriMesh
Verified
PSX Aptitude
PSX Aptitude
Posts: 225
Joined: Dec 20, 2013
PlayStation Model: DTL-H1202
Location: Hong Kong

Post by TriMesh » June 8th, 2017, 3:00 am

if you are installing a chip in a SCPH-101, you don't need the region patch. That boot ROM doesn't have a region check in it.

SCPH-100: Region check for NTSC:J
SCPH-101: No region check
SCPH-102: Region check for PAL
SCPH-103: No region check

I suspect this is because the SCPH-101 and the SCPH-103 use the same boot ROM, but boot different territory discs (NTSC:U/C and NTSC:J respectively).

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » June 8th, 2017, 3:45 am

This is the diagram for a PM-41 board. You still need to find 3.5V and GND somewhere though.
Image

Freezerburn26
What is PSXDEV?
What is PSXDEV?
Posts: 3
Joined: Jun 07, 2017

Post by Freezerburn26 » June 8th, 2017, 5:58 am

Awesome, thanks a bunch.

I also have a SCPH 1001 model I'd love to try it out in as well if you have any diagram for that one.

likeabaus
Extreme PSXDEV User
Extreme PSXDEV User
Posts: 133
Joined: Jul 27, 2016

Post by likeabaus » June 8th, 2017, 11:08 am

Has anyone managed to get this to run on a straight up Arduino Uno board? I tried the v6 code from assemblergames ages ago, but there was no digital logic going on at all (confirmed with a multimeter, checking for fluctuations on all points besides vcc and gnd, sorry i lack a proper oscilloscope). I think what's happening is, every time the Uno powers on with the psx, its wiping itself (just like when you plug the board into a pc via usb, all code is wiped). I remember reading about a trace that can be cut on the Uno PCB to disable the reset circuit, but never really followed up and tried it. If anyone who's managed to get this to run with the basic Arduino Uno, could point out, the trace that needs cutting, i can do some testing on my NTSC US scph-9001 (overclock mod).

For those wondering, i did test out the vcc and gnd points and those were fine, just that no logic was taking place (only booted domestic originals) and no logic confirmed with the meter....

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » June 8th, 2017, 6:24 pm

You don't need to cut any traces. You couldn't measure anything because of these things:

- You probably had the line "NTSC_fix()" in it. This code waits for an event on the BIOS lines. If those aren't connected to a PAL PSOne BIOS, nothing will happen.

Code: Select all

DDRB = 0x00;
  delay(delay_ntsc);
  while (!(PINB & 0b00001000))
  {
    ;  //Wait
  }
- If you took care of that code, you still could not measure activity on the Data and Gate lines, because all they ever do is pull down a line to GND.

A regular Ardiuno Uno will work great with the original and my code. Try mine :)

Freezerburn26:
Image

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » June 8th, 2017, 8:39 pm

Just did a little test on a PAL PSOne. Code works and the diagram is correct.

kalymnos77
Curious PSXDEV User
Curious PSXDEV User
Posts: 23
Joined: Jun 06, 2017

Post by kalymnos77 » June 8th, 2017, 11:15 pm

Hi Freezerburn26 for the ATTINY45 there is the version https://github.com/kalymos/PsNee/tree/postal2201.
It resumes the same wiring as the version 6 of PSNee, and is obtimized for the smallest chip, If I understood everything to the translation of russian.

I can not find their complete wiring of the old version, it seems to me that it is identical to that of MultiMode, like its
MultiMode V3 PSNee v6
chip pin name Arduino pin name

pin-1 = 3.5v
pin-2 = stealth signal
pin-4 = reset line
pin-5 = « Gate » = Arduino pin-9 = gate
pin-6 = data output = Arduino pin-8 = data
pin-7 = dooor signal = Arduino pin-10 = lid
pin-8 = ground = Arduino Pin-Gnd = gnd

?? = Arduino pin-11 = biosA18
?? =Arduino pin-12 = biosD2

likeabaus
Extreme PSXDEV User
Extreme PSXDEV User
Posts: 133
Joined: Jul 27, 2016

Post by likeabaus » June 9th, 2017, 2:20 am

rama3 wrote:You don't need to cut any traces. You couldn't measure anything because of these things:

- You probably had the line "NTSC_fix()" in it. This code waits for an event on the BIOS lines. If those aren't connected to a PAL PSOne BIOS, nothing will happen.

Code: Select all

DDRB = 0x00;
  delay(delay_ntsc);
  while (!(PINB & 0b00001000))
  {
    ;  //Wait
  }
- If you took care of that code, you still could not measure activity on the Data and Gate lines, because all they ever do is pull down a line to GND.

A regular Ardiuno Uno will work great with the original and my code. Try mine :)

Freezerburn26:
Image
Okay thanks! I didn't catch that in the code comments, must've over looked it! As for your code, do I need to modify it with the NTSC US sceX code (like I did for v6 (it was PAL by default))?

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » June 9th, 2017, 3:25 am

Yes, you have to inject the correct region.
I could make this universal, I suppose. It will be a bit slower to boot but all PSX, including all PSOne, have it so that the HC-05 ignores the wrong region strings.
Making it universal would reduce what regular users have to worry about when setting it all up.

rama3
Verified
/// PSXDEV | ELITE ///
/// PSXDEV | ELITE ///
Posts: 510
Joined: Apr 16, 2017

Post by rama3 » June 9th, 2017, 3:31 am

Slightly altered new version:
- multi-region by default

Code: Select all

// 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

// PAL PU-41 support isn't implemented here yet. Use PsNee v6 for them.

// By default, this code is multi-region. You can optimize it for your console, if you want to.
// Choose the correct inject_SCEX() for your console region. 
// e = Europe / PAL
// a = North America / NTSC-U
// i = Japan / NTSC-J

// Uncomment #define PU22_MODE for PU-22, PU-23, PU-41 mainboards.

#define PU22_MODE

//Pins
const int data = 8;         // Arduino pin 8, ATmega PB0 injects SCEX string. point 6 in old modchip Diagrams
const int SUBQ = 10;     // Arduino pin 10, ATmega PB2 "SUBQ" Mechacon pin 24 (PU-7 and early PU-8 Mechacons: pin 39)
const int SQCK = 11;    // Arduino pin 11, ATmega PB3 "SQCK" Mechacon pin 26 (PU-7 and early PU-8 Mechacons: pin 41)
//Timing
const int delay_between_bits = 4000; // 250 bits/s (microseconds)
const int delay_between_injections = 74; // 74 original, 72 in oldcrow (milliseconds)

// for PU-22 mode: specific delay times for the high bit injection. It depends on the MCU speed.
#if F_CPU == 16000000
const byte gate_high = 21;
const byte gate_low = 23;
#else
const byte gate_high = 20;
const byte gate_low = 20;
#endif

// SQCK (SUBQ clock) sampling timeout: 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 byte and wait for a better chance. 1000 is a good value.
const int sampling_timeout = 1000;


//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}; //SCEE
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,1,1,1,1,0,1,0,0}; //SCEA
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,1,0,1,1,0,1,0,0}; //SCEI

void inject_SCEX(char region)
{
  const boolean *SCEXData;
  switch (region){
    case 'e': SCEXData = SCEEData; break;
    case 'a': SCEXData = SCEAData; break;
    case 'i': SCEXData = SCEIData; break;
  }

  digitalWrite(LED_BUILTIN, HIGH); // this is Arduino Pin 13 / PB5

  for (byte bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1)
  {
    if (*(SCEXData+bit_counter) == 0)
    {
      bitClear(PORTB,0); // pull data low
      delayMicroseconds(delay_between_bits);
    }
    else
    {
      unsigned long now = micros();
      do {
#ifdef PU22_MODE
        bitWrite(PORTB,0,1); // output high
        delayMicroseconds(gate_high);
        bitWrite(PORTB,0,0); // output low
        delayMicroseconds(gate_low);
#else
        bitSet(PORTB,0); // drag data pin high
#endif
      }
      while ((micros() - now) < delay_between_bits);
      //Serial.println((micros() - now));
    }
  }
  bitClear(PORTB,0); // pull data low
  delay(delay_between_injections);
  
  digitalWrite(LED_BUILTIN, LOW);
}

//--------------------------------------------------
//     Setup
//--------------------------------------------------
void setup()
{
  pinMode(data, INPUT); // Arduino pin 8, ATmega PB0
  pinMode(SUBQ, INPUT); // spi data in Arduino pin 10, ATmega PB2
  pinMode(SQCK, INPUT); // spi clock Arduino pin 11, ATmega PB3
  pinMode(LED_BUILTIN, OUTPUT); // Blink on injection / debug.
  Serial.begin (1000000);
  Serial.println("Start ");
  
  // 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;
}

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

  static unsigned int timeout_clock_counter = 0;
  static byte bitbuf = 0;         // SUBQ bit storage
  static bool sample = 0;

  // Capture 8 bits per loop run. 
  // unstable clock, bootup, reset and disc changes are ignored
  noInterrupts(); // start critical section

// yes, a goto jump label. This is to avoid a return out of critical code with interrupts disabled. 
// It prevents bad behaviour, for example running the Arduino Serial Event routine without interrupts.
// Using a function makes shared variables messier.
// We really want to have an 8 bit packet before doing anything else.
timedout:

  for (byte bitpos = 0; bitpos<8; bitpos++) {
    do {
      // nothing, reset on timeout
      timeout_clock_counter++;
      if (timeout_clock_counter > sampling_timeout){
        scpos = 0;  // reset SUBQ packet stream
        timeout_clock_counter = 0;
        num_resets++;
        bitpos = 0;
        goto timedout;
      }
    }
    while (bitRead(PINB, 3) == 1); // wait for clock to go low
    
#if F_CPU == 16000000 // wait a few cpu cycles > better readings in tests
    __asm__("nop\n\t"); __asm__("nop\n\t"); __asm__("nop\n\t");
#endif

    // sample the bit.
    sample = bitRead(PINB, 2);
    bitbuf |= sample << bitpos;

    do {
      // nothing
    } while ((bitRead(PINB, 3)) == 0); // Note: Even if sampling is bad, it will not get stuck here. There will be clock pulses eventually.

    timeout_clock_counter = 0; // This bit came through fine. 
  }
  
  scbuf[scpos] = bitbuf;
  scpos++;
  bitbuf = 0;

  if (scpos < 12){
    return;
  }

  interrupts(); // end critical section
  
  // logging. 
  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++) {
      Serial.print(scbuf[i], HEX);
      Serial.print(" ");
    }
    Serial.print(" resets:  ");
    Serial.println(num_resets);
  }

  num_resets = 0;
  scpos = 0;
  
  // check if this is the wobble area
  // 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) ||
    (scbuf[2] > 0x00 && scbuf[2] <= 0x99)) ){ // lead in / wobble area
    
    Serial.println("INJECT!");

    pinMode(data, OUTPUT); // prepare for SCEX injection

    bitClear(PORTB,0); // pull data low
    
    // HC-05 is waiting for a bit of silence (pin Low) before it begins decoding. 
	  // minimum 66ms required on SCPH-7000
	  // minimum 79ms required on SCPH-7502
	  delay(82); 
    
    for (int 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'); // makes it easier for people to get working
    }

    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)


likeabaus
Extreme PSXDEV User
Extreme PSXDEV User
Posts: 133
Joined: Jul 27, 2016

Post by likeabaus » June 9th, 2017, 8:38 am

Oh okay, you didn't have to do that, thanks! I'll reset up the Arduino SDK and see if I can get this up and running....

Post Reply

Who is online

Users browsing this forum: Google [Bot] and 2 guests