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 » May 8th, 2017, 9:56 am

Sorry, I don't have any more information than what's in that thread. That forum is visitor unfriendly, hiding links behind pictures and not allowing downloads of attachments. I won't bother to register :p

User avatar
Shadow
Verified
Admin / PSXDEV
Admin / PSXDEV
Posts: 2670
Joined: Dec 31, 2012
PlayStation Model: H2000/5502
Discord: Shadow^PSXDEV

Post by Shadow » May 8th, 2017, 10:27 am

rama3 wrote:Sorry, I don't have any more information than what's in that thread. That forum is visitor unfriendly, hiding links behind pictures and not allowing downloads of attachments. I won't bother to register :p
All good. Argh, can't stand forums which do that. I actually made sure that PSXDEV.NET didn't do that nor have ads to make money from because it's just plain annoying :P
Development Console: SCPH-5502 with 8MB RAM, MM3 Modchip, PAL 60 Colour Modification (for NTSC), PSIO Switch Board, DB-9 breakout headers for both RGB and Serial output and an Xplorer with CAETLA 0.34.

PlayStation Development PC: Windows 98 SE, Pentium 3 at 400MHz, 128MB SDRAM, DTL-H2000, DTL-H2010, DTL-H201A, DTL-S2020 (with 4GB SCSI-2 HDD), 21" Sony G420, CD-R burner, 3.25" and 5.25" Floppy Diskette Drives, ZIP 100 Diskette Drive and an IBM Model M keyboard.

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

Post by rama3 » May 9th, 2017, 1:14 am

So in my WIP PsNee I do stealth by sensing the POS0 switch on the laser assembly.
It's an inelegant solution but it determines when to send the SCEX string and when to turn it off.

It would be nicer if we had some other signal to go by, one that works without modifications to the laser.
Suggestions I've heard so far ar the subchannel data and the CD controller command stream.
I've probed the SUBQ pin on the HC05 but I'm not sure what the clock for it is (Pin 26?), nor a way to extract the position information from the data. It's quite a lot of data, too.
For the CD controller commands, I've successfully logged the SCEX request packets by hooking the DATA and CLOK lines.
For this route, the microcontroller has to be fast enough to do real-time decoding. I don't know how fast, yet.
My logic analyzer was set to 24MS/s though.

Are there any other convenient signals, maybe?
We just need to know when a game can expect an SCEX stream and when not.

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

Post by TriMesh » May 9th, 2017, 8:34 pm

Yeah, pin 24 is the subcode input from the CD DSP and pin 26 is the clock output (driven by the MCU). The subcode format is somewhat complicated - there is one byte of subcode data per frame, and since this output only carries the "Q" subcode there are hence 98 bits per sector.

The first 2 bits are sync word flags, and are asserted if the sync words are found in the data (these are otherwise illegal EFM patterns, and will never be found in data).
Next byte is control/address - the control field holds flags for stuff like copy protect, data mode, etc, and the address field indicates the format of the rest of the data (it's normally 0001).
Next byte is track number (in BCD, 0xaa = in lead out)
Next byte is index (in BCD, 0x00 = in track gap)
Next 3 bytes are position in current track (MM:SS:FF, in BCD)
Next byte is zero
Next 3 bytes are absolute position on disc (MM:SS:FF, in BCD)
Last 2 bytes are a 16 bit CRCC code

Normally you would sync the subcode readout by waiting for the CD frame sync (SCOR - pin 31 on the MCU in the Playstation).

Note that these same lines also carry peak audio data and (on the later boards) SCEx data depending on the mode you are in, so to be 100 % sure of what you are looking at you also need to sniff the command signals (data / clk / xlat) on pins 43 to 45 of the MCU.

My gut feeling is that it's probably too much hassle to try and decode it...

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

Post by rama3 » May 9th, 2017, 11:42 pm

Definitely too much hassle. I can spot the SCEX commands in the regular controller data already. If anything, I'll be using that. Unless I happen to discover another really simple flag. The POS0 switch is already really good, if it wasn't so unreliable without the modification :p

With POS0, I get a reliable and very fast boot every time. Every antimod game I tried passes the checks.
It's definitely worth the effort to come up with a "deterministic" modchip!

User avatar
nocash
Verified
PSX Aficionado
PSX Aficionado
Posts: 541
Joined: Nov 12, 2012
Contact:

Post by nocash » May 11th, 2017, 9:40 pm

Come on, decoding 98 bits isn't that much hassle. But aside from the SPI "input" you might also need to wire SPI "clock" and "output", and yes, that could get a bit complicated, nobody wants modchips with dozens of pins & wires. It wouldn't look too elegant.

Logging the read scex command won't help you at all. It does just tell you when the console wants to read scex from the current sector, but it doesn't tell if the current sector is within the scex area or not. To know that, you must log all commands other than the read scex command. EDIT: Plus, of course, commands that do seek-and-read-scex, those are needed because of the seeking.

The POS0 taping trick makes me a bit worried. It's making the lead-in shorter, so the drive might become unable to read longer TOCs. Did you test if it's still able to read discs with 99 tracks?
Last edited by nocash on May 11th, 2017, 9:57 pm, edited 1 time in total.

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

Post by rama3 » May 11th, 2017, 9:49 pm

True, I didn't think of that. Too focused on spotting the crucial command itself :p

I have not tested large TOC discs but I can tell you that the manufacturing margin of error for these POS0 switches is quite large.
I've tested 3 different laser assemblies:
- one requires a thick, 3 layer tape
- one works with just one layer
- one doesn't need any tape at all!
The test was practical: The same modchip and console, but 3 laser assemblies. If an assembly passed the antimod check reliably, I tested the next one.

But I will check out discs with large TOCs. Any music disc should work, right?

User avatar
nocash
Verified
PSX Aficionado
PSX Aficionado
Posts: 541
Joined: Nov 12, 2012
Contact:

Post by nocash » May 11th, 2017, 10:10 pm

Okay, if you have one console that works without taping, then the taped consoles probably won't be worse than that console.

Yes, music discs should be best because they allow to actually test if the tracks are there. You may have a hard time finding a 99-track music disc, but you could burn one yourself with 99 short audio tracks.

As far as I remember, I've once spotted a PSX game with around 50 tracks, but don't remember which game. To find some, you could search for games with "large .cue files" (although that number is misleading because the .cue size also depends on the filename length etc). Of course it would be diffcult to test all tracks without playing through the whole game.

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

Post by rama3 » May 13th, 2017, 5:56 am

I'm slowly going over my modified consoles with a 99 track audio test CD. So far everything works :)

(On that note, do you want to know why the blue BIOS design is so kind of ugly? It was there already in 1994, with the first edition. Yuck :p )

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

Post by rama3 » May 15th, 2017, 8:56 pm

I managed to read Subchannel Q on the fly. Thanks for all the help to everyone involved :)

Code will be posted later. I just want to do another check with the scope, make sure it behaves.

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

Post by rama3 » May 16th, 2017, 5:18 am

Here is the new version!
So far it's only been tested with an Arduino Uno (clone), using a PU-18 PSX.

On the Uno, pin13 is hardwired to an LED. It is also the hardware SPI clock line, which I use. The LED blinks when uploading new sketches, resetting the Arduino or when connecting a serial monitor. The blinking current goes into the SCLK pin on the HC-05. It stuns the machine when on but doesn't appear to damage it. On the scope I saw something about 1.7V, coming from my 3.3V powered Uno.
I recommend going with an ATtiny. Once I get some of these, I'll adapt the code for it. ATtinys don't have extra components like LEDs ;)
Until then, if you use a regular Arduino, do not upload new sketches while the PSX is on.

It would be nice if you guys could take a look at the beginning of the loop() for some odd SPI behavior. Thanks! :)

Code: Select all

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

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

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

#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;     //Address 18; Only used in SCPH-102 PAL mode // ToDo: rearrange. This is the MOSI pin for SPI.
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

#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! // Sorry, couldn't resist
//--------------------------------------------------
//              Seperate functions!
//--------------------------------------------------
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;
}

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
    {
      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_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
    {
      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()
{
  //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)
  {
    if (SCEIData[bit_counter] == 0)
    {       
      pinMode(data, OUTPUT);
      digitalWrite(data, 0);
      delay(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);
    }
  }

  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()
{
  //NTSC_fix(); //needs rework, new pin assigments
 
  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 function - execution starts here!
//--------------------------------------------------
void setup()
{
  // Arduino docs say all INPUT pins are high impedence by default. Let's be explicit!
  pinMode(data, INPUT);
  pinMode(gate, INPUT);

  // 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
  
  SPCR = 0b01101100; // LSBFIRST, SPI_MODE3, SPI_CLOCK_DIV4, SPI enabled, Slave mode, CPOL = 1, CPHA = 1
  
  //inject_playstation(); // not required when watching subchannel data
#ifdef DEBUG
  Serial.begin (115200);   // debugging
  Serial.print("SPCR setup to ");
  Serial.println(SPCR, HEX);
#endif
  pos = 0;
  wobbleCounter = 0;
}

//----------------------------------------------------------------
//   Loop function - executes after the initial injection cycle
//----------------------------------------------------------------

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.
  
  while(!(SPSR & (1<<SPIF))); // wait for a byte
  
  byte c = SPDR;
  if (c == 0x41) {
    buf[0] = c; // Is this a psx data cd? if so, align buffer start to this!
    pos = 1;
    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);
      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--;
    }
  }
  
  if (wobbleCounter >= 3) {
    // Read head is in wobble area. Inject SCEX.
#ifdef DEBUG
    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.
    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;
  }
}

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

Post by rama3 » May 17th, 2017, 3:42 am

Well, this is annoying. I just learned that ATtiny25/45/85 do not support full hardware SPI. Instead they have a crippled module that cannot do LSBF or Mode3.
I think I've got to do full software decoding and then see if I can spot at least the right patterns.
Very annoying indeed :/

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

Post by rama3 » May 20th, 2017, 2:59 am

So I'm looking at about 120khz for the data stream. To make it compatible with more chips, I'm doing software decoding only. It's a close call for the 8Mhz chips but it should be doable. I have it working on 16Mhz already :)

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

Post by rama3 » May 23rd, 2017, 3:08 am

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! :)

Thanks wisi! :)

Code: Select all

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

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

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

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

#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;
}

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
    {
      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_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
    {
      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()
{
  //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)
  {
    if (SCEIData[bit_counter] == 0)
    {       
      pinMode(data, OUTPUT);
      digitalWrite(data, 0);
      delay(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);
    }
  }

  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()
{
  //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;
  
  Serial.begin (115200);
  Serial.print("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;
  // 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
}

#ifdef ARDUINO_UNO
  #define SUBQ_SDI_BITN 2
  #define SUBQ_SDI_BIT (1<<SUBQ_SDI_BITN)
#else
  // todo: attiny
#endif

void loop()
{
  unsigned int timeout_clock_low_counter = 0;
  unsigned int timeout_clock_high_counter = 0;
  
  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;
  }
  
  // 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;
  }

  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("");
  }
  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
    (scbuf[2] == 0xA0 || scbuf[2] == 0xA1 || scbuf[2] == 0xA2) ){ // lead in / wobble area is marked by 0xA0, 0xA1, 0xA2 

    Serial.println("Inject!");

    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)
    {
       inject_SCEI();
       //inject_SCEA();
       //inject_SCEE();
    }
    
    pinMode(gate, INPUT);
    pinMode(data, INPUT);
  }
}

Update:
Basic functionality works with modern (PU-22 and up) boards as well. No GATE connection necessary then but I'm still missing that functionality on the DATA pin. So originals won't work yet, only copies will.

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

Post by rama3 » May 31st, 2017, 10:14 am

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

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.

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

//#define PU22_MODE

#include <Flash.h> // requires Arduino Flash library installed

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

// 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
 
  for (byte bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1)
  {
    if (SCEEData[bit_counter] == 0)
    {
      bitClear(PORTB,0); // pull data low
      delayMicroseconds(delay_between_bits);
    }
    else
    {
      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
    }
  }

  bitClear(PORTB,0); // pull data low
  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
 
  for (byte bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1)
  {
    if (SCEAData[bit_counter] == 0)
    {
      bitClear(PORTB,0); // pull data low
      delayMicroseconds(delay_between_bits);
    }
    else
    {
      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
    }
  }

  bitClear(PORTB,0); // pull data low
  delay(delay_between_injections);
}

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
  
  for (byte bit_counter = 0; bit_counter < 44; bit_counter = bit_counter + 1)
  {
    if (SCEIData[bit_counter] == 0)
    {
      bitClear(PORTB,0); // pull data low
      delayMicroseconds(delay_between_bits);
    }
    else
    {
      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
    }
  }

  bitClear(PORTB,0); // pull data low
  delay(delay_between_injections);
}

//--------------------------------------------------
//     Setup
//--------------------------------------------------
void setup()
{
  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.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

  unsigned int timeout_clock_low_counter = 0;
  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, reset on timeout
      timeout_clock_low_counter++;
      if (timeout_clock_low_counter > TIMEOUT_CLOCK){
        scpos = 0;
        num_resets++;
        return;
      }
    }
    while (bitRead(PINB, 3)); // wait for clock to go low

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

    do {
      // 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;
  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.print(" resets:  ");
    Serial.println(num_resets);
    num_resets = 0;
    scpos = 0;
  }
  else return;
  
  // 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) ){ // lead in / wobble area is marked by 0xA0, 0xA1, 0xA2 
    
    Serial.println("INJECT!");

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

    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(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)


User avatar
nocash
Verified
PSX Aficionado
PSX Aficionado
Posts: 541
Joined: Nov 12, 2012
Contact:

Post by nocash » May 31st, 2017, 8:03 pm

Subchannel "Point" can be 01h..99h or A0h..A2h during Lead-In, 01h..99h are for the tracks, and on a disc with many tracks those are outnumbering the special A0h..A2h values, so you should accept 01h..99h, too. Would be better to verify the 16bit CRC for making sure that it's intact lead-in data.
Ah, or the A0h..A2h values occuring every some sectors, interleaved with the 01h..99h values?

Btw. as the newer consoles are using the SUBQ serial bus also for the encrypted SCEX string... can you record that string with your hardware? Currently it's only known how to decrypt that string, but not how the encrypted string looks like (it can have the bits here and there, before merging them to a final character byte), and it's having some "unused" bytes with unknown values - that unknown values might be interesting, ie. they might help to distinguish between SCEX transfers and SUBQ position data.

I like low-level, but encoding the ASCII characters as 1,0,0,1,1,0,1,0,1,0,0... is a bit too much low-level for me. Something like send_char("S"), send_char("C"), etc. could make it easier to figure out what the code is doing ; )

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

Post by rama3 » May 31st, 2017, 8:43 pm

The A0, A1, A2 marker is my strongest indicator for being in the wobble area. It would complicate things if I had to include 0x01 to 0x99. Do you have any suggestion for a test game? So far I've used maybe 20 games for tests and they consistently had an "Ax" marker.
Edit: But I did see games with an 0x01 before/after an "Ax" when in the wobble area. The "Ax" was always present though.

The code can record the SUBQ strings, yea :)
It might miss one occasionally though, as I simply drop the entire string on a clock timeout.
On an 16Mhz board, it should get them all anyway.
If you want to, I can hook up my Arduino Uno to a PU-23 and give you some logs!

I've used the original PsNee method for storing and decoding the SCEX string.
Didn't feel the need to redo that part :p
Requirements:
- be able to drag the line high / low depending on which bit to send
- uses Flash and RAM efficiently: There's just 256 bytes RAM / 4kB Flash in an ATtiny45, for example.
- one function for all 3 regions would be much nicer! :)

User avatar
nocash
Verified
PSX Aficionado
PSX Aficionado
Posts: 541
Joined: Nov 12, 2012
Contact:

Post by nocash » May 31st, 2017, 9:50 pm

Here's my pseudo for a send_char function http://problemkaputt.de/psx-spx.htm#cdr ... onmodchips the code shouldn't exceed your memory limits.

Wipeout XL has around 11 audio tracks, but well, with 75 subq packets per second that wouldn't cause too much of a delay before you see a point value with A0h..A2h. It's just that discs could theoretically have up to 99 tracks, and then it could take more than a second.

Yes, logs would be great! Though, to see the SCEX packets you would either need a unmodded console (and a licensed disc), or you would need to output the SCEX string simultaneously with the logging (might be difficult as it would require some sort multitasking).

For the newer boards, you have an INPUT for WFCK, which could be theoretically replaced by a software timer (unless it works better with WFCK for some reason, like needing a time/clock signal that is in sync with the cdrom data).
And, as far as I understood, you have only one OUTPUT for a merged DATA+SYNC signal? What I still don't get is how that's wired to the board... do you have two wires on that pin, going to DATA and SYNC on mainboard? Or do you have only one wire... and then, where is it wired to?

Btw. if one could get away without the WFCK input, and needs only one pin for DATA+SYNC, could one also do that on older boards? So one would need only one pin (plus supply pins)?

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

Post by rama3 » May 31st, 2017, 10:16 pm

I require at least SUBQ, SCLK, Data and 2 supply pins. Only on PU-22 and above, I require the WFCK pin as well.
Supply *has to be* 3.5V for now, as I have a logic high output into the HC-05 in this code and that chip has 3.5V I/O.
Image

It works by cutting the data trace to the left of the Data connection point in this picture or lifting the pin on the HC-05.
This silences the original data from any disc and lets the modchip do all the work. I still need to decide if I want to require the trace cut, or go back to the alternative with a Gate wire.
Both methods have benefits and drawbacks:
- Cutting the trace has the lowest power consumption and highest compatibility but people don't like trace cutting much.
- A Gate wire avoids trace cutting but has higher power consumption when it drags the original signal low. Also that has yet unknown effects on the components that produce the original signal.
Every common modchip has done this for years though, so it should be at least safe.

Also on the boards I've tested so far, the data line is by default pulled high, even when the trace is cut.
If that's true for all boards, the modchip won't have to send a logic high anymore, further reducing potential issues.
For the newer boards, you have an INPUT for WFCK, which could be theoretically replaced by a software timer (unless it works better with WFCK for some reason, like needing a time/clock signal that is in sync with the cdrom data).
Yes, a software timer signal also works but is less reliable. The WFCK signal is really nice in that it perfectly masks original bits when a logic high is required.
And, as far as I understood, you have only one OUTPUT for a merged DATA+SYNC signal? What I still don't get is how that's wired to the board... do you have two wires on that pin, going to DATA and SYNC on mainboard? Or do you have only one wire... and then, where is it wired to?
Note: This explanation is strictly for PU-22+ boards. They also call their connection point "DATA" but as you know, it is entirely different from the old one.
One wire connects to DATA and Arduino pin 8, one wire connects to WFCK and Arduino pin 12.
When it's time to output a logic high on the data pin, I sample WFCK and output the result on DATA.
Due to the high'ish speed and analog filters on the board, the mixed signal eliminates original discs signals and gets interpreted as a logic high by the controller.
(For a logic low, I simply drag the line down as usual.)

As you can see, I have a couple decisions to make. I want to try and balance accuracy, safety, development effort, installation effort and cost.
I'm grateful for any improvements and suggestions :)

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

Post by rama3 » May 31st, 2017, 10:37 pm

Yes, logs would be great! Though, to see the SCEX packets you would either need a unmodded console (and a licensed disc), or you would need to output the SCEX string simultaneously with the logging (might be difficult as it would require some sort multitasking).
Here's what I'll do:
- hook up a PU-22 with expansion port
- disable the SCEX injection in my code (so the code just logs the SUBQ stream to tty)
- attach a "disc swap" expansion cart
- use an original disc for the console region
The expansion cart rom stops the disc as soon as the HC-05 receives a valid signal and unlocks the drive.
That isolates the SUBQ stream to just the essential stuff we're looking for :)

Post Reply

Who is online

Users browsing this forum: No registered users and 5 guests