aboutsummaryrefslogtreecommitdiff
path: root/libpsn00b/libpsxcd
diff options
context:
space:
mode:
authorJohn Wilbert M. Villamor <lameguy64@gmail.com>2019-11-22 09:11:12 +0800
committerJohn Wilbert M. Villamor <lameguy64@gmail.com>2019-11-22 09:11:12 +0800
commitea46d05aed0343c20d8fdfaa0e67d54d51e8e2a0 (patch)
tree43e2a53f4e5f7f55b075cfc9d6dc7a652a7b0837 /libpsn00b/libpsxcd
parentd80d92e13330d527ddb94420b19f9e21bf0e74eb (diff)
downloadpsn00bsdk-ea46d05aed0343c20d8fdfaa0e67d54d51e8e2a0.tar.gz
Added long awaited CD-ROM library and loads of fixes, see changelog for details
Diffstat (limited to 'libpsn00b/libpsxcd')
-rw-r--r--libpsn00b/libpsxcd/_cd_control.s109
-rw-r--r--libpsn00b/libpsxcd/cdgetsector.s53
-rw-r--r--libpsn00b/libpsxcd/cdmix.s34
-rw-r--r--libpsn00b/libpsxcd/cdsearchfile.c663
-rw-r--r--libpsn00b/libpsxcd/makefile38
-rw-r--r--libpsn00b/libpsxcd/psxcd.c319
-rw-r--r--libpsn00b/libpsxcd/psxcd_asm.s520
-rw-r--r--libpsn00b/libpsxcd/readme.txt48
8 files changed, 1784 insertions, 0 deletions
diff --git a/libpsn00b/libpsxcd/_cd_control.s b/libpsn00b/libpsxcd/_cd_control.s
new file mode 100644
index 0000000..7f69266
--- /dev/null
+++ b/libpsn00b/libpsxcd/_cd_control.s
@@ -0,0 +1,109 @@
+.set noreorder
+
+.include "hwregs_a.h"
+
+.section .text
+
+#
+# Issues command and parameter bytes to CD controller directly
+#
+.global _cd_control
+.type _cd_control, @function
+_cd_control:
+
+ # a0 - command value
+ # a1 - pointer to parameters
+ # a2 - length of parameters
+
+ li $v0, 1 # Set acknowledge wait flag
+ la $v1, _cd_ack_wait
+ sb $v0, 0($v1)
+
+ # Commands that have a 'completion' interrupt (CDIRQ2)
+
+ beq $a0, 0x07, .Lset_complete # CdlStandby
+ nop
+ beq $a0, 0x08, .Lset_complete # CdlStop
+ nop
+ beq $a0, 0x09, .Lset_complete # CdlPause
+ nop
+ beq $a0, 0x0A, .Lset_complete # CdlInit
+ nop
+ beq $a0, 0x12, .Lset_complete # CdlSetsession
+ nop
+ beq $a0, 0x15, .Lset_complete # CdlSeekL
+ nop
+ beq $a0, 0x16, .Lset_complete # CdlSeekP
+ nop
+ beq $a0, 0x1A, .Lset_complete # GetID
+ nop
+ beq $a0, 0x1D, .Lset_complete # GetQ
+ nop
+
+ la $v1, _cd_complt_wait # Set wait complete flag
+ sb $0 , 0($v1)
+
+ b .Lno_complete
+ nop
+
+.Lset_complete:
+
+ la $v1, _cd_complt_wait # Set wait complete flag
+ sb $v0, 0($v1)
+
+.Lno_complete:
+
+ bne $a0, 0x0E, .Lnot_mode
+ lbu $v0, 0($a1)
+ la $v1, _cd_last_mode
+ sb $v0, 0($v1)
+
+.Lnot_mode:
+
+ la $v1, _cd_last_int # Clear last IRQ value
+ sb $0 , 0($v1)
+
+ la $v1, _cd_last_cmd # Save command as last command
+ sb $a0, 0($v1)
+
+ lui $v1, IOBASE
+
+.Lbusy_wait:
+ lbu $v0, CD_REG0($v1)
+ nop
+ andi $v0, 0x80
+ bnez $v0, .Lbusy_wait
+ nop
+
+ li $v0, 1 # Clear parameter FIFO (in case it wasn't cleared)
+ sb $v0, CD_REG0($v1)
+ li $v0, 0x40
+ sb $v0, CD_REG3($v1)
+
+.Lcmd_wait: # Wait for CD to become ready for commands
+ lbu $v0, CD_REG0($v1)
+ nop
+ andi $v0, 0x80
+ bnez $v0, .Lcmd_wait
+ nop
+
+ sb $0 , CD_REG0($v1)
+
+ beqz $a2, .Lno_params
+ nop
+
+.Lfeed_params: # Feed parameters to parameter FIFO
+ lbu $v0, 0($a1)
+ addi $a2, -1
+ sb $v0, CD_REG2($v1)
+ bgtz $a2, .Lfeed_params
+ addiu $a1, 1
+
+.Lno_params:
+
+ sb $0 , CD_REG0($v1) # Feed command value
+ sb $a0, CD_REG1($v1)
+
+ jr $ra
+ nop
+ \ No newline at end of file
diff --git a/libpsn00b/libpsxcd/cdgetsector.s b/libpsn00b/libpsxcd/cdgetsector.s
new file mode 100644
index 0000000..70039bf
--- /dev/null
+++ b/libpsn00b/libpsxcd/cdgetsector.s
@@ -0,0 +1,53 @@
+.set noreorder
+
+.include "hwregs_a.h"
+
+.section .text
+
+.global CdGetSector
+.type CdGetSector, @function
+CdGetSector:
+
+ lui $a2, IOBASE
+
+.Lwait_fifo:
+ lbu $v0, CD_REG0($a2)
+ nop
+ andi $v0, 0x40
+ beqz $v0, .Lwait_fifo
+ nop
+
+ lui $v0, 0x1
+ srl $a1, 2
+ or $v0, $a1
+ sw $a0, D3_MADR($a2) # Set DMA base address and transfer length
+ sw $v0, D3_BCR($a2)
+
+ lui $v0, 0x1100 # Start DMA transfer
+ sw $v0, D3_CHCR($a2)
+ nop
+ nop
+.Ldma_wait:
+ lw $v0, D3_CHCR($a2)
+ nop
+ srl $v0, 24
+ andi $v0, 0x1
+ bnez $v0, .Ldma_wait
+ nop
+
+# Not stable
+# sb $0 , CD_REG0($a2)
+#.Lflush_fifo: # Read out any remaining bytes in the buffer
+# lbu $v1, CD_REG0($a2)
+# li $v0, 0x40
+# and $v1, $v0
+# beqz $v1, .Lend_flush
+# nop
+# lbu $v0, CD_REG2($a2)
+# b .Lflush_fifo
+# nop
+#.Lend_flush:
+
+ jr $ra
+ li $v0, 1
+
diff --git a/libpsn00b/libpsxcd/cdmix.s b/libpsn00b/libpsxcd/cdmix.s
new file mode 100644
index 0000000..745fb65
--- /dev/null
+++ b/libpsn00b/libpsxcd/cdmix.s
@@ -0,0 +1,34 @@
+.set noreorder
+
+.include "hwregs_a.h"
+
+.section .text
+
+
+.global CdMix
+.type CdMix, @function
+CdMix:
+
+ lui $a3, IOBASE
+
+ lbu $t0, 0($a0)
+ lbu $t1, 1($a0)
+ lbu $t2, 2($a0)
+ lbu $t3, 3($a0)
+
+ li $v0, 2
+ sb $v0, CD_REG0($a3)
+ sb $t0, CD_REG2($a3)
+ sb $t1, CD_REG3($a3)
+
+ li $v0, 3
+ sb $v0, CD_REG0($a3)
+ sb $t2, CD_REG1($a3)
+ sb $t3, CD_REG2($a3)
+
+ li $v0, 0x20
+ sb $v0, CD_REG3($a3)
+
+ jr $ra
+ li $v0, 1
+ \ No newline at end of file
diff --git a/libpsn00b/libpsxcd/cdsearchfile.c b/libpsn00b/libpsxcd/cdsearchfile.c
new file mode 100644
index 0000000..60e4377
--- /dev/null
+++ b/libpsn00b/libpsxcd/cdsearchfile.c
@@ -0,0 +1,663 @@
+#include <stdio.h>
+#include <malloc.h>
+#include <string.h>
+#include <psxgpu.h>
+#include <psxsio.h>
+#include "psxcd.h"
+
+// Uncommend to enable debug output
+//#define DEBUG
+
+#pragma pack(push, 1)
+
+/// Structure of a double-endian unsigned short word
+typedef struct ISO_USHORT_PAIR
+{
+ unsigned short lsb; /// LSB format 16-bit word
+ unsigned short msb; /// MSB format 16-bit word
+} ISO_USHORT_PAIR;
+
+/// Structure of a double-endian unsigned int word
+typedef struct ISO_UINT_PAIR
+{
+ unsigned int lsb; /// LSB format 32-bit word
+ unsigned int msb; /// MSB format 32-bit word
+} ISO_UINT_PAIR;
+
+/// ISO descriptor header structure
+typedef struct ISO_DESCRIPTOR_HEADER
+{
+ unsigned char type; /// Volume descriptor type (1 is descriptor, 255 is descriptor terminator)
+ char id[5]; /// Volume descriptor ID (always CD001)
+ unsigned short version; /// Volume descriptor version (always 0x01)
+} ISO_DESCRIPTOR_HEADER;
+
+/// Structure of a date stamp for ISO_DIR_ENTRY structure
+typedef struct ISO_DATESTAMP
+{
+ unsigned char year; /// number of years since 1900
+ unsigned char month; /// month, where 1=January, 2=February, etc.
+ unsigned char day; /// day of month, in the range from 1 to 31
+ unsigned char hour; /// hour, in the range from 0 to 23
+ unsigned char minute; /// minute, in the range from 0 to 59
+ unsigned char second; /// Second, in the range from 0 to 59
+ unsigned char GMToffs; /// Greenwich Mean Time offset
+} ISO_DATESTAMP;
+
+/// Structure of an ISO path table entry (specifically for the cd::IsoReader class)
+typedef struct ISO_PATHTABLE_ENTRY
+{
+ unsigned char nameLength; /// Name length (or 1 for the root directory)
+ unsigned char extLength; /// Number of sectors in extended attribute record
+ unsigned int dirOffs; /// Number of the first sector in the directory, as a double word
+ unsigned short dirLevel; /// Index of the directory record's parent directory
+ /// If nameLength is odd numbered, a padding byte will be present after the identifier text.
+} ISO_PATHTABLE_ENTRY;
+
+typedef struct ISO_DIR_ENTRY
+{
+ unsigned char entryLength; // Directory entry length (variable, use for parsing through entries)
+ unsigned char extLength; // Extended entry data length (always 0)
+ ISO_UINT_PAIR entryOffs; // Points to the LBA of the file/directory entry
+ ISO_UINT_PAIR entrySize; // Size of the file/directory entry
+ ISO_DATESTAMP entryDate; // Date & time stamp of entry
+ unsigned char flags; // File flags (0x02 for directories, 0x00 for files)
+ unsigned char fileUnitSize; // Unit size (usually 0 even with Form 2 files such as STR/XA)
+ unsigned char interleaveGapSize; // Interleave gap size (usually 0 even with Form 2 files such as STR/XA)
+ ISO_USHORT_PAIR volSeqNum; // Volume sequence number (always 1)
+ unsigned char identifierLen; // Identifier (file/directory name) length in bytes
+} ISO_DIR_ENTRY;
+
+typedef struct ISO_ROOTDIR_HEADER
+{
+ unsigned char entryLength; // Always 34 bytes
+ unsigned char extLength; // Always 0
+ ISO_UINT_PAIR entryOffs; // Should point to LBA 22
+ ISO_UINT_PAIR entrySize; // Size of entry extent
+ ISO_DATESTAMP entryDate; // Record date and time
+ unsigned char flags; // File flags
+ unsigned char fileUnitSize;
+ unsigned char interleaveGapSize;
+ ISO_USHORT_PAIR volSeqNum;
+ unsigned char identifierLen; // 0x01
+ unsigned char identifier; // 0x00
+} ISO_ROOTDIR_HEADER;
+
+// ISO descriptor structure
+typedef struct ISO_DESCRIPTOR
+{
+
+ // ISO descriptor header
+ ISO_DESCRIPTOR_HEADER header;
+ // System ID (always PLAYSTATION)
+ char systemID[32];
+ // Volume ID (or label, can be blank or anything)
+ char volumeID[32];
+ // Unused null bytes
+ unsigned char pad2[8];
+ // Size of volume in sector units
+ ISO_UINT_PAIR volumeSize;
+ // Unused null bytes
+ unsigned char pad3[32];
+ // Number of discs in this volume set (always 1 for single volume)
+ ISO_USHORT_PAIR volumeSetSize;
+ // Number of this disc in volume set (always 1 for single volume)
+ ISO_USHORT_PAIR volumeSeqNumber;
+ // Size of sector in bytes (always 2048 bytes)
+ ISO_USHORT_PAIR sectorSize;
+ // Path table size in bytes (applies to all the path tables)
+ ISO_UINT_PAIR pathTableSize;
+ // LBA to Type-L path table
+ unsigned int pathTable1Offs;
+ // LBA to optional Type-L path table (usually a copy of the primary path table)
+ unsigned int pathTable2Offs;
+ // LBA to Type-L path table but with MSB format values
+ unsigned int pathTable1MSBoffs;
+ // LBA to optional Type-L path table but with MSB format values (usually a copy of the main path table)
+ unsigned int pathTable2MSBoffs;
+ // Directory entry for the root directory (similar to a directory entry)
+ ISO_ROOTDIR_HEADER rootDirRecord;
+ // Volume set identifier (can be blank or anything)
+ char volumeSetIdentifier[128];
+ // Publisher identifier (can be blank or anything)
+ char publisherIdentifier[128];
+ // Data preparer identifier (can be blank or anything)
+ char dataPreparerIdentifier[128];
+ // Application identifier (always PLAYSTATION)
+ char applicationIdentifier[128];
+ // Copyright file in the file system identifier (can be blank or anything)
+ char copyrightFileIdentifier[37];
+ // Abstract file in the file system identifier (can be blank or anything)
+ char abstractFileIdentifier[37];
+ // Bibliographical file identifier in the file system (can be blank or anything)
+ char bibliographicFilelIdentifier[37];
+ // Volume create date (in text format YYYYMMDDHHMMSSMMGG)
+ char volumeCreateDate[17];
+ // Volume modify date (in text format YYYYMMDDHHMMSSMMGG)
+ char volumeModifyDate[17];
+ // Volume expiry date (in text format YYYYMMDDHHMMSSMMGG)
+ char volumeExpiryDate[17];
+ // Volume effective date (in text format YYYYMMDDHHMMSSMMGG)
+ char volumeEffeciveDate[17];
+ // File structure version (always 1)
+ unsigned char fileStructVersion;
+ // Padding
+ unsigned char dummy0;
+ // Application specific data (says CD-XA001 at [141], the rest are null bytes)
+ unsigned char appData[512];
+ // Padding
+ unsigned char pad4[653];
+
+} ISO_DESCRIPTOR;
+
+// Leave non-aligned structure packing
+#pragma pack(pop)
+
+extern char _cd_media_changed;
+int _cd_iso_last_dir_lba;
+
+u_char _cd_iso_descriptor_buff[2048];
+u_char *_cd_iso_pathtable_buff=NULL;
+u_char *_cd_iso_directory_buff=NULL;
+int _cd_iso_directory_len;
+
+int _CdReadIsoDescriptor(int session_offs)
+{
+ int i;
+ CdlLOC loc;
+ ISO_DESCRIPTOR *descriptor;
+
+ // Seek to volume descriptor location
+ CdIntToPos(16+session_offs, &loc);
+ if( !CdControl(CdlSetloc, (u_char*)&loc, 0) )
+ {
+#ifdef DEBUG
+ printf("psxcd: Could not set seek destination.\n");
+#endif
+ return -1;
+ }
+
+ // Read volume descriptor
+ CdRead(1, (u_int*)_cd_iso_descriptor_buff, CdlModeSpeed);
+ if( CdReadSync(0, 0) )
+ {
+#ifdef DEBUG
+ printf("psxcd: Error reading ISO volume descriptor.\n");
+#endif
+ return -1;
+ }
+
+
+ // Verify if volume descriptor is present
+ descriptor = (ISO_DESCRIPTOR*)_cd_iso_descriptor_buff;
+ if( strncmp("CD001", descriptor->header.id, 5) )
+ {
+#ifdef DEBUG
+ printf("psxcd: Disc does not have a ISO9660 file system.\n");
+#endif
+ return -1;
+ }
+
+#ifdef DEBUG
+ printf("psxcd_dbg: Path table LBA = %d\n", descriptor->pathTable1Offs);
+ printf("psxcd_dbg: Path table len = %d\n", descriptor->pathTableSize.lsb);
+#endif
+
+ // Allocate path table buffer
+ i = ((2047+descriptor->pathTableSize.lsb)>>11)<<11;
+ if( _cd_iso_pathtable_buff )
+ {
+ free(_cd_iso_pathtable_buff);
+ }
+ _cd_iso_pathtable_buff = (u_char*)malloc(i);
+
+#ifdef DEBUG
+ printf("psxcd_dbg: Allocated %d bytes for path table.\n", i);
+#endif
+
+ // Read path table
+ CdIntToPos(descriptor->pathTable1Offs, &loc);
+ CdControl(CdlSetloc, (u_char*)&loc, 0);
+ CdRead(i>>11, (u_int*)_cd_iso_pathtable_buff, CdlModeSpeed);
+ if( CdReadSync(0, 0) )
+ {
+#ifdef DEBUG
+ printf("psxcd: Error reading ISO path table.\n");
+#endif
+ return -1;
+ }
+
+ _cd_iso_last_dir_lba = 0;
+
+ return 0;
+}
+
+int _CdReadIsoDirectory(int lba)
+{
+ int i;
+ CdlLOC loc;
+ ISO_DIR_ENTRY *direntry;
+
+ if( lba == _cd_iso_last_dir_lba )
+ {
+ return 0;
+ }
+
+ CdIntToPos(lba, &loc);
+ i = CdPosToInt(&loc);
+#ifdef DEBUG
+ printf("psxcd_dbg: Seek to sector %d\n", i);
+#endif
+ if( !CdControl(CdlSetloc, (u_char*)&loc, 0) )
+ {
+#ifdef DEBUG
+ printf("psxcd: Could not set seek destination.\n");
+#endif
+ return -1;
+ }
+
+ if( _cd_iso_directory_buff )
+ {
+ free(_cd_iso_directory_buff);
+ }
+
+ // Read first sector of directory record
+ _cd_iso_directory_buff = (u_char*)malloc(2048);
+ CdRead(1, (u_int*)_cd_iso_directory_buff, CdlModeSpeed);
+ if( CdReadSync(0, 0) )
+ {
+#ifdef DEBUG
+ printf("psxcd: Error reading initial directory record.\n");
+#endif
+ return -1;
+ }
+
+ direntry = (ISO_DIR_ENTRY*)_cd_iso_directory_buff;
+ _cd_iso_directory_len = direntry->entrySize.lsb;
+
+#ifdef DEBUG
+ printf("psxcd_dbg: Location of directory record = %d\n", direntry->entryOffs.lsb);
+ printf("psxcd_dbg: Size of directory record = %d\n", _cd_iso_directory_len);
+#endif
+
+ if( _cd_iso_directory_len > 2048 )
+ {
+ if( !CdControl(CdlSetloc, (u_char*)&loc, 0) )
+ {
+#ifdef DEBUG
+ printf("psxcd: Could not set seek destination.\n");
+#endif
+ return -1;
+ }
+
+ free(_cd_iso_directory_buff);
+ i = ((2047+_cd_iso_directory_len)>>11)<<11;
+ _cd_iso_directory_buff = (u_char*)malloc(i);
+#ifdef DEBUG
+ printf("psxcd_dbg: Allocated %d bytes for directory record.\n", i);
+#endif
+
+ CdRead(i>>11, (u_int*)_cd_iso_directory_buff, CdlModeSpeed);
+ if( CdReadSync(0, 0) )
+ {
+#ifdef DEBUG
+ printf("psxcd: Error reading initial directory record.\n");
+#endif
+ return -1;
+ }
+ }
+
+ _cd_iso_last_dir_lba = lba;
+
+ return 0;
+}
+
+#ifdef DEBUG
+
+void dump_directory(void)
+{
+ int i;
+ int dir_pos;
+ ISO_DIR_ENTRY *dir_entry;
+ char namebuff[16];
+
+ printf("psxcd_dbg: Cached directory record contents:\n");
+
+ i = 0;
+ dir_pos = 0;
+ while(1)
+ {
+ dir_entry = (ISO_DIR_ENTRY*)(_cd_iso_directory_buff+dir_pos);
+
+ strncpy(namebuff,
+ _cd_iso_directory_buff+dir_pos+sizeof(ISO_DIR_ENTRY), dir_entry->identifierLen);
+
+ printf("P:%d L:%d %s\n", dir_pos, dir_entry->identifierLen, namebuff);
+
+ dir_pos += dir_entry->entryLength;
+ i++;
+
+ // Check if padding is reached (end of record sector)
+ if( _cd_iso_directory_buff[dir_pos] == 0 )
+ {
+ // Snap it to next sector
+ dir_pos = ((dir_pos+2047)>>11)<<11;
+
+ // Break if exceeds length of directory buffer (end)
+ if( dir_pos >= _cd_iso_directory_len )
+ {
+ break;
+ }
+ }
+ }
+
+ printf("--\n");
+
+}
+
+void dump_pathtable(void)
+{
+ u_char *tbl_pos;
+ ISO_PATHTABLE_ENTRY *tbl_entry;
+ ISO_DESCRIPTOR *descriptor;
+ char namebuff[16];
+
+ printf("psxcd_dbg: Path table entries:\n");
+
+ descriptor = (ISO_DESCRIPTOR*)_cd_iso_descriptor_buff;
+
+ tbl_pos = _cd_iso_pathtable_buff;
+ tbl_entry = (ISO_PATHTABLE_ENTRY*)tbl_pos;
+
+ while( (int)(tbl_pos-_cd_iso_pathtable_buff) <
+ descriptor->pathTableSize.lsb )
+ {
+ strncpy(namebuff,
+ tbl_pos+sizeof(ISO_PATHTABLE_ENTRY),
+ tbl_entry->nameLength);
+
+ printf("psxcd_dbg: %s\n", namebuff);
+
+ // Advance to next entry
+ tbl_pos += sizeof(ISO_PATHTABLE_ENTRY)
+ +(2*((tbl_entry->nameLength+1)/2));
+
+ tbl_entry = (ISO_PATHTABLE_ENTRY*)tbl_pos;
+ }
+
+}
+
+#endif
+
+int get_pathtable_entry(int entry, ISO_PATHTABLE_ENTRY *tbl, char *namebuff)
+{
+ int i;
+ u_char *tbl_pos;
+ ISO_PATHTABLE_ENTRY *tbl_entry;
+ ISO_DESCRIPTOR *descriptor;
+
+ descriptor = (ISO_DESCRIPTOR*)_cd_iso_descriptor_buff;
+
+ tbl_pos = _cd_iso_pathtable_buff;
+ tbl_entry = (ISO_PATHTABLE_ENTRY*)tbl_pos;
+
+ i = 0;
+ while( (int)(tbl_pos-_cd_iso_pathtable_buff) <
+ descriptor->pathTableSize.lsb )
+ {
+ if( i == (entry-1) )
+ {
+ if( namebuff )
+ {
+ strncpy(namebuff,
+ tbl_pos+sizeof(ISO_PATHTABLE_ENTRY),
+ tbl_entry->nameLength);
+ }
+
+ if( tbl )
+ {
+ *tbl = *tbl_entry;
+ }
+
+ return 0;
+ }
+
+ // Advance to next entry
+ tbl_pos += sizeof(ISO_PATHTABLE_ENTRY)
+ +(2*((tbl_entry->nameLength+1)/2));
+
+ tbl_entry = (ISO_PATHTABLE_ENTRY*)tbl_pos;
+ i++;
+ }
+
+ if( entry <= 0 )
+ {
+ return i+1;
+ }
+
+ return -1;
+}
+
+char *resolve_pathtable_path(int entry, char *rbuff)
+{
+ char namebuff[16];
+ ISO_PATHTABLE_ENTRY tbl_entry;
+
+ *rbuff = 0;
+
+ do
+ {
+ if( get_pathtable_entry(entry, &tbl_entry, namebuff) )
+ {
+ return NULL;
+ }
+
+ rbuff -= tbl_entry.nameLength;
+ memcpy(rbuff, namebuff, tbl_entry.nameLength);
+ rbuff--;
+ *rbuff = '\\';
+
+ // Parse to the parent
+ entry = tbl_entry.dirLevel;
+
+ } while( entry > 1 );
+
+ return rbuff;
+}
+
+int find_dir_entry(const char *name, ISO_DIR_ENTRY *dirent)
+{
+ int i;
+ int dir_pos;
+ ISO_DIR_ENTRY *dir_entry;
+ char namebuff[16];
+
+#ifdef DEBUG
+ printf("psxcd_dbg: Locating file %s.\n", name);
+#endif
+
+ i = 0;
+ dir_pos = 0;
+ while(dir_pos < _cd_iso_directory_len)
+ {
+ dir_entry = (ISO_DIR_ENTRY*)(_cd_iso_directory_buff+dir_pos);
+
+ if( !(dir_entry->flags & 0x2) )
+ {
+ strncpy(namebuff,
+ _cd_iso_directory_buff+dir_pos+sizeof(ISO_DIR_ENTRY),
+ dir_entry->identifierLen);
+
+ if( strcmp(namebuff, name) == 0 )
+ {
+ *dirent = *dir_entry;
+ return 0;
+ }
+ }
+
+ dir_pos += dir_entry->entryLength;
+ i++;
+
+ // Check if padding is reached (end of record sector)
+ if( _cd_iso_directory_buff[dir_pos] == 0 )
+ {
+ // Snap it to next sector
+ dir_pos = ((dir_pos+2047)>>11)<<11;
+
+ }
+ }
+
+ return -1;
+}
+
+char *get_pathname(char *path, const char *filename)
+{
+ char *c;
+ c = strrchr(filename, '\\');
+
+ if(( c == filename ) || ( !c ))
+ {
+ path[0] = '\\';
+ path[1] = 0;
+ return NULL;
+ }
+
+ strncpy(path, filename, (int)(c-filename));
+ return path;
+}
+
+char *get_filename(char *name, const char *filename)
+{
+ char *c;
+ c = strrchr(filename, '\\');
+
+ if(( c == filename ) || ( !c ))
+ {
+ strcpy(name, filename+1);
+ return name;
+ }
+
+ c++;
+ strcpy(name, c);
+ return name;
+}
+
+CdlFILE *CdSearchFile(CdlFILE *fp, const char *filename)
+{
+ int i,j,found_dir,num_dirs;
+ int dir_len;
+ char tpath_rbuff[128];
+ char search_path[128];
+ char *rbuff;
+ ISO_PATHTABLE_ENTRY tbl_entry;
+ ISO_DIR_ENTRY dir_entry;
+
+ // Read ISO descriptor if changed flag is set
+ if( _cd_media_changed )
+ {
+ // Read ISO descriptor and path table
+ if( _CdReadIsoDescriptor(0) )
+ {
+#ifdef DEBUG
+ printf("psxcd: Could not read ISO file system.\n");
+#endif
+ return NULL;
+ }
+#ifdef DEBUG
+ printf("psxcd: ISO file system cache updated.\n");
+#endif
+ _cd_media_changed = 0;
+ }
+
+ // Get number of directories in path table
+ num_dirs = get_pathtable_entry(0, NULL, NULL);
+
+#ifdef DEBUG
+ printf("psxcd_dbg: Directories in path table: %d\n", num_dirs);
+
+ rbuff = resolve_pathtable_path(num_dirs-1, tpath_rbuff+127);
+
+ if( !rbuff )
+ {
+ printf("psxcd_dbg: Could not resolve path.\n");
+ }
+ else
+ {
+ printf("psxcd_dbg: Longest path: %s|\n", rbuff);
+ }
+#endif
+
+ if( get_pathname(search_path, filename) )
+ {
+#ifdef DEBUG
+ printf("psxcd_dbg: Search path = %s|\n", search_path);
+#endif
+ }
+
+ // Search the pathtable for a matching path
+ found_dir = 0;
+ for(i=1; i<num_dirs; i++)
+ {
+ rbuff = resolve_pathtable_path(i, tpath_rbuff+127);
+#ifdef DEBUG
+ printf("psxcd_dbg: Found = %s|\n", rbuff);
+#endif
+ if( rbuff )
+ {
+ if( strcmp(search_path, rbuff) == 0 )
+ {
+ found_dir = i;
+ break;
+ }
+ }
+ }
+
+ if( !found_dir )
+ {
+#ifdef DEBUG
+ printf("psxcd_dbg: Directory path not found.\n");
+#endif
+ return NULL;
+ }
+
+#ifdef DEBUG
+ printf("psxcd_dbg: Found directory at record %d!\n", found_dir);
+#endif
+
+ get_pathtable_entry(found_dir, &tbl_entry, NULL);
+
+#ifdef DEBUG
+ printf("psxcd_dbg: Directory LBA = %d\n", tbl_entry.dirOffs);
+#endif
+
+ _CdReadIsoDirectory(tbl_entry.dirOffs);
+
+ get_filename(fp->name, filename);
+
+ // Add version number if not specified
+ if( !strchr(fp->name, ';') )
+ {
+ strcat(fp->name, ";1");
+ }
+
+#ifdef DEBUG
+ dump_directory();
+#endif
+
+ if( find_dir_entry(fp->name, &dir_entry) )
+ {
+#ifdef DEBUG
+ printf("psxcd: Could not find file.\n");
+#endif
+ return NULL;
+ }
+
+#ifdef DEBUG
+ printf("psxcd_dbg: Located file at LBA %d.\n", dir_entry.entryOffs.lsb);
+#endif
+
+ CdIntToPos(dir_entry.entryOffs.lsb, &fp->loc);
+ fp->size = dir_entry.entrySize.lsb;
+
+ return fp;
+}
diff --git a/libpsn00b/libpsxcd/makefile b/libpsn00b/libpsxcd/makefile
new file mode 100644
index 0000000..87466b3
--- /dev/null
+++ b/libpsn00b/libpsxcd/makefile
@@ -0,0 +1,38 @@
+# Run using make (Linux) or gmake (BSD)
+# Part of the PSn00bSDK Project
+# 2019 Lameguy64 / Meido-Tek Productions
+
+include ../common.mk
+
+TARGET = ../libpsxcd.a
+
+CFILES = $(notdir $(wildcard ./*.c))
+AFILES = $(notdir $(wildcard ./*.s))
+OFILES = $(addprefix build/,$(CFILES:.c=.o) $(AFILES:.s=.o))
+
+INCLUDE = -I../include
+
+CFLAGS = -g -msoft-float -fno-builtin -fdata-sections -ffunction-sections -Wa,--strip-local-absolute
+AFLAGS = -g -msoft-float -Wa,--strip-local-absolute
+
+CC = $(PREFIX)gcc
+AS = $(PREFIX)as
+AR = $(PREFIX)ar
+RANLIB = $(PREFIX)ranlib
+
+all: $(TARGET)
+
+$(TARGET): $(OFILES)
+ $(AR) cr $(TARGET) $(OFILES)
+ $(RANLIB) $(TARGET)
+
+build/%.o: %.c
+ @mkdir -p $(dir $@)
+ $(CC) -O2 $(CFLAGS) $(INCLUDE) -c $< -o $@
+
+build/%.o: %.s
+ @mkdir -p $(dir $@)
+ $(CC) $(AFLAGS) $(INCLUDE) -c $< -o $@
+
+clean:
+ rm -Rf build $(TARGET)
diff --git a/libpsn00b/libpsxcd/psxcd.c b/libpsn00b/libpsxcd/psxcd.c
new file mode 100644
index 0000000..5be9eec
--- /dev/null
+++ b/libpsn00b/libpsxcd/psxcd.c
@@ -0,0 +1,319 @@
+#include <stdio.h>
+#include "psxcd.h"
+
+extern volatile char _cd_ack_wait;
+extern volatile unsigned char _cd_last_int;
+extern volatile unsigned char _cd_last_mode;
+extern volatile unsigned char _cd_status;
+extern volatile CdlCB _cd_callback_int1_data;
+volatile unsigned char *_cd_result_ptr;
+
+extern volatile char _cd_media_changed;
+
+void _cd_init(void);
+void _cd_control(unsigned char com, unsigned char *param, int plen);
+void _cd_wait_ack(void);
+void _cd_wait(void);
+
+int CdInit(int mode)
+{
+ // Sets up CD-ROM hardware and low-level subsystem
+ _cd_init();
+
+ // So CD ISO file system component will update the ISO descriptor
+ _cd_media_changed = 1;
+
+ // Issue commands to initialize the CD-ROM hardware
+ CdControl(CdlNop, 0, 0);
+ CdControl(CdlInit, 0, 0);
+
+ if( CdSync(0, 0) != CdlDiskError )
+ {
+ CdControl(CdlDemute, 0, 0);
+ printf("psxcd: Init Ok!\n");
+ }
+ else
+ {
+ printf("psxcd: Error initializing. Bad disc/drive or no disc inserted.\n");
+ }
+
+ return 1;
+}
+
+int CdControl(unsigned char com, unsigned char *param, unsigned char *result)
+{
+ // Don't issue command if ack is not received yet
+ if( _cd_ack_wait )
+ {
+ return 0;
+ }
+
+ _cd_result_ptr = result;
+
+ CdControlF(com, param);
+ _cd_wait_ack();
+
+ return 1;
+}
+
+int CdControlB(unsigned char com, unsigned char *param, unsigned char *result)
+{
+ if( !CdControl(com, param, result) )
+ {
+ return 0;
+ }
+
+ CdSync(0, 0);
+ return 1;
+}
+
+int CdControlF(unsigned char com, unsigned char *param)
+{
+ int param_len=0;
+
+ // Command specific parameters
+ switch(com)
+ {
+ case CdlSetloc:
+ param_len = 3;
+ break;
+ case CdlPlay:
+ if( param )
+ {
+ param_len = 1;
+ }
+ break;
+ case CdlSetfilter:
+ param_len = 2;
+ break;
+ case CdlSetmode:
+ param_len = 1;
+ break;
+ case CdlSetsession:
+ param_len = 1;
+ break;
+ case CdlTest:
+ param_len = 1;
+ break;
+ case CdlGetTD:
+ param_len = 1;
+ }
+
+ // Issue Setloc if parameters are specified on CdlReadN and CdlReadS
+ if( ( com == CdlReadN ) || ( com == CdlReadS ) )
+ {
+ if( param )
+ {
+ _cd_control(CdlSetloc, param, 3);
+ }
+ }
+
+ // Issue CD command
+ _cd_control(com, param, param_len);
+
+ return 1;
+}
+
+int CdSync(int mode, unsigned char *result)
+{
+ int cdirq;
+
+ if( mode )
+ {
+ if( result )
+ {
+ *result = _cd_status;
+ }
+
+ cdirq = _cd_last_int;
+ if( cdirq == CdlAcknowledge )
+ {
+ cdirq = CdlComplete;
+ }
+ return cdirq;
+ }
+
+ _cd_wait();
+
+ if( result )
+ {
+ *result = _cd_status;
+ }
+
+ cdirq = _cd_last_int;
+ if( cdirq == CdlAcknowledge )
+ {
+ cdirq = CdlComplete;
+ }
+
+ return cdirq;
+}
+
+int CdGetToc(CdlLOC *toc)
+{
+ u_char track_info[8];
+ int i,tracks;
+
+ // Get number of tracks
+ if( !CdControl(CdlGetTN, 0, track_info) )
+ {
+ return 0;
+ }
+
+ if( CdSync(1, 0) != CdlComplete )
+ {
+ return 0;
+ }
+
+ tracks = 1+(btoi(track_info[2])-btoi(track_info[1]));
+
+ // Get track positions
+ for(i=0; i<tracks; i++)
+ {
+ int t = itob(1+i);
+ if( !CdControl(CdlGetTD, (u_char*)&t, (u_char*)&toc[i]) )
+ {
+ return 0;
+ }
+ if( CdSync(1, 0) != CdlComplete )
+ {
+ return 0;
+ }
+ toc[i].sector = 0;
+ toc[i].track = 1+i;
+ }
+
+ return tracks;
+}
+
+CdlLOC *CdIntToPos(int i, CdlLOC *p) {
+
+ i += 150;
+
+ p->minute = itob((i/75)/60);
+ p->second = itob((i/75)%60);
+ p->sector = itob(i%75);
+
+ return p;
+
+}
+
+int CdPosToInt(CdlLOC *p)
+{
+ return ((75*(btoi(p->minute)*60))+(75*btoi(p->second))+btoi(p->sector))-150;
+}
+
+int CdStatus(void)
+{
+ return _cd_status;
+}
+
+int CdMode(void)
+{
+ return _cd_last_mode;
+}
+
+
+// CD data read routines
+volatile int _cd_sector_count = 0;
+volatile unsigned int *_cd_read_addr;
+volatile unsigned char _cd_read_result[8];
+volatile unsigned int _cd_read_oldcb;
+volatile unsigned int _cd_read_sector_sz;
+volatile CdlCB _cd_read_cb;
+
+// Sector callback
+static void _CdReadReadyCallback(int status, unsigned char *result)
+{
+ if( status == CdlDataReady )
+ {
+ // Fetch sector from CD controller
+ CdGetSector((void*)_cd_read_addr, _cd_read_sector_sz);
+
+ // Increment destination address
+ _cd_read_addr += _cd_read_sector_sz>>2;
+
+ // Subtract sector count
+ _cd_sector_count--;
+ }
+
+ // End reading with pause command when sector count reaches zero
+ // or when an error occurs
+ if( ( _cd_sector_count <= 0 ) || ( status == CdlDiskError ) )
+ {
+ // Stop reading
+ _cd_control(CdlPause, 0, 0);
+
+ // Revert previous ready callback
+ _cd_callback_int1_data = (CdlCB)_cd_read_oldcb;
+
+ // Execute read completion callback
+ if( _cd_read_cb )
+ {
+ _cd_read_cb(status, result);
+ }
+ }
+}
+
+int CdRead(int sectors, unsigned int *buf, int mode)
+{
+ // Set sectors to read count
+ _cd_sector_count = sectors;
+ _cd_read_addr = buf;
+
+ // Determine sector based on mode flags
+ if( mode & CdlModeSize0 )
+ {
+ _cd_read_sector_sz = 2328;
+ }
+ else if( mode & CdlModeSize1 )
+ {
+ _cd_read_sector_sz = 2340;
+ }
+ else
+ {
+ _cd_read_sector_sz = 2048;
+ }
+
+ // Set readt callback
+ _cd_read_oldcb = CdReadyCallback(_CdReadReadyCallback);
+
+ // Set specified mode
+ CdControl(CdlSetmode, (unsigned char*)&mode, 0);
+
+ // Begin reading sectors
+ CdControl(CdlReadN, 0, (unsigned char*)_cd_read_result);
+
+ return 0;
+}
+
+int CdReadSync(int mode, unsigned char *result)
+{
+ if( mode )
+ {
+ if( CdSync(1, 0) == CdlDiskError )
+ {
+ return -1;
+ }
+ return _cd_sector_count;
+ }
+
+ while(_cd_sector_count > 0);
+ if( CdSync(0, result) != CdlComplete )
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+unsigned int CdReadCallback(CdlCB func)
+{
+ unsigned int old_func;
+
+ old_func = (unsigned int)_cd_read_cb;
+
+ _cd_read_cb = func;
+
+ return old_func;
+}
diff --git a/libpsn00b/libpsxcd/psxcd_asm.s b/libpsn00b/libpsxcd/psxcd_asm.s
new file mode 100644
index 0000000..7fe1c1d
--- /dev/null
+++ b/libpsn00b/libpsxcd/psxcd_asm.s
@@ -0,0 +1,520 @@
+.set noreorder
+
+.include "hwregs_a.h"
+
+.section .text
+
+.global _cd_init
+.type _cd_init, @function
+_cd_init:
+
+ addiu $sp, -4
+ sw $ra, 0($sp)
+
+ jal EnterCriticalSection
+ nop
+
+ lui $a3, IOBASE # Acknowledge all CD IRQs
+ li $v0, 1
+ sb $v0, CD_REG0($a3)
+ li $v0, 0x1f
+ sb $v0, CD_REG3($a3)
+ sb $v0, CD_REG2($a3) # Enable all IRQs
+
+ sb $0 , CD_REG0($a3)
+ sb $0 , CD_REG3($a3)
+
+ li $v0, 0x1325
+ sw $v0, COM_DELAY($a3)
+
+ la $a1, _cd_handler
+ jal InterruptCallback
+ li $a0, 2
+
+ li $v0, 2 # Set CD left volume
+ sb $v0, CD_REG0($a3)
+ li $v0, 0x80
+ sb $v0, CD_REG2($a3)
+
+ li $v0, 3 # Set CD right volume
+ sb $v0, CD_REG0($a3)
+ li $v0, 0x80
+ sb $v0, CD_REG1($a3)
+
+ li $v0, 0x20 # Apply volume
+ sb $v0, CD_REG3($a3)
+
+ # Clear a bunch of stats
+ la $v0, _cd_ack_wait
+ sb $0 , 0($v0)
+ la $v0, _cd_complt_wait
+ sb $0 , 0($v0)
+ la $v0, _cd_last_cmd
+ sb $0 , 0($v0)
+ la $v0, _cd_last_mode
+ sb $0 , 0($v0)
+ la $v0, _cd_last_int
+ sb $0 , 0($v0)
+
+ la $v0, _cd_result_ptr
+ sw $0 , 0($v0)
+
+ # Clear callback hooks
+ la $v0, _cd_callback_int1_data
+ sw $0 , 0($v0)
+ la $v0, _cd_callback_int4
+ sw $0 , 0($v0)
+
+ la $v0, _cd_read_cb
+ sw $0 , 0($v0)
+
+ lw $v0, DPCR($a3)
+ li $v1, 0xB000
+ or $v0, $v1
+ sw $v0, DPCR($a3)
+
+ jal ExitCriticalSection
+ nop
+
+ #li $a0, 0x01 # GetStat
+ #jal _cd_control
+ #move $a2, $0
+ #jal _cd_wait
+ #nop
+
+ #li $a0, 0x0a # Init
+ #jal _cd_control
+ #move $a2, $0
+ #jal _cd_wait
+ #nop
+
+ #li $a0, 0x0c # Demute
+ #jal _cd_control
+ #move $a2, $0
+ #jal _cd_wait
+ #nop
+
+ #la $a0, _cd_init_msg
+ #jal printf
+ #addiu $sp, -16
+ #addiu $sp, 16
+
+ lw $ra, 0($sp)
+ addiu $sp, 4
+ jr $ra
+ nop
+
+
+.global _cd_wait
+.type _cd_wait, @function
+_cd_wait:
+ la $v0, _cd_ack_wait
+ lbu $v0, 0($v0)
+ nop
+ bnez $v0, _cd_wait
+ nop
+.Lcomplete:
+ la $v0, _cd_complt_wait
+ lbu $v0, 0($v0)
+ nop
+ bnez $v0, .Lcomplete
+ nop
+ jr $ra
+ nop
+
+
+.global _cd_wait_ack
+.type _cd_wait_ack, @function
+_cd_wait_ack:
+ la $v0, _cd_ack_wait
+ lbu $v0, 0($v0)
+ nop
+ bnez $v0, _cd_wait_ack
+ nop
+ jr $ra
+ nop
+
+
+.global _cd_wait_complt
+.type _cd_wait_complt, @function
+_cd_wait_complt:
+ la $v0, _cd_complt_wait
+ lbu $v0, 0($v0)
+ nop
+ bnez $v0, _cd_wait_complt
+ nop
+ jr $ra
+ nop
+
+
+.type _cd_handler, @function
+_cd_handler:
+
+ addiu $sp, -4
+ sw $ra, 0($sp)
+
+ lui $a3, IOBASE # Print out IRQ number
+ li $v0, 1
+ sb $v0, CD_REG0($a3)
+
+ lbu $v0, CD_REG3($a3)
+ nop
+ andi $v0, 0x7
+
+ la $v1, _cd_last_int # Save last IRQ value
+ sb $v0, 0($v1)
+
+ bne $v0, 0x1, .Lno_data
+ nop
+
+ sb $0 , CD_REG0($a3) # Load data FIFO on INT1
+ li $v1, 0x80
+ sb $v1, CD_REG3($a3)
+
+.Lno_data:
+
+ li $v1, 1
+ sb $v1, CD_REG0($a3) # Clear CD interrupt and parameter FIFO
+ li $v1, 0x5f
+ sb $v1, CD_REG3($a3)
+ li $v1, 0x40
+ sb $v1, CD_REG3($a3)
+
+ li $v1, 0 # Delay when clearing parameter FIFO
+ sw $v1, 0($0)
+ li $v1, 1
+ sw $v1, 0($0)
+ li $v1, 2
+ sw $v1, 0($0)
+ li $v1, 3
+ sw $v1, 0($0)
+
+ beq $v0, 0x1, .Lirq_1 # Data ready
+ nop
+ beq $v0, 0x2, .Lirq_2 # Command finish
+ nop
+ beq $v0, 0x3, .Lirq_3 # Acknowledge
+ nop
+ beq $v0, 0x4, .Lirq_4 # Data/track end
+ nop
+ beq $v0, 0x5, .Lirq_5 # Error
+ nop
+
+ b .Lirq_misc
+ nop
+
+.Lirq_1: # Data ready
+
+ jal _cd_fetch_result
+ nop
+
+ la $v0, _cd_callback_int1_data
+ lw $v0, 0($v0)
+ nop
+ beqz $v0, .Lirq_misc
+ nop
+
+ la $a0, _cd_last_int
+ lbu $a0, 0($a0)
+ la $a1, _cd_result_ptr
+ lw $a1, 0($a1)
+
+ jalr $v0
+ addiu $sp, -16
+ addiu $sp, 16
+
+ b .Lirq_misc
+ nop
+
+.Lirq_2: # Command complete
+
+ jal _cd_fetch_result
+ nop
+
+ la $v0, _cd_complt_wait
+ sb $0 , 0($v0)
+
+ la $v0, _cd_sync_cb
+ lw $v0, 0($v0)
+ nop
+ beqz $v0, .Lirq_misc
+ nop
+
+ la $a0, _cd_last_int
+ lbu $a0, 0($a0)
+ la $a1, _cd_result_ptr
+ lw $a1, 0($a1)
+ jalr $v0
+ addiu $sp, -16
+ addiu $sp, 16
+
+ b .Lirq_misc
+ nop
+
+.Lirq_3: # Command acknowledge
+
+ jal _cd_fetch_result
+ nop
+
+ la $v0, _cd_ack_wait
+ sb $0 , 0($v0)
+
+ b .Lirq_misc
+ nop
+
+.Lirq_4:
+
+ jal _cd_fetch_result
+ nop
+
+ la $v0, _cd_callback_int4
+ lw $v0, 0($v0)
+ nop
+ beqz $v0, .Lirq_misc
+ nop
+
+ jalr $v0
+ addiu $sp, -16
+ addiu $sp, 16
+
+ b .Lirq_misc
+ nop
+
+.Lirq_5: # Error
+
+ jal _cd_fetch_result
+ nop
+
+ la $v0, _cd_complt_wait
+ lbu $v0, 0($v0)
+ nop
+ beqz $v0, .Lno_complete
+ nop
+
+ la $v0, _cd_sync_cb
+ lw $v0, 0($v0)
+ nop
+ beqz $v0, .Lno_complete
+ nop
+
+ li $a0, 0x05 # CdlDiskError
+ la $a1, _cd_result_ptr
+ lw $a1, 0($a1)
+ jalr $v0
+ addiu $sp, -16
+ addiu $sp, 16
+
+.Lno_complete:
+
+ la $v0, _cd_complt_wait
+ sb $0 , 0($v0)
+ la $v0, _cd_ack_wait
+ sb $0 , 0($v0)
+
+ la $v0, _cd_callback_int1_data
+ lw $v0, 0($v0)
+ nop
+ beqz $v0, .Lirq_misc
+ nop
+
+ li $a0, 0x05 # CdlDiskError
+ la $a1, _cd_result_ptr
+ lw $a1, 0($a1)
+
+ jalr $v0
+ addiu $sp, -16
+ addiu $sp, 16
+
+.Lirq_misc:
+
+ lw $ra, 0($sp)
+ addiu $sp, 4
+ jr $ra
+ nop
+
+
+_cd_fetch_result:
+
+ lui $a3, IOBASE
+
+ la $a0, _cd_status
+ lbu $v0, CD_REG1($a3)
+
+ la $v1, _cd_last_int
+ lbu $v1, 0($v1)
+ nop
+ beq $v1, 0x2, .Lirq2_checks
+ nop
+
+ # IRQ3 checks
+
+ la $v1, _cd_last_cmd
+ lbu $v1, 0($v1)
+ nop
+ beq $v1, 0x10, .Lskip_status
+ nop
+ beq $v1, 0x11, .Lskip_status
+ nop
+
+ b .Lwrite_status
+ nop
+
+ # IRQ2 checks
+
+.Lirq2_checks:
+
+ la $v1, _cd_last_cmd
+ lbu $v1, 0($v1)
+ nop
+ beq $v1, 0x1D, .Lskip_status
+ nop
+
+.Lwrite_status:
+
+ sb $v0, 0($a0)
+
+.Lskip_status:
+
+ la $a0, _cd_result_ptr
+ lw $a0, 0($a0)
+ nop
+ beqz $a0, .Lno_result
+ nop
+ sb $v0, 0($a0)
+ addiu $a0, 1
+
+.Lread_futher_result:
+
+ lbu $v0, CD_REG0($a3)
+ nop
+ andi $v0, 0x20
+ beqz $v0, .Lno_result
+ nop
+
+ lbu $v0, CD_REG1($a3)
+ lbu $v1, CD_REG0($a3)
+ sb $v0, 0($a0)
+ andi $v1, 0x20
+ bnez $v1, .Lread_futher_result
+ addiu $a0, 1
+
+.Lno_result:
+
+ jr $ra
+ nop
+
+
+.global CdAutoPauseCallback
+.type CdAutoPauseCallback, @function
+CdAutoPauseCallback:
+
+ addiu $sp, -8
+ sw $ra, 0($sp)
+ sw $a0, 4($sp)
+
+ la $v1, _cd_callback_int4
+ lw $v0, 0($v1)
+
+ la $v1, _cd_callback_int4
+
+ jal EnterCriticalSection
+ nop
+
+ lw $a0, 4($sp)
+ nop
+ sw $a0, 0($v1)
+
+ jal ExitCriticalSection
+ nop
+
+ lw $ra, 0($sp)
+ addiu $sp, 8
+ jr $ra
+ nop
+
+
+.global CdReadyCallback
+.type CdReadyCallback, @function
+CdReadyCallback:
+
+ addiu $sp, -12
+ sw $ra, 0($sp)
+ sw $a0, 4($sp)
+
+ la $v1, _cd_callback_int1_data
+ lw $v0, 0($v1)
+
+ la $v1, _cd_callback_int1_data
+ sw $v0, 8($sp)
+
+ jal EnterCriticalSection
+ nop
+
+ lw $a0, 4($sp)
+ nop
+ sw $a0, 0($v1)
+
+ jal ExitCriticalSection
+ nop
+
+ lw $ra, 0($sp)
+ lw $v0, 8($sp)
+ jr $ra
+ addiu $sp, 12
+
+
+.global CdSyncCallback
+.type CdSyncCallback, @function
+CdSyncCallback:
+ addiu $sp, -12
+ sw $ra, 0($sp)
+ sw $a0, 4($sp)
+
+ la $v1, _cd_sync_cb
+ lw $v0, 0($v1)
+
+ la $v1, _cd_sync_cb
+ sw $v0, 8($sp)
+
+ jal EnterCriticalSection
+ nop
+
+ lw $a0, 4($sp)
+ nop
+ sw $a0, 0($v1)
+
+ jal ExitCriticalSection
+ nop
+
+ lw $ra, 0($sp)
+ lw $v0, 8($sp)
+ jr $ra
+ addiu $sp, 12
+
+
+.section .data
+
+.global psxcd_credits
+.type psxcd_credits, @object
+psxgpu_credits:
+ .ascii "psxcd library programs by Lameguy64\n"
+ .asciiz "2019 PSn00bSDK Project / Meido-Tek Productions\n"
+
+_cd_init_msg:
+.asciiz "psxcd: Init OK\n"
+.align 4
+
+.comm _cd_last_cmd, 1, 1
+.comm _cd_last_mode, 1, 1
+.comm _cd_ack_wait, 1, 1
+.comm _cd_complt_wait, 1, 1
+
+.comm _cd_status, 1, 1
+.comm _cd_last_int, 1, 1
+
+.comm _cd_media_changed, 1, 1
+
+# Callback hooks
+.comm _cd_callback_int1_data, 4, 4 # Data IRQ callback
+.comm _cd_callback_int4, 4, 4 # Autopause callback
+.comm _cd_sync_cb, 4, 4
diff --git a/libpsn00b/libpsxcd/readme.txt b/libpsn00b/libpsxcd/readme.txt
new file mode 100644
index 0000000..a85fab5
--- /dev/null
+++ b/libpsn00b/libpsxcd/readme.txt
@@ -0,0 +1,48 @@
+PSX CD-ROM library, part of PSn00bSDK
+2019 Lameguy64 / Meido-Tek Productions
+
+Licensed under Mozilla Public License
+
+ Open source implementation of the long awaited CD-ROM library that
+provides greater functionality than the BIOS CD-ROM subsystem. Supports
+pretty much all features of the CD-ROM hardware such as CD data read,
+CD Audio and XA audio playback. Data streaming should also be possible
+with the use of CdlReadN/CdlReadS commands and CdReadyCallback().
+
+ Library also includes an ISO9660 file system parser for locating
+files within the CD-ROM file system. Unlike the file system parser in
+the official libraries libpsxcd can parse directories containing any
+number of files. Rock-ridge and Joliet extensions are not supported
+however.
+
+ Be aware that the CD-ROM library might have some loose ends as it
+is still a work in progress, but should work flawlessly in most use
+cases.
+
+
+Library developer(s):
+
+ Lameguy64
+
+
+Library header(s):
+
+ psxcd.h
+
+
+Todo list:
+
+ * Command query mechanism so that more than 2 CD-ROM commands can
+ easily be issued in callbacks. Official library probably does this.
+
+ * Helper functions for handling disc changes (CdDiskReady and
+ CdGetDiskType) are not yet implemented.
+
+ * CdReadBreak() not yet implemented.
+
+ * Data streaming functions (prefixed with St*) not yet implemented.
+ Would require devising a PSn00bSDK equivalent of the STR file
+ format.
+
+ * Original functions for querying directory contents in the file
+ system not yet implemented. \ No newline at end of file