Skip to content

Latest commit

 

History

History
2111 lines (1644 loc) · 80.5 KB

File metadata and controls

2111 lines (1644 loc) · 80.5 KB

Cdromdrive

Source: https://problemkaputt.de/psx-spx.htm Section: Cdromdrive

CDROM Drive Playstation CDROM I/O Ports

CDROM Controller I/O Ports

Playstation CDROM Commands

CDROM Controller Command Summary

CDROM - Control Commands

CDROM - Seek Commands

CDROM - Read Commands

CDROM - Status Commands

CDROM - CD Audio Commands

CDROM - Test Commands

CDROM - Secret Unlock Commands

CDROM - Video CD Commands

CDROM - Mainloop/Responses

CDROM - Response Timings

CDROM - Response/Data Queueing

General CDROM Disk Format

CDROM Disk Format

CDROM Subchannels

CDROM Sector Encoding

CDROM Scrambling

CDROM XA Subheader, File, Channel, Interleave

CDROM XA Audio ADPCM Compression

CDROM ISO Volume Descriptors

CDROM ISO File and Directory Descriptors

CDROM ISO Misc

CDROM File Formats

CDROM Video CDs (VCD)

Playstation CDROM Protection

CDROM Protection - SCEx Strings

CDROM Protection - Bypassing it

CDROM Protection - Modchips

CDROM Protection - Chipless Modchips

CDROM Protection - LibCrypt

General CDROM Disk Images

CDROM Disk Images CCD/IMG/SUB (CloneCD)

CDROM Disk Images CDI (DiscJuggler)

CDROM Disk Images CUE/BIN/CDT (Cdrwin)

CDROM Disk Images MDS/MDF (Alcohol 120%)

CDROM Disk Images NRG (Nero)

CDROM Disk Images PBP (Sony)

CDROM Disk Images CHD (MAME)

CDROM Disk Image/Containers CDZ

CDROM Disk Image/Containers ECM

CDROM Subchannel Images

CDROM Disk Images Other Formats

Playstation CDROM Coprocessor

CDROM Internal Info on PSX CDROM Controller

CDROM Controller Command Summary

Command Summary

Command          Parameters      Response(s)
00h -            -               INT5(11h,40h)  ;reportedly "Sync" uh?
01h Getstat      -               INT3(stat)
02h Setloc     E amm,ass,asect   INT3(stat)
03h Play       E (track)         INT3(stat), optional INT1(report bytes)
04h Forward    E -               INT3(stat), optional INT1(report bytes)
05h Backward   E -               INT3(stat), optional INT1(report bytes)
06h ReadN      E -               INT3(stat), INT1(stat), datablock
07h MotorOn    E -               INT3(stat), INT2(stat)
08h Stop       E -               INT3(stat), INT2(stat)
09h Pause      E -               INT3(stat), INT2(stat)
0Ah Init         -               INT3(late-stat), INT2(stat)
0Bh Mute       E -               INT3(stat)
0Ch Demute     E -               INT3(stat)
0Dh Setfilter  E file,channel    INT3(stat)
0Eh Setmode      mode            INT3(stat)
0Fh Getparam     -               INT3(stat,mode,null,file,channel)
10h GetlocL    E -               INT3(amm,ass,asect,mode,file,channel,sm,ci)
11h GetlocP    E -               INT3(track,index,mm,ss,sect,amm,ass,asect)
12h SetSession E session         INT3(stat), INT2(stat)
13h GetTN      E -               INT3(stat,first,last)  ;BCD
14h GetTD      E track (BCD)     INT3(stat,mm,ss)       ;BCD
15h SeekL      E -               INT3(stat), INT2(stat)  ;\use prior Setloc
16h SeekP      E -               INT3(stat), INT2(stat)  ;/to set target
17h -            -               INT5(11h,40h)  ;reportedly "SetClock" uh?
18h -            -               INT5(11h,40h)  ;reportedly "GetClock" uh?
19h Test         sub_function    depends on sub_function (see below)
1Ah GetID      E -               INT3(stat), INT2/5(stat,flg,typ,atip,"SCEx")
1Bh ReadS      E?-               INT3(stat), INT1(stat), datablock
1Ch Reset        -               INT3(stat), Delay            ;-not DTL-H2000
1Dh GetQ       E adr,point       INT3(stat), INT2(10bytesSubQ,peak_lo) ;\not
1Eh ReadTOC      -               INT3(late-stat), INT2(stat)           ;/vC0
1Fh VideoCD      sub,a,b,c,d,e   INT3(stat,a,b,c,d,e)   ;"      INT5(11h,40h)  ;
56h Secret 7     -               INT5(11h,40h)  ;/
57h SecretLock   -               INT5(11h,40h)  ;-Secret Lock Command
58h..5Fh Crash   -               Crashes the HC05 (jumps into a data area)
6Fh..FFh -       -               INT5(11h,40h)  ;-Unused/invalid

E = Error 80h appears on some commands (02h..09h, 0Bh..0Dh, 10h..16h, 1Ah, 1Bh?, and 1Dh) when the disk is missing, or when the drive unit is disconnected from the mainboard.

Some commands (04h,05h,10h,11h,1Dh) do also trigger Error 80h when the disk is stopped.

sub_function numbers (for command 19h)

Test commands are invoked with command number 19h, followed by a sub_function number as first parameter byte. The Kernel seems to be using only sub_function 20h (to detect the CDROM Controller version).

sub  params  response           ;Effect
00h      -   INT3(stat)         ;Force motor on, clockwise, even if door open
01h      -   INT3(stat)         ;Force motor on, anti-clockwise, super-fast
02h      -   INT3(stat)         ;Force motor on, anti-clockwise, super-fast
03h      -   INT3(stat)         ;Force motor off (ignored during spin-up)
04h      -   INT3(stat)         ;Start SCEx reading and reset counters
05h      -   INT3(total,success);Stop SCEx reading and get counters
06h *    n   INT3(old)  ;\early ;Adjust balance in RAM, send CX(30+n XOR 7)
07h *    n   INT3(old)  ; PSX   ;Adjust gain in RAM, send CX(38+n XOR 7)
08h *    n   INT3(old)  ;/only  ;Adjust balance in RAM only
06h..0Fh -   INT5(11h,10h)      ;N/A (11h,20h when NONZERO number of params)
10h      -   INT3(stat) ;CX(..) ;Force motor on, anti-clockwise, super-fast
11h      -   INT3(stat) ;CX(03) ;Move Lens Up (leave parking position)
12h      -   INT3(stat) ;CX(02) ;Move Lens Down (enter parking position)
13h      -   INT3(stat) ;CX(28) ;Move Lens Outwards
14h      -   INT3(stat) ;CX(2C) ;Move Lens Inwards
15h      -   INT3(stat) ;CX(22) ;If motor on: Move outwards,inwards,motor off
16h      -   INT3(stat) ;CX(23) ;No effect?
17h      -   INT3(stat) ;CX(E8) ;Force motor on, clockwise, super-fast
18h      -   INT3(stat) ;CX(EA) ;Force motor on, anti-clockwise, super-fast
19h      -   INT3(stat) ;CX(25) ;No effect?
1Ah      -   INT3(stat) ;CX(21) ;No effect?
1Bh..1Fh -   INT5(11h,10h)      ;N/A (11h,20h when NONZERO number of params)
20h      -   INT3(yy,mm,dd,ver) ;Get cdrom BIOS date/version (yy,mm,dd,ver)
21h      -   INT3(n)            ;Get Drive Switches (bit0=POS0, bit1=DOOR)
22h ***  -   INT3("for ...")    ;Get Region ID String
23h ***  -   INT3("CXD...")     ;Get Chip ID String for Servo Amplifier
24h ***  -   INT3("CXD...")     ;Get Chip ID String for Signal Processor
25h ***  -   INT3("CXD...")     ;Get Chip ID String for Decoder/FIFO
26h..2Fh -   INT5(11h,10h)      ;N/A (11h,20h when NONZERO number of params)
30h *    i,x,y     INT3(stat)       ;Prototype/Debug stuff   ;\supported on
31h *    x,y       INT3(stat)       ;Prototype/Debug stuff   ; early PSX only
4xh *    i         INT3(x,y)        ;Prototype/Debug stuff   ;/
30h..4Fh ..        INT5(11h,10h)    ;N/A always 11h,10h (no matter of params)
50h      a[,b[,c]] INT3(stat)       ;Servo/Signal send CX(a:b:c)
51h **   39h,xx    INT3(stat,hi,lo) ;Servo/Signal send CX(39xx) with response
51h..5Fh -         INT5(11h,10h)    ;N/A
60h      lo,hi     INT3(databyte)   ;HC05 SUB-CPU read RAM and I/O ports
61h..70h -         INT5(11h,10h)    ;N/A
71h ***  adr       INT3(databyte)   ;Decoder Read one register
72h ***  adr,dat   INT3(stat)       ;Decoder Write one register
73h ***  adr,len   INT3(databytes..);Decoder Read multiple registers, bugged
74h ***  adr,len,..INT3(stat)       ;Decoder Write multiple registers, bugged
75h ***  -         INT3(lo,hi,lo,hi);Decoder Get Host Xfer Info Remain/Addr
76h ***  a,b,c,d   INT3(stat)       ;Decoder Prepare Transfer to/from SRAM
77h..FFh -         INT5(11h,10h)    ;N/A
80h..8Fh a,b       ?                ;seem to do something on PS2
  • sub_functions 06h..08h, 30h..31h, and 4xh are supported only in vC0 and vC1.

** sub_function 51h is supported only in BIOS version vC2 and up.

*** sub_functions 22h..25h, 71h..76h supported only in BIOS version vC1 and up.

Unsupported GetQ,VCD,SecretUnlock (command 1Dh,1Fh,5xh)

INT5 will be returned if the command is unsupported. That, WITHOUT removing the Parameters from the FIFO, so the parameters will be accidently passed to the NEXT command. To avoid that: clear the parameter FIFO via [1F801803h.Index1]=40h after receiving the INT5 error.

CDROM - Seek Commands

Setloc - Command 02h,amm,ass,asect --> INT3(stat)

Sets the seek target - but without yet starting the seek operation. The actual seek is invoked by certain commands: SeekL (Data) and SeekP (Audio) are doing plain seeks (and do Pause after completion). ReadN/ReadS are similar to SeekL (and do start reading data after the seek operation). Play is similar to SeekP (and does start playing audio after the seek operation).

The amm,ass,asect parameters refer to the entire disk (not to the current track). To seek to a specific location within a specific track, use GetTD to get the start address of the track, and add the desired time offset to it.

SeekL - Command 15h --> INT3(stat) --> INT2(stat)

Seek to Setloc's location in data mode (using data sector header position data, which works/exists only on Data tracks, not on CD-DA Audio tracks).

After the seek, the disk stays on the seeked location forever (namely: when seeking sector N, it does stay at around N-8..N-0 in single speed mode, or at around N-5..N+2 in double speed mode).

Trying to use SeekL on Audio CDs passes okay on the first response, but (after two seconds or so) the second response will return an error (stat+4,04h), and stop the drive motor... that error doesn't appear ALWAYS though... works in some situations... such like when previously reading data sectors or so...?

SeekP - Command 16h --> INT3(stat) --> INT2(stat)

Seek to Setloc's location in audio mode (using the Subchannel Q position data, which works on both Audio on Data disks).

After the seek, the disk stays on the seeked location forever (namely: when

seeking sector N, it does stay at around N-9..N-1 in single speed mode, or at around N-2..N in double speed mode).

Note: Some older docs claim that SeekP would recurse only "MM:SS" of the "MM:SS:FF" position from Setloc - that is wrong, it does seek to MM:SS:FF (verified on a PSone).

After the seek, status is stat.bit7=0 (ie. audio playback off), until sending a new Play command (without parameters) to start playback at the seeked location.

SetSession - Command 12h,session --> INT3(stat) --> INT2(stat)

Seeks to session (ie. moves the drive head to the session, with stat bit6 set during the seek phase).

When issued during active-play, the command returns error code 80h.

When issued during play-spin-up, play is aborted.

___Errors___
session = 00h causes error code 10h.     ;INT5(03h,10h), no 2nd/3rd response
___On a non-multisession-disk___
session = 01h passes okay.               ;INT3(stat), and once INT2(stat)
session = 02h or higher cause seek error ;INT3(stat), and twice INT5(06h,40h)
___On a multisession-disk with N sessions___
session = 01h..N+1 passes okay   ;where N+1 moves to the END of LAST session
session = N+2 or higher cause seek error  ;2nd response = INT5(06h,20h)

after seek error --> disk stops spinning at 2nd response, then restarts spinning for 1 second or so, then stops spinning forever... and following gettn/gettd/getid/getlocl/getlocp fail with error 80h...

The command does automatically read the TOC of the new session. BUG: Older CD Firmwares (16 May 1995 and older) don't clear the old TOC when loading Session 1, in that case SetSession(1) may update some (not all) TOC entries; ending up with a mixup of old and new TOC entries.

There seems to be no way to determine the current sessions number (via Getparam or so), and more important, no way to determine if the disk is a multi-session disk or not... except by trial... which would stop the drive motor on seek errors on single-session disks...?

For setloc, one must probably specifiy minutes within the 1st track of the new session (the 1st track of 1st session usually/always starts at 00:02:00, but for other sessions one would need to use GetTD)...?

CDROM - Status Commands

Status code (stat)

The 8bit status code is returned by Getstat command (and many other commands), the meaning of the separate stat bits is:

7  Play          Playing CD-DA         ;\only ONE of these bits can be set
6  Seek          Seeking               ; at a time (ie. Read/Play won't get
5  Read          Reading data sectors  ;/set until after Seek completion)
4  ShellOpen     Once shell open (0=Closed, 1=Is/was Open)
3  IdError       (0=Okay, 1=GetID denied) (also set when Setmode.Bit4=1)
2  SeekError     (0=Okay, 1=Seek error)     (followed by Error Byte)
1  Spindle Motor (0=Motor off, or in spin-up phase, 1=Motor on)
0  Error         Invalid Command/parameters (followed by Error Byte)

If the shell is closed, then bit4 is automatically reset to zero after reading stat with the Getstat command (most or all other commands do not reset that bit after reading). If stat bit0 or bit2 is set, then the normal respons(es) and interrupt(s) are not send, and, instead, INT5 occurs, and an error-byte is send as second response byte, with the following values:

___These values appear in the FIRST response; with stat.bit0 set___
10h - Invalid Sub_function (for command 19h), or invalid parameter value
20h - Wrong number of parameters
40h - Invalid command
80h - Cannot respond yet (eg. required info was not yet read from disk yet)
(namely, TOC not-yet-read or so)
(also appears if no disk inserted at all)
___These values appear in the SECOND response; with stat.bit2 set___
04h - Seek failed (when trying to use SeekL on Audio CDs)
___These values appear even if no command was sent; with stat.bit2 set___
08h - Drive door became opened

80h appears on some commands (02h..09h, 0Bh..0Dh, 10h..16h, 1Ah, 1Bh?, and 1Dh) when the disk is missing, or when the drive unit is disconnected from the mainboard.

Stat Seek/Play/Read bits

There's is only max ONE of the three Seek/Play/Read bits set at a time, ie. during Seek, ONLY the seek bit is set (and Read or Play doesn't get until seek completion), that is important for Gran Turismo 1, which checks for seek completion by waiting for READ getting set (rather than waiting for SEEK getting cleared).

Getstat - Command 01h --> INT3(stat)

Returns stat (like many other commands), and additionally does reset the shell open flag (for the following commands; unless the shell is still opened). This is different as for most or all other commands (which may return stat, but which do not reset the shell open flag).

In other docs, the command is eventually referred to as "Nop", believing that it does nothing than returning stat (ignoring the fact that it's having the special shell open reset feature).

Getparam - Command 0Fh --> INT3(stat,mode,null,file,channel)

Returns stat (see Getstat above), mode (see Setmode), a null byte (always 00h), and file/channel filter values (see Setfilter).

GetlocL - Command 10h --> INT3(amm,ass,asect,mode,file,channel,sm,ci)

Retrieves 4-byte sector header, plus 4-byte subheader of the current sector. GetlocL can be send during active Read commands (but, mind that the GetlocL-INT3-response can't be received until any pending Read-INT1's are acknowledged).

The PSX hardware can buffer a handful of sectors, the INT1 handler receives the buffered sector, the GetlocL command returns the header and subheader of the buffered sector. Note: If the returned sector number is much bigger than the expected sector number, then it's likely that a buffer overrun has occured.

GetlocL fails (with error code 80h) when playing Audio CDs (or Audio Tracks on Data CDs). These errors occur because Audio sectors don't have any header/subheader (instead, equivalent data is stored in Subchannel Q, which can be read with GetlocP).

GetlocL also fails (with error code 80h) when the drive is in Seek phase (such like shortly after a new ReadN/ReadS command). In that case one can retry issuing GetlocL (until it passes okay, ie. until the seek has completed). During Seek, the drive seems to decode only Subchannel position data (but no header/subheader data), accordingly GetlocL won't work during seek (however, GetlocP does work during Seek).

GetlocP - Command 11h - INT3(track,index,mm,ss,sect,amm,ass,asect)

Retrieves 8 bytes of position information from Subchannel Q with ADR=1. Mainly intended for displaying the current audio position during Play. All results are in BCD.

track:  track number (AAh=Lead-out area) (FFh=unknown, toc, none?)
index:  index number (Usually 01h)
mm:     minute number within track (00h and up)
ss:     second number within track (00h to 59h)
sect:   sector number within track (00h to 74h)
amm:    minute number on entire disk (00h and up)
ass:    second number on entire disk (00h to 59h)
asect:  sector number on entire disk (00h to 74h)

Note: GetlocP is also used for reading the LibCrypt protection data:

CDROM Protection - LibCrypt

GetTN - Command 13h --> INT3(stat,first,last) ;BCD

Get first track number, and last track number in the TOC of the current Session. The number of tracks in the current session can be calculated as (last-first+1). The first track number is usually 01h in the first (or only) session, and "last track of previous session plus 1" in further sessions.

GetTD - Command 14h,track --> INT3(stat,mm,ss) ;BCD

For a disk with NN tracks, parameter values 01h..NNh return the start of the specified track, parameter value 00h returns the end of the last track, and parameter values bigger than NNh return error code 10h.

The GetTD values are relative to Index=1 and are rounded down to second boundaries (eg. if track=N Index=0 starts at 12:34:56, and Track=N Index=1 starts at 12:36:56, then GetTD(N) will return 12:36, ie. the sector number is truncated, and the Index=0 region is skipped).

GetQ - Command 1Dh,adr,point --> INT3(stat) --> INT2(10bytesSubQ,peak_lo)

Caution: Supported only in BIOS version vC1 and up. Not supported in vC0.
Caution: When unsupported, Parameter Fifo isn't cleared after the command.

Allows to read 10 bytes from Subchannel Q in Lead-In (see CDROM Subchannels chapter for details). Unlike GetTD, this command allows to receive the exact MM:SS:FF address of the point'ed Track (GetTD reads a memorized MM:SS value from RAM, whilst GetQ reads the full MM:SS:FF from the disk, which is slower than GetTD, due to the disk-access).

With ADR=1, point can be a any point number for ADR=1 in Lead-in (eg. 01h..99h=Track N, A2h=Lead-Out). The returned 10 bytes are raw SubQ data (starting with the ADR/Control value; of which the lower 4bits are always ADR=1).

The 11th returned byte is the Peak LSB (similar as in Play+Report, but in this case only the LSB is transferred, which is apparently a bug in CDROM BIOS, the programmer probably wanted to send 10 bytes without peak, or 12 bytes with full peak; although peak wouldn't be too useful, as it should always zero during Lead-In... but some discs do seem return non-zero values for whatever reason).

Aside from ADR=1, a value of ADR=5 can be used on multisession disks (eg. with point B0h, C0h). Not sure if any other ADR values can be used (ADR=3, ISRC is usually not in the Lead-In, ADR=2, EAN may be in the lead-in, but one may need to specify point equal to the first EAN byte).

If the ADR/Point combination isn't found, then a timeout occurs after circa 6 seconds (to avoid this, use GetTN to see which tracks/points exist). After the timeout, the command starts playing track 1. If the controller wasn't already in audio mode before sending the command, then it does switch off the drive motor for a moment (that, after the timeout, and before starting playback).

In case of timeout, the normal INT3/INT2 responses are replaced by INT3/INT5/INT5 (INT3 at command start, 1st INT5 at timeout/stop, and 2nd INT5 at restart/play).

Note: GetQ sends scratch noise to the SPU while seeking to the Lead-In area.

GetID - Command 1Ah --> INT3(stat) --> INT2/5 (stat,flags,type,atip,"SCEx")

Drive Status           1st Response   2nd Response
Door Open              INT5(11h,80h)  N/A
Spin-up                INT5(01h,80h)  N/A
Detect busy            INT5(03h,80h)  N/A
No Disk                INT3(stat)     INT5(08h,40h, 00h,00h, 00h,00h,00h,00h)
Audio Disk             INT3(stat)     INT5(0Ah,90h, 00h,00h, 00h,00h,00h,00h)
Unlicensed:Mode1       INT3(stat)     INT5(0Ah,80h, 00h,00h, 00h,00h,00h,00h)
Unlicensed:Mode2       INT3(stat)     INT5(0Ah,80h, 20h,00h, 00h,00h,00h,00h)
Unlicensed:Mode2+Audio INT3(stat)     INT5(0Ah,90h, 20h,00h, 00h,00h,00h,00h)
Debug/Yaroze:Mode2     INT3(stat)     INT2(02h,00h, 20h,00h, 20h,20h,20h,20h)
Licensed:Mode2         INT3(stat)     INT2(02h,00h, 20h,00h, 53h,43h,45h,4xh)
Modchip:Audio/Mode1    INT3(stat)     INT2(02h,00h, 00h,00h, 53h,43h,45h,4xh)

The status byte (ie. the first byte in the responses), may differ in some cases; values shown above are typically received when issuing GetID shortly after power-up; however, shortly after the detect-busy phase, seek-busy flag (bit6) bit may be set, and, after issuing commands like Play/Read/Stop, bit7,6,5,1 may differ. The meaning of the separate 2nd response bytes is:

1st byte: stat  (as usually, but with bit3 same as bit7 in 2nd byte)
2nd byte: flags (bit7=denied, bit4=audio... or reportedly import, uh?)
bit7: Licensed (0=Licensed Data CD, 1=Denied Data CD or Audio CD)
bit6: Missing  (0=Disk Present, 1=Disk Missing)
bit4: Audio CD (0=Data CD, 1=Audio CD) (always 0 when Modchip installed)
3rd byte: Disk type (from TOC Point=A0h) (eg. 00h=Audio or Mode1, 20h=Mode2)
4th byte: Usually 00h (or 8bit ATIP from Point=C0h, if session info exists)
that 8bit ATIP value is taken form the middle 8bit of the 24bit ATIP value
5th-8th byte: SCEx region (eg. ASCII "SCEE" = Europe) (0,0,0,0 = Unlicensed)

The fourth letter of the "SCEx" string contains region information: "SCEI" (Japan/NTSC), "SCEA" (America/NTSC), "SCEE" (Europe/PAL). The "SCEx" string is displayed in the intro, and the PSX refuses to boot if it doesn't match up for the local region.

With a modchip installed, the same response is sent for Mode1 and Audio disks (except for Audio disks with very short TOCs (eg. singles) because SCEX reading is aborted immediately after reading all TOC entries on Audio disks); whether it is Audio or Mode1 can be checked by examining Subchannel Q ADR/Control.Bit6 (eg. via command 19h,60h,50h,00h).

Yaroze does return "SCEA" for SCEA discs, but, for SCEI,SCEE,SCEW discs it does return four ASCII spaces (20h).

CDROM - Test Commands

CDROM - Test Commands - Version, Switches, Region, Chipset, SCEx

CDROM - Test Commands - Test Drive Mechanics

CDROM - Test Commands - Prototype Debug Transmission

CDROM - Test Commands - Read/Write Decoder RAM and I/O Ports

CDROM - Test Commands - Read HC05 SUB-CPU RAM and I/O Ports

CDROM - Test Commands - Test Drive Mechanics

Signal Processor and Servo Amplifier

19h,50h,msb[,mid,[lsb[,xlo]]] --> INT3(stat)

Sends an 8bit/16bit/24bit command to the hardware, depending on number of parameters:

1 byte  --> send CX(Xx)       ;short 8bit command
2 bytes --> send CX(Xxxx)     ;longer 16bit command
3 bytes --> send CX(Xxxxxx)   ;full 24bit command
4 bytes --> send CX(Xxxxxxxx) ;extended 32bit command (BIOS vC3 only)
4..15 bytes: acts same as max (3 or 4 bytes) (extra bytes are ignored)
0 bytes or more than 15 bytes: generates an error

19h,51h,msb[,mid,[lsb]] --> INT3(stat,hi,lo) ;BIOS vC2/vC3 only

Supported by newer CDROM BIOSes only (such that use CXD2545Q or newer chips).

Works same as 19h,50h, but does additionally receive a response.

The command is always sending a 24bit CX(Xxxxxx) command, but it doesn't verify the number of parameter bytes (when using more than 3 bytes: extra bytes are ignored, when using less than 3 bytes: garbage is appended, which is somewhat valid because 8bit/16bit commands can be padded to 24bit size by appending "don't care" bits).

The command can be used to send any CX(..) command, but actually it does make sense only for the get-status commands, see below "19h,51h,39h,xxh" description.

19h,51h,39h,xxh --> INT3(stat,hi,lo) ;BIOS vC2/vC3 only

Supported by newer CDROM BIOSes only (such that use CXD2545Q or newer chips).

Sends CX(39xx) to the hardware, and receives a response (the response.hi byte is usually 00h for 8bit responses, or 00h..01h for 9bit responses). For example, this can be used to dump the Coefficient RAM.

19h,03h --> INT3(stat) ;force motor off

Forces the motor to stop spinning (ignored during spin-up phase).

19h,17h --> INT3(stat) ;force motor on, clockwise, super-fast

19h,01h --> INT3(stat) ;force motor on, anti-clockwise, super-fast

19h,02h --> INT3(stat) ;force motor on, anti-clockwise, super-fast

19h,10h --> INT3(stat) ;force motor on, anti-clockwise, super-fast

19h,18h --> INT3(stat) ;force motor on, anti-clockwise, super-fast

Forces the drive motor to spin at maximum speed (which is much faster than normal or double speed), in normal (clockwise), or reversed (anti-clockwise) direction. The commands work even if the shell is open. The commands do not try to synchronize the motor with the data on the disk (and do thus work even if no disk is inserted).

19h,00h --> INT3(stat) ;force motor on, clockwise (even if shell open)

This command seems to have effect only if the drive motor was off. If it was off, it does FFh-fills the TOC entries in RAM, and seek to the begin of the TOC at 98:30:00 or so (where minute=98 means minus two). From that location, it follows the spiral on the disk, although it does occassionally jump back some seconds. After clearing the TOC, the command does not write new data to the TOC buffer in RAM.

Note: Like 19h,04h, this command forces the drive motor to spin at standard speed (synchronized with the data on the disk), works even if the shell is open (but stops spinning after a while if the drive is empty).

19h,11h --> INT3(stat) ;Move Lens Up (leave parking position)

19h,12h --> INT3(stat) ;Move Lens Down (enter parking position)

19h,13h --> INT3(stat) ;Move Lens Outwards (away from center of disk)

19h,14h --> INT3(stat) ;Move Lens Inwards (towards center of disk)

Moves the laser lens. The inwards/outwards commands do move ONLY the lens (ie. unlike as for Seek commands, the overall-laser-unit remains in place, only the lens is moved).

19h,15h - if motor on: move head outwards + inwards + motor off

Moves the drive head to outer-most and inner-most position. Note that the drive doesn't have a switch that'd tell the controller when it has reached the outer-most position (so it'll forcefully hit against the outer edge) (ie. using this command too often may destroy the drive mechanics).

Note: The same destructive hit-outer-edge effect happens when using Setloc/Seek with too large values (like minute=99h).

19h,16h --> INT3(stat) ;Unknown / makes some noise if motor is on

19h,19h --> INT3(stat) ;Unknown / no effect

19h,1Ah --> INT3(stat) ;Unknown / makes some noise if motor is on

Seem to have no effect?

19h,16h seems to Move Lens Inwards, too.

19h,06h,new --> INT3(old) ;Adjust balance in RAM, and apply it via CX(30+n)

19h,07h,new --> INT3(old) ;Adjust gain in RAM, and apply it via CX(38+n)

19h,08h,new --> INT3(old) ;Adjust balance in RAM only

These commands are supported only by older CDROM BIOS versions (those with CXA1782BR Servo Amplifier).

Later BIOSes will respond with INT5(11h,20h) when trying to use these commands (because CXD2545Q and later Servo Amplifiers don't support the CX(30/38+n) commands).

CDROM - Test Commands - Read/Write Decoder RAM and I/O Ports

Caution: Below commands 19h,71h..76h are supported only in BIOS version vC1 and up. Not supported in vC0.

19h,71h,index --> INT3(databyte) ;Read single register

index can be 00h..1Fh, bigger values seem to be mirrored to "index AND 1Fh", with one exception: index 13h in NOT mirrored, instead, index 33h, 53h, 93h, B3h, D3h, F3h return INT5(stat+1,10h), and index 73h returns INT5(stat+1,20h).

Aside from returning a value, the commands seem to DO something (like moving the drive head when a disk is inserted). Return values are usually:

index     value
00h       04h      ;04h=empty, 8Eh=licensed, 24h=audio
01h       [0B1h]   ;DCh=empty/licensed, DDh=audio
02h       00h
03h       00h          ;or variable when disk inserted
04h       00h
05h       80h          ;or 86h or 89h when disk inserted
06h       C0h
07h       02h
08h       8Ah
09h       C0h
0Ah       00h
0Bh       C0h
0Ch       [1F2h]
0Dh       [1F3h]
0Eh       00h          ;or 8Eh or E6h when disk inserted    ;D4h/audio
0Fh       00h          ;or sometimes 01h when disk inserted ;50h/audio
10h       C0h
11h       E0h
12h       71h
13h       stat
14h       FFh
15h..1Fh  C0h-filled        ;or 17h --> DEh

19h,72h,index,databyte --> INT3(stat) ;Write single register

;other response on param xx16h,xx18h with xx>00h

19h,73h,index,len --> INT3(databytes...) ;Read multiple registers (bugged)

19h,74h,index,len,databytes --> INT3(stat) ;Write multiple registers (bugged)

Same as read/write single register, but trying to transfer multiple registers at once. BUG: The transfer should range from 00h to len-1, but the loop counter is left uninitialized (set to X=48h aka "command number 19h-minus-1-mul-2" instead of X=00h). Causing to the function to read/write garbage at index 48h..FFh, it does then wrap to 00h and do the correct intended transfer, but the preceeding bugged part may have smashed RAM or I/O ports.

19h,75h --> INT3(remain.lo,remain.hi,addr.lo,addr.hi) ;Get Host Xfer Info

Returns a 4-byte value. In my early tests, on the first day it returned B1h,CEh,4Ch,01h, on the next day 2Ch,E4h,95h,D5h, and on all following days 00h,C0h,00h,00h (no idea why/where the earlier values came from).

The first byte seems to be always 00h; no matter of [1F0h].

The second byte seems to be always C0h; no matter of [1F1h].

The third,fourth bytes are [1F2h,1F3h].

That two bytes are 0Ch,08h after Read commands.

The first bytes are NOT affected by:
destroying [1F0h] via too-many-parameters in command-buffer,
changes to [1F1h] which may occur after read command (eg. may be 20h)

19h,76h,len_lo,len_hi,addr_lo,addr_hi --> INT3(stat) ;Prepare SRAM Transfer

Prepare Transfer to/from 32K SRAM.

After INT3, data can be read (same way as sector data after INT1).

CDROM - Secret Unlock Commands

SecretUnlockPart1 - Command 50h --> INT5(11h,40h)

SecretUnlockPart2 - Command 51h,"Licensed by" --> INT5(11h,40h)

SecretUnlockPart3 - Command 52h,"Sony" --> INT5(11h,40h)

SecretUnlockPart4 - Command 53h,"Computer" --> INT5(11h,40h)

SecretUnlockPart5 - Command 54h,"Entertainment" --> INT5(11h,40h)

SecretUnlockPart6 - Command 55h, --> INT5(11h,40h)

SecretUnlockPart7 - Command 56h --> INT5(11h,40h)

Caution: Supported only in BIOS version vC1 and up. Not supported in vC0.
Caution: Supported only in Europe/USA. Nonfunctional in Japan/Asia.
Caution: When unsupported, Parameter Fifo isn't cleared after the command.

Sending these commands with the correct strings (in order 50h through 56h) does disable the "SCEx" protection. The region can be detected via test command 19h,22h, and must be translated to the following string:

"of America"    ;for NTSC/US          ;\
"(Europe)"      ;for PAL/Europe       ; handled, and actually working
"World wide"    ;for Yaroze           ;/
"Inc."          ;for NTSC/JP          ;-non-functional

In the unlocked state, ReadN/ReadS are working for unlicensed CD-Rs, and for imported CDROMs from other regions (both without needing modchips). However there are some cases which may still cause problems: The GetID command (1Ah) does still identify the disc as being unlicensed, same for the Get SCEx Counters test command (19h,05h). And, if a game should happen to send the Reset command (1Ch) for some weird reason, then the BIOS would forget the unlocking, same for games that set the "HCRISD" I/O port bit. On the contrary, opening/closing the drive door does not affect the unlocking state.

The commands have been discovered in September 2013, and appear to be supported by all CDROM BIOS versions (from old PSXes up to later PSones).

Note that the commands do always respond with INT5 errors (even on successful unlocking).

Japanese consoles are internally containing code for processing the Secret Unlock commands, but they are not actually executing that code, and even if they would do so: they are ignoring the resulting unlocking flag, making the commands nonfunctional in Japan/Asia regions.

SecretLock - Command 57h --> INT5(11h,40h)

Undoes the unlocking and restores the normal locked state (same happens when sending the Unlocking commands in wrong order or with wrong parameters).

SecretCrash - Command 58h..5Fh --> Crash

Jumps to a data area and executes random code. Results are more or less unpredictable (as they involve executing undefined opcodes). Eventually the CPU might hit a RET opcode and recover from the crash.

CDROM - Mainloop/Responses

SUB-CPU Mainloop

The SUB-CPU is running a mainloop that is handling hardware events (by simple polling, not by IRQs):

check for incoming sectors (from CDROM decoder)
check for incoming commands (from Main CPU)
do maintenance stuff on the drive mechanics

There is no fixed priority: if both incoming sector and incoming command are present, then the SUB-CPU may handle either one, depending on which portion of the mainloop it is currently executing.

There is no fixed timing: if the mainloop is just checking for a specific event, then a new event may be processed immediately, otherwise it may take whole mainloop cycle until the SUB-CPU sees the event.

Whereas, the mainloop cycle execution time isn't constant: It may vary depending on various details. Especially, some maintenance stuff is only handled approximately around 15 times per second (so there are 15 slow mainloop cycles per second).

Responses

The PSX can deliver one INT after another. Instead of using a real queue, it's merely using some flags that do indicate which INT(s) need to be delivered. Basically, there seem to be two flags: One for Second Response (INT2), and one for Data/Report Response (INT1). There is no flag for First Response (INT3); because that INT is generated immediately after executing a command.

The flag mechanism means that the SUB-CPU cannot hold more than one undelivered INT1. That, although the CDROM Decoder does notify the SUB-CPU about all newly received sectors, and it can hold up to eight sectors in the 32K SRAM. However, the SUB-CPU BIOS merely sets a sector-delivery-needed flag (instead of memorizing which/how many sectors need to be delivered, and, accordingly, the PSX can use only three of the available eight SRAM slots: One for currently pending INT1, one for undelivered INT1, and one for currently/incompletely received sector).

First Response (INT3) (or INT5 if failed)

The first response is sent immediately after processing a command. In detail:

The mainloop checks for incoming commands once every some clock cycles, and executes commands under following condition:

Main CPU has sent a command, AND, there is no INT pending
(if an INT is pending, then the command won't be executed yet, but will be
executed in following mainloop cycles; once when INT got acknowledged)
(even if no INT is pending, the mainloop may generate INT1/INT2 before
executing the command, if so, as said above, the command won't execute yet)

Once when the command gets executed it will sent the first response immediately after the command execution (which may only take a few clock cycles, or some more cycles, for example Init/ReadTOC do include some time consuming initializations). Anyways, there will be no other INTs generated during command execution, so once when the command execution has started, it's guaranteed that the next INT will contain the first response.

Second Responses (INT2) (or INT5 if failed)

Some commands do send a second response after actual command execution:

07h MotorOn    E -               INT3(stat), INT2(stat)
08h Stop       E -               INT3(stat), INT2(stat)
09h Pause      E -               INT3(stat), INT2(stat)
0Ah Init         -               INT3(late-stat), INT2(stat)
12h SetSession E session         INT3(stat), INT2(stat)
15h SeekL      E -               INT3(stat), INT2(stat)  ;\use prior Setloc
16h SeekP      E -               INT3(stat), INT2(stat)  ;/to set target
1Ah GetID      E -               INT3(stat), INT2/5(stat,flg,typ,atip,"SCEx")
1Dh GetQ       E adr,point       INT3(stat), INT2(10bytesSubQ,peak_lo)
1Eh ReadTOC      -               INT3(late-stat), INT2(stat)

In some cases (like seek or spin-up), it may take more than a second until the 2nd response is sent.

It should be highly recommended to WAIT until the second response is generated BEFORE sending a new command (it wouldn't make too much sense to send a new command between first and second response, and results would be unknown, and probably totally unpredictable).

Error Notes: If the command has been rejected (INT5 sent as 1st response) then the 2nd response isn't sent (eg. on wrong number of parameters, or if disc missing). If the command fails at a later stage (INT5 as 2nd response), then there are cases where another INT5 occurs as 3rd response (eg. on SetSession=02h on non-multisession-disk).

Data/Report Responses (INT1)

03h Play       E (track)         INT3(stat), optional INT1(report bytes)
04h Forward    E -               INT3(stat), optional INT1(report bytes)
05h Backward   E -               INT3(stat), optional INT1(report bytes)
06h ReadN      E -               INT3(stat), INT1(stat), datablock
1Bh ReadS      E?-               INT3(stat), INT1(stat), datablock

CDROM - Response/Data Queueing

[Below are some older/outdated test cases]

Sector Buffer

The CDROM sector buffer is 32Kx8 SRAM (IC303). The buffer is apparently divided into 8 slots, theoretically allowing to buffer up to 8 sectors.

BUG: The drive controller seems to allow only 2 of those 8 sectors (the oldest sector, and the current/newest sector).

Ie. after processing the INT1 for the oldest sector, one would expect the controller to generate another INT1 for next newer sector - but instead it appears to jump directly to INT1 for the newest sector (skipping all other unprocessed sectors). There is no known way to get around that effect.

So far, the big 32Kbyte buffer is entirely useless (the two accessible sectors could have been as well stored in a 8Kbyte chip) (unless, maybe the 32Kbytes have been intended for some error-correction "read-ahead" purposes, rather than as "look-back" buffer for old sectors; one of the unused slots might be also used for XA-ADPCM sectors).

The bottom line is that one should process INT1's as soon as possible (ie. before the cdrom controller receives and skips further sectors). Otherwise sectors would be lost without notice (there appear to be absolutely no overrun status flags, nor overrun error interrupts).

Sector Buffer Test Cases

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Process INT1 --> receives sector header for 0:2:1
Process INT1 --> receives sector header for 0:2:2
Process INT1 --> receives sector header for 0:2:3

Above shows the normal flow when processing INT1's as they arise. Now, inserting delays (and not processing INT1's during that delays):

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
delay(1)
Process INT1 --> receives sector header for 0:2:1 (oldest sector)
Process INT1 --> receives sector header for 0:2:6 (newest sector)
Process INT1 --> receives sector header for 0:2:7 (next sector)

Above suggests that the CDROM buffer can hold max 2 sectors (the oldest and current one). However, using a longer delay:

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
delay(2)
Process INT1 --> receives sector header for 0:2:9  (oldest/overwritten)
Process INT1 --> receives sector header for 0:2:11 (newest sector)
Process INT1 --> receives sector header for 0:2:12 (next sector)

Above indicates that sector buffer can hold 8 sectors (as the sector 1 slot is overwritten by sector 9). And, another test with even longer delay:

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
delay(3)
Process INT1 --> receives sector header for 0:2:17 (currently received)
Process INT1 --> receives sector header for 0:2:16 (newest full sector)
Process INT1 --> receives sector header for 0:2:17 (next sector)
Process INT1 --> receives sector header for 0:2:18 (next sector)

Above is a special case where sector 17 appears twice; the first one is the sector 1 slot (which was overwritten by sector 9, and apparently then half overwritten by sector 17).

Sector Buffer VS GetlocL Response Tests

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
GetlocL
Process INT3 --> receives getloc info for 0:2:0
Process INT1 --> receives sector header for 0:2:1
Process INT1 --> receives sector header for 0:2:2
Process INT1 --> receives sector header for 0:2:3

Another test, with Delay BEFORE Getloc:

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Delay(1)
GetlocL
Process INT1 --> receives sector header for 0:2:1
Process INT3 --> receives getloc info for 0:2:6
Process INT1 --> receives sector header for 0:2:6
Process INT1 --> receives sector header for 0:2:7

Another test, with Delay AFTER Getloc:

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
GetlocL
Delay(1)
Process INT3 --> receives getloc info for 0:2:0
Process INT1 --> receives sector header for 0:2:5
Process INT1 --> receives sector header for 0:2:6
Process INT1 --> receives sector header for 0:2:7

Another test, with Delay BEFORE and AFTER Getloc:

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Delay(1)
GetlocL
Delay(1)
Process INT1 --> receives sector header for 0:2:9
Process INT1 --> receives sector header for 0:2:11
Process INT3 --> receives getloc info for 0:2:12
Process INT1 --> receives sector header for 0:2:12
Process INT1 --> receives sector header for 0:2:13

Sector Buffer VS Pause Response Tests

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Pause
Process INT3 --> receives stat=22h (first pause response)
Process INT2 --> receives stat=02h (second pause response)

Another test, with Delay BEFORE Pause:

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Delay(1)
Pause
Process INT1 --> receives sector header for 0:2:1 (oldest)
Process INT3 --> receives stat=22h (first pause response)
Process INT2 --> receives stat=02h (second pause response)

Another test, with Delay AFTER Pause:

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Pause
Delay(1)
Process INT3 --> receives stat=22h (first pause response)
Process INT2 --> receives stat=02h (second pause response)

Another test, with Delay BEFORE and AFTER Pause:

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Delay(1)
Pause
Delay(1)
Process INT1 --> receives sector header for 0:2:9 (oldest/overwritten)
Process INT3 --> receives stat=22h (first pause response)
Process INT2 --> receives stat=02h (second pause response)

For above: Note that, despite of Pause, the CDROM is still writing to the internal buffer (and overwrites slot 1 by sector 9) (this might be because the Pause command isn't processed at all until INT1 is processed).

Double Commands (Getloc then Pause)

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
GetlocL
Pause
Process INT3 --> receives stat=22h (first pause response)
Process INT2 --> receives stat=02h (second pause response)

Another test,

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Delay(1)
GetlocL
Pause
Process INT1 --> receives sector header for 0:2:1
Process INT1 --> receives sector header for 0:2:6
Process INT3 --> receives stat=22h (first pause response)
Process INT2 --> receives stat=02h (second pause response)

Another test,

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
GetlocL
Delay(1)
Pause
Process INT3 --> receives getloc info for 0:2:0 (first getloc response)
Process INT3 --> receives stat=22h (first pause response)
Process INT2 --> receives stat=02h (second pause response)

Another test,

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Delay(1)
GetlocL
Delay(1)
Pause
Process INT1 --> receives sector header for 0:2:9 (oldest/overwritten)
Process INT3 --> receives stat=22h (first pause response)
Process INT2 --> receives stat=02h (second pause response)

Double Commands (Pause then Getloc)

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Pause
GetlocL
Process INT3 --> receives getloc info for 0:2:0 (first getloc response)
Process INT1 --> receives sector header for 0:2:1
Process INT1 --> receives sector header for 0:2:2
Process INT1 --> receives sector header for 0:2:3

Another test,

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Delay(1)
Pause
GetlocL
Process INT1 --> receives sector header for 0:2:1
Process INT3 --> receives getloc info for 0:2:6 (first getloc response)
Process INT1 --> receives sector header for 0:2:6
Process INT1 --> receives sector header for 0:2:7

Another test,

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Pause
Delay(1)
GetlocL
Process INT3 --> receives stat=22h (first pause response)
Process INT3 --> receives getloc info for 0:2:6 (first getloc response)
(No further INT's, ie. read is paused, but second-pause-response is lost).

Another test,

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Pause
Delay(1)
GetlocL
Delay(1)
Process INT3 --> receives stat=22h (first pause response)
Process INT3 --> receives getloc info for 0:2:6 (first getloc response)
Process INT2 --> receives stat=02h (second pause response)

Another test,

Setloc(0:2:0)+Read
Process INT1 --> receives sector header for 0:2:0
Delay(1)
Pause
Delay(1)
GetlocL
Process INT1 --> receives sector header for 0:2:9
Process INT1 --> receives sector header for 0:2:11
Process INT3 --> receives getloc info for 0:2:12 (first getloc response)
Process INT1 --> receives sector header for 0:2:12
Process INT1 --> receives sector header for 0:2:13

CDROM Subchannels

Subchannel P

Subchannel P contains some kind of a Pause flag (to indicate muted areas between Audio Tracks). This subchannel doesn't have any checksum, so the data cannot be trusted to be intact (unless when sensing a longer stream of all-one's, or all zero's). Theoretically, the 98 pause bits are somehow associated to the 98 audio frames (with 24 audio bytes each) of the sector. However, reportedly, Subchannel P does contain two sync bits, if that is true, then there'd be only 96 pause flags for 98 audio frames. Strange.

Note: Another way to indicate "paused" regions is to set Subchannel Q to ADR=1 and Index=00h.

Subchannel Q

contains the following information:

Bits Expl.
2    Sub-channel synchronization field
8    ADR/Control (see below)
72   Data (content depends on ADR)
16   CRC-16-CCITT error detection code (big-endian: bytes ordered MSB, LSB)

Possible values for the ADR/Control field are:

Bit0-3 ADR (0=No data, 1..3=see below, 4..0Fh=Reserved)
Bit4   Audio Preemphasis  (0=No, 1=Yes)      (Audio only, must be 0 for Data)
Bit5   Digital Copy       (0=Prohibited, 1=Allowed)
Bit6   Data               (0=Audio, 1=Data)
Bit7   Four-Channel Audio (0=Stereo, 1=Quad) (Audio only, must be 0 for Data)

The 72bit data regions are, depending on the ADR value...

Subchannel Q with ADR=1 during Lead-In -- Table of Contents (TOC)

8    Track number (fixed, must be 00h=Lead-in)
8    Point (01h..99h or A0h..A2h, see last three bytes for more info)
24   MSF address (incrementing address within the Lead-in area)
Note: On some disks, these values are choosen so that the lead-in
at 00:00:00, on other disks so that it  at 99:59:74.
8    Reserved (00h)

When Point=01h..99h (Track 1..99) or Point=A2h (Lead-Out):

24   MSF address (absolute address, start address of the "Point" track)

When Point=A0h (First Track Number):

8    First Track number (BCD)
8    Disk Type Byte (00h=CD-DA or CD-ROM, 10h=CD-I, 20h=CD-ROM-XA)
8    Reserved (00h)

When Point=A1h (Last Track Number):

8    Last Track number (BCD)
16   Reserved (0000h)

ADR=1 should exist in 3 consecutive lead-in sectors.

Subchannel Q with ADR=1 in Data region -- Position

8    Track number (01h..99h=Track 1..99)
8    Index number (00h=Pause, 01h..99h=Index within Track)
24   Track relative MSF address (decreasing during Pause)
8    Reserved (00h)
24   Absolute MSF address

ADR=1 is required to exist in at least 9 out of 10 consecutive data sectors.

Subchannel Q with ADR=1 during Lead-Out -- Position

8    Track number (fixed, must be AAh=Lead-Out)
8    Index number (fixed, must be 01h) (there's no Index=00h in Lead-Out)
24   Track relative MSF address (increasing, 00:00:00 and up)
8    Reserved (00h)
24   Absolute MSF address

ADR=1 should exist in 3 consecutive lead-out sectors (and may then be followed by ADR=5 on multisession disks).

Subchannel Q with ADR=2 -- Catalogue number of the disc (UPC/EAN barcode)

52   EAN-13 barcode number (13-digit BCD)
12   Reserved (000h)
8    Absolute Sector number (BCD, 00h..74h) (always 00h during Lead-in)

If the first digit of the EAN-13 number is "0", then the remaining digits are a UPC-A barcode number. Either the 13-digit EAN-13 number, or the 12-digit UPC-A number should be printed as barcode on the rear-side of the CD package.

The first some digits contain a country code (EAN only, not UPC), followed by a manufacturer code, followed by a serial number. The last digit contains a checksum, which can be calculated as 250 minus the sum of the first 12 digits, minus twice the sum of each second digit, modulated by 10.

ADR=2 isn't included on all CDs, and, many CDs do have ADR=2, but the 13 digits are all zero. Most CDROM drives do not allow to read EAN/UPC numbers.

If present, ADR=2 should exist in at least 1 out of 100 consecutive sectors. ADR=2 may occur also in Lead-in.

Subchannel Q with ADR=3 -- ISRC number of the current track

(ISO 3901 and DIN-31-621):

12   Country Code      (two 6bit characters)   (ASCII minus 30h) ;eg. "US"
18   Owner Code        (three 6bit characters) (ASCII minus 30h)
2    Reserved          (zero)
8    Year of recording (2-digit BCD) ;eg. 82h for 1982
20   Serial number     (5-digit BCD) ;usually increments by 1 or 10 per track
4    Reserved          (zero)
8    Absolute Sector number (BCD, 00h..74h) (always 00h during Lead-in)

Most CDROM drives for PC's do not allow to read ISRC numbers (or even worse, they may accidently return the same ISRC number on every two tracks).

If present, ADR=3 should exist in at least 1 out of 100 consecutive sectors. However, reportedly, ADR=3 should not occur in Lead-in.

Subchannel Q with ADR=5 in Lead-in -- Multisession Lead-In Info

When Point=B0h:

8    Track number (fixed, must be 00h=Lead-in)
8    POINT = B0h (multi-session disc)
24   MM:SS:FF = the start time for the next possible session's program area,
a final session is indicated by FFh:FFh:FFh,
or when the ADR=5 / Point=B0h is absent.
8    Number of different Mode-5 pointers present.
24   MM:SS:FF = the maximum possible start time of the outermost Lead-out

When Point=C0h:

8    Track number (fixed, must be 00h=Lead-in)
8    POINT = C0h (Identifies a Multisession disc, together with POINT=B0h)
24   ATIP values from Special Information 1, ID=101
8    Reserved (must be 00h)
24   MM:SS:FF = Start time of the first Lead-in area of the disc

And, optionally, when Point=C1h:

8    Track number (fixed, must be 00h=Lead-in)
8    POINT=C1h
8x7  Copy of information from A1 point in ATIP

Subchannel Q with ADR=5 in Lead-Out -- Multisession Lead-Out Info

8    Track number (fixed, must be AAh=Lead-out)
8    POINT = D1h (Identifies a Multisession lead-out)
24   Usually zero (or maybe ATIP as in Lead-In with Point=C0h...?)
8    Seems to be the session number?
24   MM:SS:FF = Absolute address of the First data sector of the session

Present in 3 consequtive sectors (3x ADR=1, 3x ADR=5, 3x ADR=1, 3x ADR=5, etc).

Subchannel Q with ADR=5 in Lead-in -- CDR/CDRW Skip Info (Audio Only)

When Point=01h..40h:

8    Track number (fixed, must be 00h=Lead-in)
8    POINT=01h..40h (This identifies a specific playback skip interval)
24   MM:SS:FF Skip interval stop time in 6 BCD digits
8    Reserved (must be 00h)
24   MM:SS:FF Skip interval start time in 6 BCD digits

When Point=B1h:

8    Track number (fixed, must be 00h=Lead-in)
8    POINT=B1h (Audio only: This identifies the presence of skip intervals)
8x4  Reserved (must be 00h,00h,00h,00h)
8    the number of skip interval pointers in POINT=01h..40h
8    the number of skip track assignments in POINT=B2h..B4h
8    Reserved (must be 00h)

When Point=B2h,B3h,B4h:

8    Track number (fixed, must be 00h=Lead-in)
8    POINT=B2h,B3h,B4h (This identifies tracks that should be skipped)
8    1st Track number to skip upon playback (01h..99h, must be nonzero)
8    2nd Track number to skip upon playback (01h..99h, or 00h=None)
8    3rd Track number to skip upon playback (01h..99h, or 00h=None)
8    Reserved (must be 00h)... unclear... OR... 4th (of 7) skip info's...?
8    4th Track number to skip upon playback (01h..99h, or 00h=None)
8    5th Track number to skip upon playback (01h..99h, or 00h=None)
8    6th Track number to skip upon playback (01h..99h, or 00h=None)

Note: Skip intervals are seldom written by recorders and typically ignored by readers.

Subchannel R..W

Subchannels R..W are usually unused, except for some extended formats:

CD-TEXT in the Lead-In area (see below)
CD-TEXT in the Data area    (rarely used)
CD plus Graphics (CD+G)     (rarely used)

Most CDROM drives do not allow to read these subchannels. CD-TEXT was designed by Sony and Philips in 1997, so it should be found only on (some) newer discs. Most CD/DVD players don't support it (the only exception is that CD-TEXT seems to be popular for car hifi equipment). Most record labels don't support CD-TEXT, even Sony seems to have discontinued it on their own records after some years (so CD-TEXT is very rare on original disks, however, CDR software does often allow to write CD-TEXT on CDRs).

Subchannel R..W, when used for CD-TEXT in the Lead-In area

CD-TEXT is stored in the six Subchannels R..W. Of the 12.25 bytes (98 bits) per subchannel, only 12 bytes are used. Together, all 6 subchannels have a capacity of 72 bytes (6x12 bytes) per sector. These 72 bytes are divided into four CD-TEXT fragments (of 18 bytes each). The format of these 18 bytes is:

00h 1  Header Field ID1: Pack Type Indicator
01h 1  Header Field ID2: Track Number
02h 1  Header Field ID3: Sequence Number
03h 1  Header Field ID4: Block Number and Character Position Indicator
04h 12 Text/Data Field
10h 2  CRC-16-CCITT (big-endian) (across bytes 00h..0Fh)

ID1 - Pack Type Indicator:

80h   Titel      (TEXT)
81h   Performer  (TEXT)
82h   Songwriter (TEXT)
83h   Composer   (TEXT)
84h   Arranger   (TEXT)
85h   Message    (TEXT)
86h   Disc ID    (TEXT?)  (content/format/purpose unknown?)
87h   Genre      (BINARY) (ID codes unknown?)
88h   TOC        (BINARY) (content/format/purpose unknown?)
89h   TOC2       (BINARY) (content/format/purpose unknown?)
8Ah   Reserved for future
8Bh   Reserved for future
8Ch   Reserved for future
8Dh   Reserved for "content provider" aka "closed information"
8Eh   UPC/EAN and ISRC Codes (TEXT) (content/format/purpose unknown?)
8Fh   Blocksize (BINARY) (see below)

ID2 - Track Number:

00h       Title/Performer/etc. for the Disc
01h..63h  Title/Performer/etc. for Track 1..99 (Non-BCD) (Bit7=Extension)

ID3 - Sequence Number:

00h..FFh  Incrementing Number (00h=First 18-byte fragment, 01h=Second, etc.)

ID4 - Block Number and Character Position Indicator:

Bit7      Character Set      (0=8bit, 1=16bit)
Bit6-4    Block Number       (0..7 = Language number, as set by "Blocksize")
Bit3-0    Character Position (0..0Eh=Position, 0Fh=Append to prev fragment)

Example Data (generated with CDRWIN):

ID TR SQ CH  -CRC-
80 00 00 00 54 65 73 74 44 69 73 6B 54 69 74 6C E2 22  TestDiskTitl
80 00 01 0C 65 00 54 65 73 74 54 72 61 63 6B 54 C9 1B  e.TestTrackT
80 01 02 0A 69 74 6C 65 31 00 54 65 73 74 54 72 40 3A  itle1.TestTr
80 02 03 06 61 63 6B 54 69 74 6C 65 32 00 00 00 80 E3  ackTitle2...
81 00 04 00 54 65 73 74 44 69 73 6B 50 65 72 66 03 DF  TestDiskPerf
81 00 05 0C 6F 72 6D 65 72 00 54 65 73 74 54 72 12 A5  ormer.TestTr
81 01 06 06 61 63 6B 50 65 72 66 6F 72 6D 65 72 BC 5B  ackPerformer
81 01 07 0F 31 00 54 65 73 74 54 72 61 63 6B 50 AC 41  1.TestTrackP
81 02 08 0A 65 72 66 6F 72 6D 65 72 32 00 00 00 64 1A  erformer2...
8F 00 09 00 01 01 02 00 04 05 00 00 00 00 00 00 6D E2  ............
8F 01 0A 00 00 00 00 00 00 00 00 03 0B 00 00 00 CD 0C  ............
8F 02 0B 00 00 00 00 00 09 00 00 00 00 00 00 00 FC 8C  ............
00   ;
CDROM Scrambling

**Scrambling**

Scambling does XOR the data sectors with random values (done to avoid regular
patterns). The scrambling is applied to Data sector bytes[00Ch..92Fh] (not to
CD-DA audio sectors, and not to the leading 12-byte Sync mark in Data sectors).

The (de-)scrambling is done automatically by the CDROM controller, so disc
images should usually contain unscrambled data (there are some exceptions such
like CD-i discs that have audio and data sectors mixed inside of the same
track; which may confuse the CDROM controller about whether or not to apply
scrambling to which sectors; so one may need to manually XOR the faulty sectors
in the disc image).

The scrambling pattern is derived from a 15bit polynomial counter (much like a
noise generator in sound chips). The data bits are XORed with the counters low
bit, and the counters lower 2bit are XORed with each other, and shifted in to
the counters upper bit. To compute 8 bits and once, and store them in a
924h-byte table:

poly=0001h ;init 15bit polynomial counter for i=0 to 924h-1 scramble_table[i]=poly AND FFh poly=(((poly XOR poly/2) AND 0FFh)*80h) XOR (poly/100h) next i


The resulting table content should be:

01h,80h,00h,60h,00h,28h,00h,1Eh,80h,08h,60h,06h,A8h,02h,FEh,81h, 80h,60h,60h,28h,28h,1Eh,9Eh,88h,68h,66h,AEh,AAh,FCh,7Fh,01h,E0h, etc.


After scrambling, the data is reportedly "shuffled and byte-swapped". Unknown
what shuffling means. And unknown what/where/why byte-swapping is done (it does
reportedly swap each two bytes in the whole(?) 930h-byte (data-?) sector; which
might date back to different conventions for disc images to contain "16bit
audio samples" in big- or little-endian format).

CDROM XA Audio ADPCM Compression

CD-ROM XA ADPCM is used for Audio data compression. Each 16bit sample is
encoded in 4bit nibbles; so the compression rate is almost 1:4 (only almost 1:4
because there are 16 header bytes within each 128-byte portion). The data is
usually/always stored on 914h-byte sectors (without error correction).

**Subheader**

The Subheader (see previous chapter) contains important info for ADPCM: The
file/channel numbers for Interleaved data, and the codinginfo flags:
mono/stereo flag, 37800Hz/18900Hz sampling rate, 4bit/8bit format, and
emphasis.

**ADPCM Sectors**

Each sector consists of 12h 128-byte portions (=900h bytes) (the remaining 14h
bytes of the sectors 914h-byte data region are 00h filled).

The separate 128-byte portions consist of a 16-byte header,

00h..03h Copy of below 4 bytes (at 04h..07h) 04h Header for 1st Block/Mono, or 1st Block/Left 05h Header for 2nd Block/Mono, or 1st Block/Right 06h Header for 3rd Block/Mono, or 2nd Block/Left 07h Header for 4th Block/Mono, or 2nd Block/Right 08h Header for 5th Block/Mono, or 3rd Block/Left ;\unknown/unused 09h Header for 6th Block/Mono, or 3rd Block/Right ; for 8bit ADPCM 0Ah Header for 7th Block/Mono, or 4th Block/Left ; (maybe 0, or maybe 0Bh Header for 8th Block/Mono, or 4th Block/Right ;/copy of above) 0Ch..0Fh Copy of above 4 bytes (at 08h..0Bh)


followed by twentyeight data words (4x28-bytes),

10h..13h 1st Data Word (packed 1st samples for 2-8 blocks) 14h..17h 2nd Data Word (packed 2nd samples for 2-8 blocks) 18h..1Bh 3rd Data Word (packed 3rd samples for 2-8 blocks) ... Nth Data Word (packed Nth samples for 2-8 blocks) 7Ch..7Fh 28th Data Word (packed 28th samples for 2-8 blocks)


and then followed by the next 128-byte portion.

The "Copy" bytes are allowing to repair faulty headers (ie. if the CDROM
controller has sensed a read-error in the header then it can eventually replace
it by the copy of the header).

**XA-ADPCM Header Bytes**

0-3 Shift (0..12) (0=Loudest) (13..15=Reserved/Same as 9) 4-5 Filter (0..3) (only four filters, unlike SPU-ADPCM which has five) 6-7 Unused (should be 0)


Note: The 4bit (or 8bit) samples are expanded to 16bit by left-shifting them by
12 (or 8), that 16bit value is then right-shifted by the selected 'shift'
amount. For 8bit ADPCM shift should be 0..8 (values 9..12 will cut-off the
LSB(s) of the 8bit value, this works, but isn't useful). For both 4bit and 8bit
ADPCM, reserved shift values 13..15 will act same as shift=9).

**XA-ADPCM Data Words (32bit, little endian)**

0-3 Nibble for 1st Block/Mono, or 1st Block/Left (-8h..+7h) 4-7 Nibble for 2nd Block/Mono, or 1st Block/Right (-8h..+7h) 8-11 Nibble for 3rd Block/Mono, or 2nd Block/Left (-8h..+7h) 12-15 Nibble for 4th Block/Mono, or 2nd Block/Right (-8h..+7h) 16-19 Nibble for 5th Block/Mono, or 3rd Block/Left (-8h..+7h) 20-23 Nibble for 6th Block/Mono, or 3rd Block/Right (-8h..+7h) 24-27 Nibble for 7th Block/Mono, or 4th Block/Left (-8h..+7h) 28-31 Nibble for 8th Block/Mono, or 4th Block/Right (-8h..+7h)


or, for 8bit ADPCM format:

0-7 Byte for 1st Block/Mono, or 1st Block/Left (-80h..+7Fh) 8-15 Byte for 2nd Block/Mono, or 1st Block/Right (-80h..+7Fh) 16-23 Byte for 3rd Block/Mono, or 2nd Block/Left (-80h..+7Fh) 24-31 Byte for 4th Block/Mono, or 2nd Block/Right (-80h..+7Fh)


**decode_sector(src)**

src=src+12+4+8 ;skip sync,header,subheader for i=0 to 11h for blk=0 to 3 IF stereo ;left-samples (LO-nibbles), plus right-samples (HI-nibbles) decode_28_nibbles(src,blk,0,dst_left,old_left,older_left) decode_28_nibbles(src,blk,1,dst_right,old_right,older_right) ELSE ;first 28 samples (LO-nibbles), plus next 28 samples (HI-nibbles) decode_28_nibbles(src,blk,0,dst_mono,old_mono,older_mono) decode_28_nibbles(src,blk,1,dst_mono,old_mono,older_mono) ENDIF next blk src=src+128 next i src=src+14h+4 ;skip padding,edc


**decode_28_nibbles(src,blk,nibble,dst,old,older)**

shift = 12 - (src[4+blk2+nibble] AND 0Fh) filter = (src[4+blk2+nibble] AND 30h) SHR 4 f0 = pos_xa_adpcm_table[filter] f1 = neg_xa_adpcm_table[filter] for j=0 to 27 t = signed4bit((src[16+blk+j4] SHR (nibble4)) AND 0Fh) s = (t SHL shift) + ((oldf0 + olderf1+32)/64); s = MinMax(s,-8000h,+7FFFh) halfword[dst]=s, dst=dst+2, older=old, old=s next j


**Pos/neg Tables**

pos_xa_adpcm_table[0..4] = (0, +60, +115, +98, +122) neg_xa_adpcm_table[0..4] = (0, 0, -52, -55, -60)


Note: XA-ADPCM supports only four filters (0..3), unlike SPU-ADPCM which
supports five filters (0..4).

**Old/Older Values**

The incoming old/older values are usually that from the previous part, or
garbage (in case of decoding errors in the previous part), or whatever (in case
there was no previous part) (ie. maybe zero on power-up?) (and maybe there's
also a way to reset the values to zero at the begin of a new file, or *maybe*
it's silently done automatically when issuing seek commands?).

**25-point Zigzag Interpolation**

The CDROM decoder is applying some weird 25-point zigzag interpolation when
resampling the 37800Hz XA-ADPCM output to 44100Hz. This part is different from
SPU-ADPCM (which uses 4-point gaussian pitch interpolations). For example,
XA-ADPCM interpolation applied to a square wave looks like this:

. . .--------------. | | | | | | .'.'.'----'.'.'. | | | | | | | | | | | Decompressed | | Final | | XA-ADPCM | | XA-ADPCM | | Waveform | | Output | | | | | | | | | ---.'.'.' '.'.'.--- --------' '-------- | | | | ' '


The zigzagging does produce some (inaudible) 22050Hz noise, and does produce
some low-pass (?) filtering ("sinc filter"). The effect can be reproduced
somewhat like so:

Output37800Hz(sample): ringbuf[p AND 1Fh]=sample, p=p+1, sixstep=sixstep-1 if sixstep=0 sixstep=6 Ouput44100Hz(ZigZagInterpolate(p,Table1)) Ouput44100Hz(ZigZagInterpolate(p,Table2)) Ouput44100Hz(ZigZagInterpolate(p,Table3)) Ouput44100Hz(ZigZagInterpolate(p,Table4)) Ouput44100Hz(ZigZagInterpolate(p,Table5)) Ouput44100Hz(ZigZagInterpolate(p,Table6)) Ouput44100Hz(ZigZagInterpolate(p,Table7)) endif ZigZagInterpolate(p,TableX): sum=0 for i=1 to 29, sum=sum+(ringbuf[(p-i) AND 1Fh]*TableX[i])/8000h, next i return MinMax(sum,-8000h,+7FFFh) Table1, Table2, Table3, Table4, Table5, Table6, Table7 ;Index 0 , 0 , 0 , 0 , -0001h, +0002h, -0005h ;1 0 , 0 , 0 , -0001h, +0003h, -0008h, +0011h ;2 0 , 0 , -0001h, +0003h, -0008h, +0010h, -0023h ;3 0 , -0002h, +0003h, -0008h, +0011h, -0023h, +0046h ;4 0 , 0 , -0002h, +0006h, -0010h, +002Bh, -0017h ;5 -0002h, +0003h, -0005h, +0005h, +000Ah, +001Ah, -0044h ;6 +000Ah, -0013h, +001Fh, -001Bh, +006Bh, -00EBh, +015Bh ;7 -0022h, +003Ch, -004Ah, +00A6h, -016Dh, +027Bh, -0347h ;8 +0041h, -004Bh, +00B3h, -01A8h, +0350h, -0548h, +080Eh ;9 -0054h, +00A2h, -0192h, +0372h, -0623h, +0AFAh, -1249h ;10 +0034h, -00E3h, +02B1h, -05BFh, +0BCDh, -16FAh, +3C07h ;11 +0009h, +0132h, -039Eh, +09B8h, -1780h, +53E0h, +53E0h ;12 -010Ah, -0043h, +04F8h, -11B4h, +6794h, +3C07h, -16FAh ;13 +0400h, -0267h, -05A6h, +74BBh, +234Ch, -1249h, +0AFAh ;14 -0A78h, +0C9Dh, +7939h, +0C9Dh, -0A78h, +080Eh, -0548h ;15 +234Ch, +74BBh, -05A6h, -0267h, +0400h, -0347h, +027Bh ;16 +6794h, -11B4h, +04F8h, -0043h, -010Ah, +015Bh, -00EBh ;17 -1780h, +09B8h, -039Eh, +0132h, +0009h, -0044h, +001Ah ;18 +0BCDh, -05BFh, +02B1h, -00E3h, +0034h, -0017h, +002Bh ;19 -0623h, +0372h, -0192h, +00A2h, -0054h, +0046h, -0023h ;20 +0350h, -01A8h, +00B3h, -004Bh, +0041h, -0023h, +0010h ;21 -016Dh, +00A6h, -004Ah, +003Ch, -0022h, +0011h, -0008h ;22 +006Bh, -001Bh, +001Fh, -0013h, +000Ah, -0005h, +0002h ;23 +000Ah, +0005h, -0005h, +0003h, -0001h, 0 , 0 ;24 -0010h, +0006h, -0002h, 0 , 0 , 0 , 0 ;25 +0011h, -0008h, +0003h, -0002h, +0001h, 0 , 0 ;26 -0008h, +0003h, -0001h, 0 , 0 , 0 , 0 ;27 +0003h, -0001h, 0 , 0 , 0 , 0 , 0 ;28 -0001h, 0 , 0 , 0 , 0 , 0 , 0 ;29


The above formula/table gives nearly correct results, but with small rounding
errors in some cases - possibly due to actual rounding issues, or due to
factors with bigger fractional portions, or due to a completely different
formula...

Probably, the hardware does actually do the above stuff in two steps: first,
applying a zig-zag filter (with only around 21-points) to the 37800Hz output,
and then doing 44100Hz interpolation (2-point linear or 4-point gaussian or
whatever) in a second step.

That two-step theory would also match well for 18900Hz resampling (which has
lower-pitch zigzag, and gets spread across about fifty 44100Hz samples).

**XA-ADPCM Emphasis**

With XA-Emphasis enabled in Sub-header, output will appear as so:

.------------. ....-----. | | .'' | | Raw | .' XA | | ADPCM | | Emphasis '. | Waveform | | Output '.. --------' '---------- --------' ''''---


The exact XA-Emphasis formula is unknown (maybe it's just same as for CD-DA's
SUBQ emphasis). Additionally, zig-zag interpolation is applied (somewhere
before or after applying the emphasis stuff).

Note: The Emphasis feature isn't used by any known PSX games.

**Uninitialized Six-step Counter**

The hardware does contain some six-step counter (for interpolating 37800Hz to
44100Hz, ie. to insert one extra sample after each six samples). The 900h-byte
sectors contain a multiple of six samples, so the counter will be always same
before & after playing a sector. However, the initial counter value on
power-up is uninitialized random (and the counter will fallback to that initial
random setting after each 900h-byte sector).

**RIFF Headers (on PCs)**

When reading files that consist of 914h-byte sectors on a PC, the PC seems to
automatically insert a 2Ch-byte RIFF fileheader. Like so, for ADPCM audio
files:

00h 4 "RIFF" 04h 4 Total Filesize (minus 8) 08h 8 "CDXAfmt " 10h 4 Size of below stuff (10h) 14h 14 Stuff (looks like the "LEN_SU" region from XA-Directory Record) 22h 2 Zero (probably just dummy padding for 32bit alignment) 24h 4 "data" 28h 4 Size of following data (usually N*930h)


That RIFF stuff isn't stored on the CDROM (at least not in the file area)
(however, some of that info, like the "=UXA" stuff, is stored in the directory
area of the CDROM).

After the RIFF header, the normal sector data is appended, that, with the full
930h bytes per sector (ie. the 914h data bytes preceeded by sync bytes, header,
subheader, and followed by the EDC value).

The Channel Interleave doesn't seem to be resolved, ie. the Channels are kept
arranged as how they are stored on the CDROM. However, File Interleave
<should> be resolved, ie. other Files that "overlap" the file shouldn't
be included in the file.

CDROM ISO File and Directory Descriptors

The location of the Root Directory is described by a 34-byte Directory Record
being located in Primary Volume Descriptor entries 09Ch..0BDh. The data therein
is: Block Number (usually 22 on PSX disks), LEN_FI=01h, Name=00h, and,
LEN_SU=00h (due to the 34-byte limit).

**Format of a Directory Record**

00h 1 Length of Directory Record (LEN_DR) (33+LEN_FI+pad+LEN_SU) (0=Pad) 01h 1 Extended Attribute Record Length (usually 00h) 02h 8 Data Logical Block Number (2x32bit) 0Ah 8 Data Size in Bytes (2x32bit) 12h 7 Recording Timestamp (yy-1900,mm,dd,hh,mm,ss,timezone) 19h 1 File Flags 8 bits (usually 00h=File, or 02h=Directory) 1Ah 1 File Unit Size (usually 00h) 1Bh 1 Interleave Gap Size (usually 00h) 1Ch 4 Volume Sequence Number (2x16bit, usually 0001h) 20h 1 Length of Name (LEN_FI) 21h LEN_FI File/Directory Name ("FILENAME.EXT;1" or "DIR_NAME" or 00h or 01h) xxh 0..1 Padding Field (00h) (only if LEN_FI is even) xxh LEN_SU System Use (LEN_SU bytes) (see below for CD-XA disks)


LEN_SU can be calculated as "LEN_DR-(33+LEN_FI+Padding)". For CD-XA disks (as
used in the PSX), LEN_SU is 14 bytes:

00h 2 Owner ID Group (whatever, usually 0000h, big endian) 02h 2 Owner ID User (whatever, usually 0000h, big endian) 04h 2 File Attributes (big endian): 0 Owner Read (usually 1) 1 Reserved (0) 2 Owner Execute (usually 1) 3 Reserved (0) 4 Group Read (usually 1) 5 Reserved (0) 6 Group Execute (usually 1) 7 Reserved (0) 8 World Read (usually 1) 9 Reserved (0) 10 World Execute (usually 1) 11 IS_MODE2 (0=MODE1 or CD-DA, 1=MODE2) 12 IS_MODE2_FORM2 (0=FORM1, 1=FORM2) 13 IS_INTERLEAVED (0=No, 1=Yes...?) (by file and/or channel?) 14 IS_CDDA (0=Data or ADPCM, 1=CD-DA Audio Track) 15 IS_DIRECTORY (0=File or CD-DA, 1=Directory Record) Commonly used Attributes are: 0D55h=Normal Binary File (with 800h-byte sectors) 1555h=Uncommon (fade to black .DPS and .XA files) 2555h=Uncommon (wipeout .AV files) (MODE1 ??) 4555h=CD-DA Audio Track (wipeout .SWP files, alone .WAV file) 3D55h=Streaming File (ADPCM and/or MDEC or so) 8D55h=Directory Record (parent-, current-, or sub-directory) 06h 2 Signature ("XA") 08h 1 File Number (Must match Subheader's File Number) 09h 5 Reserved (00h-filled)


Directory sectors do usually have zeropadding at the end of each sector:

  • Directory sizes are always rounded up to N*800h-bytes.
  • Directory entries should not cross 800h-byte sector boundaries. There may be further directory entries on the next sector after the padding. To deal with that, skip 00h-bytes until finding a nonzero LEN_DR value (or slightly faster, upon a 00h-byte, directly jump to next sector instead of doing a slow byte-by-byte skip). Note: Padding between sectors does rarely happen on PSX discs because the PSX kernel supports max 800h bytes per directory (one exception is PSX Hot Shots Golf 2, which has an ISO directory with more than 800h bytes; it does use a lookup file instead of actually parsing the while ISO directory).

Names are alphabetically sorted, no matter if the names refer to files or
directories (ie. SUBDIR would be inserted between STRFILE.EXT and SYSFILE.EXT).
The first two entries (with non-ascii names 00h and 01h) are referring to
current and parent directory.

**Path Tables**

The Path Table contain a summary of the directory names (the same information
is also stored in the directory records, so programs may either use path tables
or directory records; the path tables are allowing to read the whole directory
tree quickly at once, without neeeding to seek from directory to directory).

Path Table 1 is in Little-Endian format, Path Table 3 contains the same data in
Big-Endian format. Path Table 2 and 4 are optional copies of Table 1 and 3. The
size and location of the tables is stored in Volume Descriptor entries
084h..09Bh. The format of the separate entries within a Path Table is,

00h 1 Length of Directory Name (LEN_DI) (01h..08h for PSX) 01h 1 Extended Attribute Record Length (usually 00h) 02h 4 Directory Logical Block Number 06h 2 Parent Directory Number (0001h and up) 08h LEN_DI Directory Name (d-characters, d1-characters) (or 00h for Root) xxh 0..1 Padding Field (00h) (only if LEN_FI is odd)


The first entry (directory number 0001h) is the root directory, the root
doesn't have a name, nor a parent (the name field contains a 00h byte, rather
than ASCII text, LEN_DI is 01h, and parent is 0001h, making the root it's own
parent; ignoring the fact that incest is forbidden in many countries).

The next entries (directory number 0002h and up) (if any) are sub-directories
within the root (sorted in alphabetical order, and all having parent=0001h).
The next entries are sub-directories (if any) of the first sub-directory (also
sorted in alphabetical order, and all having parent=0002h). And so on.

PSX disks usually contain all four tables (usually on sectors 18,19,20,21).

**Format of an Extended Attribute Record (none such on PSX disks)**

If present, an Extended Attribute Record shall be recorded over at least one
Logical Block. It shall have the following contents.

00h 4 Owner Identification (numerical value) ;\used only if 04h 4 Group Identification (numerical value) ; File Flags Bit4=1 08h 2 Permission Flags (16bit, little-endian) ;/ 0Ah 17 File Creation Timestamp ("YYYYMMDDHHMMSSFF",timezone) 1Bh 17 File Modification Timestamp ("0000000000000000",00h) 2Ch 17 File Expiration Timestamp ("0000000000000000",00h) 3Dh 17 File Effective Timestamp ("0000000000000000",00h) 4Eh 1 Record Format (numerical value) 4Fh 1 Record Attributes (numerical value) 50h 4 Record Length (numerical value) 54h 32 System Identifier (a-characters, a1-characters) 74h 64 System Use (not specified content) B4h 1 Extended Attribute Record Version (numerical value) B5h 1 Length of Escape Sequences (LEN_ESC) B6h 64 Reserved for future standardization (00h-filled) F6h 4 Length of Application Use (LEN_AU) FAh LEN_AU Application Use xxh LEN_ESC Escape Sequences


Unknown WHERE that data is located... the Directory Records can specify the
Extended Attribute Length, but not the location... maybe it's meant to be
located in the first some bytes or blocks of the File or Directory...?

CDROM Extension Joliet

**Typical Joliet Disc Header**

The discs contains two separate filesystems, the ISO one for backwards
compatibilty, and the Joliet one with longer filenames and Unicode characters.

Sector 16 - Primary Volume Descriptor (with 8bit uppercase ASCII ISO names) Sector 17 - Secondary Volume Descriptor (with 16bit Unicode Joliet names) Sector 18 - Volume Descriptor Set Terminator Sector .. - Path Tables and Directory Records (for ISO) Sector .. - Path Tables and Directory Records (for Joliet) Sector .. - File Data Sectors (shared for ISO and Joliet)


There is no way to determine which ISO name belongs to which Joliet name
(except, filenames do usually point to the same file data sectors, but that
doesn't work for empty files, and doesn't work for folder names).

The ISO names can be max 31 chars (or shorter for compatibility with DOS short
names: Nero does truncate them to max 14 chars "FILENAME.EXT;1", all uppercase,
with underscores instead of spaces, and somehow assigning names like
"FILENAMx.EXT;1" in case of duplicated short names).

**Secondary Volume Descriptor (aka Supplementary Volume Descriptor)**

This is using the same format as ISO Primary Volume Descriptor (but with some
changed entries).

CDROM ISO Volume Descriptors

Changed entries are:

000h 1 Volume Descriptor Type (02h=Supplementary instead of 01h=Primary) 007h 1 Volume Flags (whatever, instead of Reserved) 008h 2x32 Identifier Strings (16-char Unicode instead 32-char ASCII) 058h 32 Escape Sequences (see below, instead of Reserved) 08Ch 4x4 Path Tables (point to new tables with Unicode chars) 09Ch 34 Root Directory Record (point to root with Unicode chars) 0BEh 4x128 Identifier Strings (64-char Unicode instead 128-char ASCII) 2BEh 3x37 Filename Strings (18-char Unicode instead 37-char ASCII)


The Escape Sequences entry contains three ASCII chars (plus 29-byte
zeropadding), indicating the ISO 2022 Unicode charset:

%/@ UCS-2 Level 1 %/C UCS-2 Level 2 %/E UCS-2 Level 3


**Directory Records and Path Tables**

This is using the standard ISO format (but with 16bit Unicode characters
instead of 8bit ASCII chars).

CDROM ISO File and Directory Descriptors

**File and Directory Name Characters**

All characters are stored in 16bit Big Endian format. The LEN_FI filename entry
contains the length in bytes (ie. numchars*2). Charaters 0000h/0001h are
current/parent directory. Characters 0020h and up can be used for
file/directory names, except six reserved characters: */:;?\

All names must be sorted by their character numbers, padded with zero (without
attempting to merge uppercase, lowercase, or umlauts to nearby locations).

**File and Directory Name Length**

max 64 chars according to original Joliet specs from 1995 max 110 chars (on standard CDROMs, with LEN_SU=0) max 103 chars (on CD-XA discs, with LEN_SU=14)


Joliet Filenames include ISO-style version suffices (usually ";1", so the
actual filename lengths are two chars less than shown above).

The original 64-char limit was perhaps intended to leave space for future
extensions in the LEN_SU region. The 64-char limit can cause problems with
verbose names (eg. "Interprete - Title (version).mp3"). Microsoft later changed
the limit to up to 110 chars.

The 110/103-char limit is caused by the 8bit "LEN_DR=(33+LEN_FI+pad+LEN_SU)"
entry in the Directory Records.

Joliet allows to exceed the 8-level ISO directory nesting limit, however, it
doesn't allow to exceed the 240-byte (120-Unicode-char) limit in ISO 9660
section 6.8.2.1 for the total "path\filename" lengths.

**Official Specs**

Joliet Specification, CD-ROM Recording Spec ISO 9660:1988, Extensions for
Unicode Version 1; May 22, 1995, Copyright 1995, Microsoft Corporation

http://littlesvr.ca/isomaster/resources/JolietSpecification.html