2020-05-11 02:55:57 +00:00
# ifndef _WIN32
# include "common.h"
# include "crossplatform.h"
# include <pthread.h>
# include <signal.h>
# include <semaphore.h>
# include <sys/types.h>
# include <unistd.h>
# include <sys/time.h>
# include <sys/statvfs.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <sys/resource.h>
# include <sys/syscall.h>
# include "CdStream.h"
# include "rwcore.h"
# include "RwHelper.h"
# define CDDEBUG(f, ...) debug ("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__)
# define CDTRACE(f, ...) printf("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__)
// #define ONE_THREAD_PER_CHANNEL // Don't use if you're not on SSD/Flash. (Also you may want to benefit from this via using all channels in Streaming.cpp)
bool flushStream [ MAX_CDCHANNELS ] ;
struct CdReadInfo
{
uint32 nSectorOffset ;
uint32 nSectorsToRead ;
void * pBuffer ;
bool bLocked ;
bool bReading ;
int32 nStatus ;
# ifdef ONE_THREAD_PER_CHANNEL
2020-10-04 19:39:54 +00:00
int8 nThreadStatus ; // 0: created 1:priority set up 2:abort now
pthread_t pChannelThread ;
sem_t * pStartSemaphore ;
2020-05-11 02:55:57 +00:00
# endif
2020-10-01 07:21:06 +00:00
sem_t * pDoneSemaphore ; // used for CdStreamSync
int32 hFile ;
2020-05-11 02:55:57 +00:00
} ;
char gCdImageNames [ MAX_CDIMAGES + 1 ] [ 64 ] ;
int32 gNumImages ;
int32 gNumChannels ;
int32 gImgFiles [ MAX_CDIMAGES ] ; // -1: error 0:unused otherwise: fd
char * gImgNames [ MAX_CDIMAGES ] ;
# ifndef ONE_THREAD_PER_CHANNEL
pthread_t _gCdStreamThread ;
2020-10-01 07:21:06 +00:00
sem_t * gCdStreamSema ; // released when we have new thing to read(so channel is set)
2020-10-04 19:39:54 +00:00
int8 gCdStreamThreadStatus ; // 0: created 1:priority set up 2:abort now
2020-05-11 02:55:57 +00:00
Queue gChannelRequestQ ;
bool _gbCdStreamOverlapped ;
# endif
CdReadInfo * gpReadInfo ;
int32 lastPosnRead ;
int _gdwCdStreamFlags ;
void * CdStreamThread ( void * channelId ) ;
void
CdStreamInitThread ( void )
{
int status ;
2020-10-04 19:39:54 +00:00
char semName [ 20 ] ;
2020-05-11 02:55:57 +00:00
# ifndef ONE_THREAD_PER_CHANNEL
gChannelRequestQ . items = ( int32 * ) calloc ( gNumChannels + 1 , sizeof ( int32 ) ) ;
gChannelRequestQ . head = 0 ;
gChannelRequestQ . tail = 0 ;
gChannelRequestQ . size = gNumChannels + 1 ;
ASSERT ( gChannelRequestQ . items ! = nil ) ;
2020-10-04 19:39:54 +00:00
gCdStreamSema = sem_open ( " /semaphore_cd_stream " , O_CREAT , 0644 , 1 ) ;
2020-05-11 02:55:57 +00:00
2020-10-01 07:21:06 +00:00
if ( gCdStreamSema = = SEM_FAILED ) {
2020-05-11 02:55:57 +00:00
CDTRACE ( " failed to create stream semaphore " ) ;
ASSERT ( 0 ) ;
return ;
}
2020-10-04 19:39:54 +00:00
# endif
2020-05-11 02:55:57 +00:00
if ( gNumChannels > 0 )
{
for ( int32 i = 0 ; i < gNumChannels ; i + + )
{
2020-10-04 19:39:54 +00:00
sprintf ( semName , " /semaphore_done%d " , i ) ;
gpReadInfo [ i ] . pDoneSemaphore = sem_open ( semName , O_CREAT , 0644 , 1 ) ;
2020-05-11 02:55:57 +00:00
2020-10-04 19:39:54 +00:00
if ( gpReadInfo [ i ] . pDoneSemaphore = = SEM_FAILED )
2020-05-11 02:55:57 +00:00
{
CDTRACE ( " failed to create sync semaphore " ) ;
ASSERT ( 0 ) ;
return ;
}
# ifdef ONE_THREAD_PER_CHANNEL
2020-10-04 19:39:54 +00:00
sprintf ( semName , " /semaphore_start%d " , i ) ;
gpReadInfo [ i ] . pStartSemaphore = sem_open ( semName , O_CREAT , 0644 , 1 ) ;
2020-05-11 02:55:57 +00:00
2020-10-04 19:39:54 +00:00
if ( gpReadInfo [ i ] . pStartSemaphore = = SEM_FAILED )
2020-05-11 02:55:57 +00:00
{
CDTRACE ( " failed to create start semaphore " ) ;
ASSERT ( 0 ) ;
return ;
}
2020-10-04 19:39:54 +00:00
gpReadInfo [ i ] . nThreadStatus = 0 ;
2020-05-11 02:55:57 +00:00
int * channelI = ( int * ) malloc ( sizeof ( int ) ) ;
* channelI = i ;
2020-10-04 19:39:54 +00:00
status = pthread_create ( & gpReadInfo [ i ] . pChannelThread , NULL , CdStreamThread , ( void * ) channelI ) ;
2020-05-11 02:55:57 +00:00
if ( status = = - 1 )
{
CDTRACE ( " failed to create sync thread " ) ;
ASSERT ( 0 ) ;
return ;
}
# endif
}
}
# ifndef ONE_THREAD_PER_CHANNEL
2020-10-04 19:39:54 +00:00
debug ( " Using one streaming thread for all channels \n " ) ;
gCdStreamThreadStatus = 0 ;
status = pthread_create ( & _gCdStreamThread , NULL , CdStreamThread , nil ) ;
if ( status = = - 1 )
{
CDTRACE ( " failed to create sync thread " ) ;
ASSERT ( 0 ) ;
return ;
}
2020-05-11 02:55:57 +00:00
# else
2020-10-04 19:39:54 +00:00
debug ( " Using separate streaming threads for each channel \n " ) ;
2020-05-11 02:55:57 +00:00
# endif
}
void
CdStreamInit ( int32 numChannels )
{
2020-10-04 19:39:54 +00:00
struct statvfs fsInfo ;
if ( ( statvfs ( " models/gta3.img " , & fsInfo ) ) < 0 )
{
CDTRACE ( " can't get filesystem info " ) ;
ASSERT ( 0 ) ;
return ;
}
2020-08-05 12:32:37 +00:00
# ifdef __linux__
2020-05-11 02:55:57 +00:00
_gdwCdStreamFlags = O_RDONLY | O_NOATIME ;
2020-08-07 15:51:15 +00:00
# else
2020-08-05 12:32:37 +00:00
_gdwCdStreamFlags = O_RDONLY ;
# endif
2020-05-11 02:55:57 +00:00
// People say it's slower
/*
if ( fsInfo . f_bsize < = CDSTREAM_SECTOR_SIZE )
{
_gdwCdStreamFlags | = O_DIRECT ;
debug ( " Using no buffered loading for streaming \n " ) ;
}
*/
2020-10-02 00:45:38 +00:00
void * pBuffer = ( void * ) RwMallocAlign ( CDSTREAM_SECTOR_SIZE , ( RwUInt32 ) fsInfo . f_bsize ) ;
2020-05-11 02:55:57 +00:00
ASSERT ( pBuffer ! = nil ) ;
gNumImages = 0 ;
gNumChannels = numChannels ;
2020-05-13 15:48:07 +00:00
gpReadInfo = ( CdReadInfo * ) calloc ( numChannels , sizeof ( CdReadInfo ) ) ;
2020-05-11 02:55:57 +00:00
ASSERT ( gpReadInfo ! = nil ) ;
CDDEBUG ( " read info %p " , gpReadInfo ) ;
CdStreamInitThread ( ) ;
ASSERT ( pBuffer ! = nil ) ;
RwFreeAlign ( pBuffer ) ;
}
uint32
GetGTA3ImgSize ( void )
{
ASSERT ( gImgFiles [ 0 ] > 0 ) ;
2020-10-04 19:39:54 +00:00
struct stat statbuf ;
2020-05-11 02:55:57 +00:00
2020-10-04 19:39:54 +00:00
char path [ PATH_MAX ] ;
realpath ( gImgNames [ 0 ] , path ) ;
if ( stat ( path , & statbuf ) = = - 1 ) {
2020-05-11 02:55:57 +00:00
// Try case-insensitivity
2020-07-26 17:59:58 +00:00
char * real = casepath ( gImgNames [ 0 ] , false ) ;
if ( real )
2020-05-11 02:55:57 +00:00
{
2020-07-26 17:59:58 +00:00
realpath ( real , path ) ;
free ( real ) ;
2020-10-04 19:39:54 +00:00
if ( stat ( path , & statbuf ) ! = - 1 )
2020-05-11 02:55:57 +00:00
goto ok ;
}
2020-10-04 19:39:54 +00:00
CDTRACE ( " can't get size of gta3.img " ) ;
ASSERT ( 0 ) ;
return 0 ;
}
ok :
2020-10-02 00:45:38 +00:00
return ( uint32 ) statbuf . st_size ;
2020-05-11 02:55:57 +00:00
}
void
CdStreamShutdown ( void )
{
// Destroying semaphores and free(gpReadInfo) will be done at threads
# ifndef ONE_THREAD_PER_CHANNEL
2020-10-04 19:39:54 +00:00
gCdStreamThreadStatus = 2 ;
sem_post ( gCdStreamSema ) ;
# else
for ( int32 i = 0 ; i < gNumChannels ; i + + ) {
gpReadInfo [ i ] . nThreadStatus = 2 ;
sem_post ( gpReadInfo [ i ] . pStartSemaphore ) ;
}
2020-05-11 02:55:57 +00:00
# endif
}
int32
CdStreamRead ( int32 channel , void * buffer , uint32 offset , uint32 size )
{
ASSERT ( channel < gNumChannels ) ;
ASSERT ( buffer ! = nil ) ;
lastPosnRead = size + offset ;
ASSERT ( _GET_INDEX ( offset ) < MAX_CDIMAGES ) ;
int32 hImage = gImgFiles [ _GET_INDEX ( offset ) ] ;
ASSERT ( hImage > 0 ) ;
CdReadInfo * pChannel = & gpReadInfo [ channel ] ;
ASSERT ( pChannel ! = nil ) ;
2020-10-04 19:39:54 +00:00
if ( pChannel - > nSectorsToRead ! = 0 | | pChannel - > bReading ) {
2020-10-08 23:19:49 +00:00
if ( pChannel - > nSectorOffset = = _GET_OFFSET ( offset ) & & pChannel - > nSectorsToRead > = size )
return STREAM_SUCCESS ;
2020-10-04 19:39:54 +00:00
flushStream [ channel ] = 1 ;
CdStreamSync ( channel ) ;
//return STREAM_NONE;
}
2020-05-11 02:55:57 +00:00
2020-10-08 23:19:49 +00:00
pChannel - > hFile = hImage - 1 ;
2020-10-04 19:39:54 +00:00
pChannel - > nStatus = STREAM_NONE ;
pChannel - > nSectorOffset = _GET_OFFSET ( offset ) ;
pChannel - > nSectorsToRead = size ;
pChannel - > pBuffer = buffer ;
pChannel - > bLocked = 0 ;
2020-05-11 02:55:57 +00:00
# ifndef ONE_THREAD_PER_CHANNEL
2020-10-04 19:39:54 +00:00
AddToQueue ( & gChannelRequestQ , channel ) ;
if ( sem_post ( gCdStreamSema ) ! = 0 )
printf ( " Signal Sema Error \n " ) ;
2020-05-11 02:55:57 +00:00
# else
2020-10-04 19:39:54 +00:00
if ( sem_post ( pChannel - > pStartSemaphore ) ! = 0 )
printf ( " Signal Sema Error \n " ) ;
2020-05-11 02:55:57 +00:00
# endif
2020-10-04 19:39:54 +00:00
return STREAM_SUCCESS ;
2020-05-11 02:55:57 +00:00
}
int32
CdStreamGetStatus ( int32 channel )
{
ASSERT ( channel < gNumChannels ) ;
CdReadInfo * pChannel = & gpReadInfo [ channel ] ;
ASSERT ( pChannel ! = nil ) ;
# ifdef ONE_THREAD_PER_CHANNEL
2020-10-04 19:39:54 +00:00
if ( pChannel - > nThreadStatus = = 2 )
return STREAM_NONE ;
2020-05-11 02:55:57 +00:00
# else
2020-10-04 19:39:54 +00:00
if ( gCdStreamThreadStatus = = 2 )
return STREAM_NONE ;
2020-05-11 02:55:57 +00:00
# endif
2020-10-04 19:39:54 +00:00
if ( pChannel - > bReading )
return STREAM_READING ;
2020-05-11 02:55:57 +00:00
2020-10-04 19:39:54 +00:00
if ( pChannel - > nSectorsToRead ! = 0 )
return STREAM_WAITING ;
2020-05-11 02:55:57 +00:00
2020-10-04 19:39:54 +00:00
if ( pChannel - > nStatus ! = STREAM_NONE )
{
int32 status = pChannel - > nStatus ;
2020-05-11 02:55:57 +00:00
2020-10-04 19:39:54 +00:00
pChannel - > nStatus = STREAM_NONE ;
2020-05-11 02:55:57 +00:00
2020-10-04 19:39:54 +00:00
return status ;
}
2020-05-11 02:55:57 +00:00
2020-10-04 19:39:54 +00:00
return STREAM_NONE ;
2020-05-11 02:55:57 +00:00
}
int32
CdStreamGetLastPosn ( void )
{
return lastPosnRead ;
}
// wait for channel to finish reading
int32
CdStreamSync ( int32 channel )
{
ASSERT ( channel < gNumChannels ) ;
CdReadInfo * pChannel = & gpReadInfo [ channel ] ;
ASSERT ( pChannel ! = nil ) ;
if ( flushStream [ channel ] ) {
# ifdef ONE_THREAD_PER_CHANNEL
pChannel - > nSectorsToRead = 0 ;
2020-10-08 23:19:49 +00:00
pthread_kill ( pChannel - > pChannelThread , SIGUSR1 ) ;
2020-10-04 19:39:54 +00:00
if ( pChannel - > bReading ) {
pChannel - > bLocked = true ;
sem_wait ( pChannel - > pDoneSemaphore ) ;
}
2020-05-11 02:55:57 +00:00
# else
2020-10-04 19:39:54 +00:00
pChannel - > nSectorsToRead = 0 ;
2020-05-11 02:55:57 +00:00
if ( pChannel - > bReading ) {
2020-10-04 19:39:54 +00:00
pChannel - > bLocked = true ;
2020-10-08 23:19:49 +00:00
pthread_kill ( _gCdStreamThread , SIGUSR1 ) ;
2020-10-04 19:39:54 +00:00
sem_wait ( pChannel - > pDoneSemaphore ) ;
2020-10-08 23:19:49 +00:00
2020-05-11 02:55:57 +00:00
}
# endif
pChannel - > bReading = false ;
flushStream [ channel ] = false ;
2020-10-04 19:39:54 +00:00
return STREAM_NONE ;
2020-05-11 02:55:57 +00:00
}
2020-10-04 19:39:54 +00:00
if ( pChannel - > nSectorsToRead ! = 0 )
{
pChannel - > bLocked = true ;
2020-05-11 02:55:57 +00:00
2020-10-04 19:39:54 +00:00
sem_wait ( pChannel - > pDoneSemaphore ) ;
}
2020-05-11 02:55:57 +00:00
2020-10-04 19:39:54 +00:00
pChannel - > bReading = false ;
2020-05-11 02:55:57 +00:00
2020-10-04 19:39:54 +00:00
return pChannel - > nStatus ;
2020-05-11 02:55:57 +00:00
}
void
AddToQueue ( Queue * queue , int32 item )
{
ASSERT ( queue ! = nil ) ;
ASSERT ( queue - > items ! = nil ) ;
queue - > items [ queue - > tail ] = item ;
queue - > tail = ( queue - > tail + 1 ) % queue - > size ;
if ( queue - > head = = queue - > tail )
debug ( " Queue is full \n " ) ;
}
int32
GetFirstInQueue ( Queue * queue )
{
ASSERT ( queue ! = nil ) ;
if ( queue - > head = = queue - > tail )
return - 1 ;
ASSERT ( queue - > items ! = nil ) ;
return queue - > items [ queue - > head ] ;
}
void
RemoveFirstInQueue ( Queue * queue )
{
ASSERT ( queue ! = nil ) ;
if ( queue - > head = = queue - > tail )
{
debug ( " Queue is empty \n " ) ;
return ;
}
queue - > head = ( queue - > head + 1 ) % queue - > size ;
}
void * CdStreamThread ( void * param )
{
debug ( " Created cdstream thread \n " ) ;
# ifndef ONE_THREAD_PER_CHANNEL
2020-10-04 19:39:54 +00:00
while ( gCdStreamThreadStatus ! = 2 ) {
2020-10-01 07:21:06 +00:00
sem_wait ( gCdStreamSema ) ;
2020-10-04 19:39:54 +00:00
int32 channel = GetFirstInQueue ( & gChannelRequestQ ) ;
2020-05-11 02:55:57 +00:00
# else
2020-10-04 19:39:54 +00:00
int channel = * ( ( int * ) param ) ;
while ( gpReadInfo [ channel ] . nThreadStatus ! = 2 ) {
sem_wait ( gpReadInfo [ channel ] . pStartSemaphore ) ;
2020-05-11 02:55:57 +00:00
# endif
CdReadInfo * pChannel = & gpReadInfo [ channel ] ;
ASSERT ( pChannel ! = nil ) ;
// spurious wakeup or we sent interrupt signal for flushing
if ( pChannel - > nSectorsToRead = = 0 )
2020-10-04 19:39:54 +00:00
continue ;
2020-05-11 02:55:57 +00:00
pChannel - > bReading = true ;
2020-10-04 19:39:54 +00:00
// Not standard POSIX :shrug:
# ifdef __linux__
# ifdef ONE_THREAD_PER_CHANNEL
if ( gpReadInfo [ channel ] . nThreadStatus = = 0 ) {
gpReadInfo [ channel ] . nThreadStatus = 1 ;
# else
if ( gCdStreamThreadStatus = = 0 ) {
gCdStreamThreadStatus = 1 ;
# endif
pid_t tid = syscall ( SYS_gettid ) ;
int ret = setpriority ( PRIO_PROCESS , tid , getpriority ( PRIO_PROCESS , getpid ( ) ) + 1 ) ;
}
# endif
2020-05-11 02:55:57 +00:00
if ( pChannel - > nStatus = = STREAM_NONE )
{
2020-10-04 19:39:54 +00:00
ASSERT ( pChannel - > hFile > = 0 ) ;
ASSERT ( pChannel - > pBuffer ! = nil ) ;
2020-05-11 02:55:57 +00:00
lseek ( pChannel - > hFile , pChannel - > nSectorOffset * CDSTREAM_SECTOR_SIZE , SEEK_SET ) ;
2020-10-04 19:39:54 +00:00
if ( read ( pChannel - > hFile , pChannel - > pBuffer , pChannel - > nSectorsToRead * CDSTREAM_SECTOR_SIZE ) = = - 1 ) {
2020-05-11 02:55:57 +00:00
// pChannel->nSectorsToRead == 0 at this point means we wanted to flush channel
2020-10-04 19:39:54 +00:00
// STREAM_WAITING is a little hack to make CStreaming not process this data
pChannel - > nStatus = pChannel - > nSectorsToRead = = 0 ? STREAM_WAITING : STREAM_ERROR ;
} else {
pChannel - > nStatus = STREAM_NONE ;
}
2020-05-11 02:55:57 +00:00
}
2020-10-04 19:39:54 +00:00
2020-05-11 02:55:57 +00:00
# ifndef ONE_THREAD_PER_CHANNEL
RemoveFirstInQueue ( & gChannelRequestQ ) ;
# endif
pChannel - > nSectorsToRead = 0 ;
if ( pChannel - > bLocked )
{
2020-10-04 19:39:54 +00:00
sem_post ( pChannel - > pDoneSemaphore ) ;
2020-05-11 02:55:57 +00:00
}
pChannel - > bReading = false ;
}
2020-10-04 19:39:54 +00:00
char semName [ 20 ] ;
2020-05-11 02:55:57 +00:00
# ifndef ONE_THREAD_PER_CHANNEL
2020-10-04 19:39:54 +00:00
for ( int32 i = 0 ; i < gNumChannels ; i + + )
{
sem_close ( gpReadInfo [ i ] . pDoneSemaphore ) ;
sprintf ( semName , " /semaphore_done%d " , i ) ;
sem_unlink ( semName ) ;
}
sem_close ( gCdStreamSema ) ;
sem_unlink ( " /semaphore_cd_stream " ) ;
free ( gChannelRequestQ . items ) ;
2020-05-11 02:55:57 +00:00
# else
2020-10-04 19:39:54 +00:00
sem_close ( gpReadInfo [ channel ] . pStartSemaphore ) ;
sprintf ( semName , " /semaphore_start%d " , channel ) ;
sem_unlink ( semName ) ;
sem_close ( gpReadInfo [ channel ] . pDoneSemaphore ) ;
sprintf ( semName , " /semaphore_done%d " , channel ) ;
sem_unlink ( semName ) ;
2020-05-11 02:55:57 +00:00
# endif
2020-10-04 19:39:54 +00:00
if ( gpReadInfo )
free ( gpReadInfo ) ;
gpReadInfo = nil ;
2020-05-11 02:55:57 +00:00
pthread_exit ( nil ) ;
}
bool
CdStreamAddImage ( char const * path )
{
ASSERT ( path ! = nil ) ;
ASSERT ( gNumImages < MAX_CDIMAGES ) ;
gImgFiles [ gNumImages ] = open ( path , _gdwCdStreamFlags ) ;
// Fix case sensitivity and backslashes.
if ( gImgFiles [ gNumImages ] = = - 1 ) {
2020-07-26 17:59:58 +00:00
char * real = casepath ( path , false ) ;
if ( real )
2020-05-11 02:55:57 +00:00
{
2020-10-04 19:39:54 +00:00
gImgFiles [ gNumImages ] = open ( real , _gdwCdStreamFlags ) ;
2020-07-26 17:59:58 +00:00
free ( real ) ;
2020-05-11 02:55:57 +00:00
}
}
if ( gImgFiles [ gNumImages ] = = - 1 ) {
assert ( false ) ;
return false ;
}
gImgNames [ gNumImages ] = strdup ( path ) ;
gImgFiles [ gNumImages ] + + ; // because -1: error 0: not used
strcpy ( gCdImageNames [ gNumImages ] , path ) ;
gNumImages + + ;
return true ;
}
char *
CdStreamGetImageName ( int32 cd )
{
ASSERT ( cd < MAX_CDIMAGES ) ;
if ( gImgFiles [ cd ] > 0 )
return gCdImageNames [ cd ] ;
return nil ;
}
void
CdStreamRemoveImages ( void )
{
2020-10-04 19:39:54 +00:00
for ( int32 i = 0 ; i < gNumChannels ; i + + ) {
flushStream [ i ] = 1 ;
2020-05-11 02:55:57 +00:00
CdStreamSync ( i ) ;
2020-10-04 19:39:54 +00:00
}
2020-05-11 02:55:57 +00:00
for ( int32 i = 0 ; i < gNumImages ; i + + )
{
close ( gImgFiles [ i ] - 1 ) ;
free ( gImgNames [ i ] ) ;
gImgFiles [ i ] = 0 ;
}
gNumImages = 0 ;
}
int32
CdStreamGetNumImages ( void )
{
return gNumImages ;
}
# endif