781 lines
13 KiB
C
781 lines
13 KiB
C
/*
|
|
* small libc functions for the PSXSDK
|
|
*
|
|
* In this file, C functions either not implemented by the BIOS
|
|
* or very broken in the BIOS are implemented independently
|
|
*
|
|
* Some functions, such as printf, have both a BIOS implementation
|
|
* and an implementation here. The implementation here is prefixed
|
|
* with libc_ to not make confusion.
|
|
*/
|
|
|
|
#include <psx.h>
|
|
#include <stdio.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <strings.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
|
|
int errno;
|
|
int __stdio_direction = STDIO_DIRECTION_BIOS;
|
|
static unsigned int __sio_cr_mapped = 0;
|
|
|
|
#define NUM_OF_FILE_STRUCTS 6
|
|
|
|
enum {SECTOR_SZ = 2048};
|
|
|
|
static FILE file_structs[NUM_OF_FILE_STRUCTS] =
|
|
{
|
|
[0] = // stdin
|
|
{
|
|
.fildes = 0,
|
|
.pos = 0,
|
|
.mode = O_RDONLY,
|
|
.dev = FDEV_CONSOLE,
|
|
.size = 0,
|
|
.used = 1,
|
|
.eof = 0,
|
|
.error = 0
|
|
},
|
|
[1] = // stdout
|
|
{
|
|
.fildes = 1,
|
|
.pos = 0,
|
|
.mode = O_WRONLY,
|
|
.dev = FDEV_CONSOLE,
|
|
.size = 0,
|
|
.used = 1,
|
|
.eof = 0,
|
|
.error = 0
|
|
},
|
|
[2] = // stderr
|
|
{
|
|
.fildes = 2,
|
|
.pos = 0,
|
|
.mode = O_WRONLY,
|
|
.dev = FDEV_CONSOLE,
|
|
.size = 0,
|
|
.used = 1,
|
|
.eof = 0,
|
|
.error = 0
|
|
},
|
|
|
|
[3 ... NUM_OF_FILE_STRUCTS - 1] =
|
|
{
|
|
.buf = (unsigned char[SECTOR_SZ]){}
|
|
}
|
|
};
|
|
|
|
FILE *const stdin = &file_structs[0];
|
|
FILE *const stdout = &file_structs[1];
|
|
FILE *const stderr = &file_structs[2];
|
|
|
|
#define IS_CONS_IN(f) (f->fildes == 0)
|
|
#define IS_CONS_OUT(f) (f->fildes == 1 || f->fildes == 2)
|
|
|
|
//unsigned char file_state[NUM_OF_FILE_STRUCTS];
|
|
|
|
int libc_get_transtbl_fname(const char *tofind, char *outstr, int outl);
|
|
|
|
unsigned int fmode_to_desmode(const char *fmode)
|
|
{
|
|
char rmode[16];
|
|
int x, y;
|
|
|
|
y = 0;
|
|
|
|
for(x=0;x<15;x++)
|
|
{
|
|
if(fmode[x] == 0)
|
|
break;
|
|
else
|
|
{
|
|
if(fmode[x] != 'b' && fmode[x] != 'f')
|
|
rmode[y++] = fmode[x];
|
|
}
|
|
}
|
|
|
|
rmode[y] = 0;
|
|
|
|
if(strcmp(rmode, "r") == 0)
|
|
{
|
|
return O_RDONLY;
|
|
}
|
|
else if(strcmp(rmode, "r+") == 0)
|
|
{
|
|
return O_RDWR;
|
|
}
|
|
else if(strcmp(rmode, "w") == 0)
|
|
{
|
|
return O_WRONLY | O_CREAT | O_TRUNC;
|
|
}
|
|
else if(strcmp(rmode, "w+") == 0)
|
|
{
|
|
return O_RDWR | O_CREAT | O_TRUNC;
|
|
}
|
|
else if(strcmp(rmode, "a") == 0)
|
|
{
|
|
dprintf("Append; open for writing. Create file if it doesn't exist.\n");
|
|
return O_WRONLY | O_APPEND;
|
|
}
|
|
else if(strcmp(rmode, "a+") == 0)
|
|
{
|
|
dprintf("Append; open for reading and writing. Create file if it doesn't exist.\n");
|
|
return O_RDWR | O_APPEND | O_CREAT;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
FILE *fdopen(int fildes, const char *mode)
|
|
{
|
|
// Adjust for malloc
|
|
int x;
|
|
|
|
// Find a free file structure
|
|
for(x = 0; x < NUM_OF_FILE_STRUCTS; x++)
|
|
{
|
|
FILE *const f = &file_structs[x];
|
|
|
|
if(f->used == 0)
|
|
{
|
|
f->size = 0;
|
|
f->eof = 0;
|
|
f->error = 0;
|
|
f->used = 1;
|
|
f->last_sect = 0;
|
|
f->cache_available = 0;
|
|
|
|
if (f->buf)
|
|
memset(f->buf, 0, SECTOR_SZ);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we found no free file structure, return NULL pointer
|
|
|
|
if(x == NUM_OF_FILE_STRUCTS)
|
|
return NULL;
|
|
|
|
file_structs[x].fildes = fildes;
|
|
file_structs[x].pos = lseek(fildes, 0, SEEK_CUR);
|
|
file_structs[x].mode = fmode_to_desmode(mode);
|
|
|
|
return &file_structs[x];
|
|
}
|
|
|
|
static FILE *fopen_internal(const char *path, const char *mode, FILE *f)
|
|
{
|
|
int fd;
|
|
char *s = NULL;
|
|
|
|
if(strncmp(path, "cdromL:", 7) == 0)
|
|
{
|
|
#ifdef PSXSDK_CDROML_ENABLED
|
|
s = malloc(1024);
|
|
|
|
if(libc_get_transtbl_fname(path+7, s, 1024) == 0)
|
|
return NULL;
|
|
|
|
fd = open(s, fmode_to_desmode(mode));
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
else
|
|
fd = open(path, fmode_to_desmode(mode));
|
|
|
|
if(fd == -1)
|
|
{
|
|
errno = ENOENT;
|
|
if(s!=NULL)free(s);
|
|
return NULL;
|
|
}
|
|
|
|
if(f == NULL)
|
|
f = fdopen(fd, mode);
|
|
else
|
|
{
|
|
f->fildes = fd;
|
|
f->pos = lseek(fd, 0, SEEK_CUR);
|
|
f->mode = fmode_to_desmode(mode);
|
|
}
|
|
|
|
if(f == NULL)
|
|
{
|
|
if(s!=NULL)free(s);
|
|
return NULL;
|
|
}
|
|
|
|
f->dev = FDEV_UNKNOWN;
|
|
|
|
if(strncmp(path, "cdrom", 5) == 0 || strncmp(path, "cdromL", 6) == 0)
|
|
f->dev = FDEV_CDROM;
|
|
else if(strncmp(path, "bu", 2) == 0)
|
|
f->dev = FDEV_MEMCARD;
|
|
|
|
// nocash bios freezes at get_real_file_size(), due
|
|
// to problems with firstfile()
|
|
|
|
if(s!=NULL)
|
|
{
|
|
f->size = get_real_file_size(s);
|
|
free(s);
|
|
}
|
|
else
|
|
f->size = get_real_file_size(path);
|
|
|
|
return f;
|
|
}
|
|
|
|
FILE *fopen(const char *path, const char *mode)
|
|
{
|
|
return fopen_internal(path, mode, NULL);
|
|
}
|
|
|
|
int fclose(FILE *stream)
|
|
{
|
|
stream->used = 0;
|
|
close(stream->fildes);
|
|
return 0;
|
|
}
|
|
|
|
static size_t cdromread(void *restrict ptr, size_t sz,
|
|
FILE *restrict const f)
|
|
{
|
|
const size_t start_sect = f->pos >> __builtin_ctz(SECTOR_SZ);
|
|
const size_t end_pos = f->pos + sz;
|
|
const size_t end_sector = end_pos >> __builtin_ctz(SECTOR_SZ);
|
|
size_t readb = 0;
|
|
int read_complete_sector = 0;
|
|
|
|
if (feof(f) || ferror(f))
|
|
return 0;
|
|
else if (f->cache_available)
|
|
{
|
|
const size_t first_sector_sz = f->pos % SECTOR_SZ;
|
|
|
|
if (f->last_sect == start_sect && f->last_sect == end_sector)
|
|
{
|
|
/* Use buffered I/O. */
|
|
size_t max;
|
|
|
|
if (end_pos >= f->size)
|
|
{
|
|
max = f->size - f->pos;
|
|
f->eof = 1;
|
|
}
|
|
else
|
|
{
|
|
max = sz;
|
|
}
|
|
|
|
memcpy(ptr, &f->buf[first_sector_sz], max);
|
|
f->pos += max;
|
|
readb = max;
|
|
}
|
|
else if (f->last_sect == start_sect)
|
|
{
|
|
/* Partial buffered I/O possible. */
|
|
const size_t firstsz = SECTOR_SZ - first_sector_sz;
|
|
|
|
memcpy(ptr, &f->buf[first_sector_sz], firstsz);
|
|
ptr = (char *)ptr + firstsz;
|
|
f->pos += firstsz;
|
|
f->cache_available = 0;
|
|
readb += firstsz;
|
|
read_complete_sector = 1;
|
|
}
|
|
}
|
|
|
|
const size_t rem = sz - readb;
|
|
|
|
if (rem)
|
|
{
|
|
const size_t whole_sectors = rem / SECTOR_SZ;
|
|
|
|
if (whole_sectors)
|
|
{
|
|
if (!read_complete_sector)
|
|
lseek(f->fildes, f->pos & ~(SECTOR_SZ - 1), SEEK_SET);
|
|
|
|
const size_t s = whole_sectors * SECTOR_SZ;
|
|
const int n = read(f->fildes, &((unsigned char *)ptr)[readb], s);
|
|
|
|
if (n >= 0)
|
|
{
|
|
readb += s;
|
|
f->pos += s;
|
|
ptr = (unsigned char *)ptr + s;
|
|
|
|
if (f->pos >= f->size)
|
|
f->eof = 1;
|
|
|
|
f->cache_available = 1;
|
|
}
|
|
}
|
|
|
|
if (f->pos < f->size)
|
|
{
|
|
int n;
|
|
|
|
if (!read_complete_sector)
|
|
lseek(f->fildes, f->pos & ~(SECTOR_SZ - 1), SEEK_SET);
|
|
|
|
n = read(f->fildes, f->buf, SECTOR_SZ);
|
|
|
|
if (n >= 0)
|
|
{
|
|
readb = n > rem ? rem : n;
|
|
|
|
memmove(ptr, f->buf, readb);
|
|
ptr = (unsigned char *)ptr + readb;
|
|
f->pos += readb;
|
|
|
|
if (f->pos >= f->size)
|
|
f->eof = 1;
|
|
|
|
f->cache_available = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
f->last_sect = end_sector;
|
|
return f->pos;
|
|
}
|
|
|
|
/*
|
|
* fread doesn't require reads to be carried in block unit
|
|
* Notice that however seeks on the CD drive will be very slow - so avoid using non block units
|
|
*
|
|
* This is done to make programming and porting easier
|
|
*/
|
|
|
|
size_t fread(void *restrict const ptr, const size_t size, const size_t nmemb,
|
|
FILE *restrict const f)
|
|
{
|
|
if (size && nmemb)
|
|
{
|
|
size_t rsize = size * nmemb;
|
|
|
|
switch (f->dev)
|
|
{
|
|
case FDEV_CDROM:
|
|
rsize = cdromread(ptr, rsize, f);
|
|
break;
|
|
|
|
case FDEV_CONSOLE:
|
|
/* Fall through. */
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return rsize / size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fgetc(FILE *f)
|
|
{
|
|
unsigned char c;
|
|
|
|
if(f->pos >= f->size)
|
|
return EOF;
|
|
|
|
fread(&c, sizeof(char), 1, f);
|
|
|
|
return (int)c;
|
|
}
|
|
|
|
int ftell(FILE *f)
|
|
{
|
|
return f->pos;
|
|
}
|
|
|
|
int fseek(FILE *f, int offset, int whence)
|
|
{
|
|
switch(whence)
|
|
{
|
|
case SEEK_SET:
|
|
f->pos = offset;
|
|
break;
|
|
case SEEK_CUR:
|
|
f->pos+= offset;
|
|
break;
|
|
case SEEK_END:
|
|
f->pos = f->size + offset;
|
|
break;
|
|
default:
|
|
f->pos = whence + offset;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void rewind(FILE *f)
|
|
{
|
|
fseek(f, 0, SEEK_SET);
|
|
clearerr(f);
|
|
}
|
|
|
|
int toupper(int c)
|
|
{
|
|
if(c >= 'a' && c <= 'z')
|
|
return (c-'a')+'A';
|
|
|
|
return c;
|
|
}
|
|
|
|
int tolower(int c)
|
|
{
|
|
if(c >= 'A' && c <= 'Z')
|
|
return (c-'A')+'a';
|
|
|
|
return c;
|
|
}
|
|
|
|
int libc_get_transtbl_fname(const char *tofind, char *outstr, int outl)
|
|
{
|
|
FILE *f;
|
|
int s;
|
|
int x;
|
|
int type = 0;
|
|
int y;
|
|
int l = strlen(tofind);
|
|
int filename_found = 0;
|
|
int exit_loop = 0;
|
|
int otfp = 0;
|
|
int tfp = 0;
|
|
char transtbl[0x4000];
|
|
char orgname[16];
|
|
char newname[256];
|
|
char rootpath[256];
|
|
|
|
bzero(transtbl, 0x4000);
|
|
strcpy(rootpath, "cdrom:\\");
|
|
|
|
f = fopen("cdrom:\\TRANS.TBL;1", "rb");
|
|
|
|
if(f == NULL)
|
|
return 0;
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
s = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
fread(transtbl, 1, s, f);
|
|
fclose(f);
|
|
|
|
outstr[0] = 0;
|
|
|
|
x = 0;
|
|
|
|
exit_loop = 0;
|
|
filename_found = 0;
|
|
|
|
for(tfp = 0; tofind[tfp] == '\\' || tofind[tfp] == '/'; tfp++);
|
|
|
|
otfp = tfp;
|
|
|
|
for(y = otfp; y < l; y++)
|
|
{
|
|
if(tofind[y] == '\0' || tofind[y] == '\\' || tofind[y] == '/')
|
|
break;
|
|
}
|
|
|
|
tfp = y;
|
|
|
|
while((x < s) && !exit_loop)
|
|
{
|
|
while(transtbl[x] == ' ' || transtbl[x] == '\t' || transtbl[x] == '\n' || transtbl[x] == '\r')
|
|
x++;
|
|
|
|
if(transtbl[x] == 'F')
|
|
type = 0;
|
|
else if(transtbl[x] == 'D')
|
|
type = 1;
|
|
|
|
x++;
|
|
|
|
while(transtbl[x] == ' ' || transtbl[x] == '\t' || transtbl[x] == '\n' || transtbl[x] == '\r')
|
|
x++;
|
|
|
|
y = 0;
|
|
|
|
while(!(transtbl[x] == ' ' || transtbl[x] == '\t' || transtbl[x] == '\n' || transtbl[x] == '\r'
|
|
|| transtbl[x] == 0))
|
|
orgname[y++] = transtbl[x++];
|
|
|
|
orgname[y] = 0;
|
|
//printf("orgname = %s\n", orgname);
|
|
|
|
while(transtbl[x] == ' ' || transtbl[x] == '\t' || transtbl[x] == '\n' || transtbl[x] == '\r')
|
|
x++;
|
|
|
|
y = 0;
|
|
|
|
while(!(transtbl[x] == '\n' || transtbl[x] == '\r' || transtbl[x] == 0))
|
|
newname[y++] = transtbl[x++];
|
|
|
|
newname[y] = 0;
|
|
|
|
while(transtbl[x] == ' ' || transtbl[x] == '\t' || transtbl[x] == '\n' || transtbl[x] == '\r')
|
|
x++;
|
|
|
|
//printf("newname = %s\n", newname);
|
|
|
|
if(strncasecmp(&tofind[otfp], newname, tfp-otfp) == 0)
|
|
{
|
|
if(type == 0)
|
|
{
|
|
dprintf("Filename found: %s -> %s%s\n", tofind, rootpath, orgname);
|
|
filename_found = 1;
|
|
exit_loop = 1;
|
|
|
|
strncpy(outstr, rootpath, outl);
|
|
strncat(outstr, orgname, outl-strlen(rootpath));
|
|
}
|
|
else
|
|
{
|
|
//printf("Found directory! %s\n", newname);
|
|
|
|
//printf("tfp = %d\n", tfp);
|
|
|
|
if(tfp == l || tofind[l-1] == '/'
|
|
|| tofind[l-1] == '\\')
|
|
{
|
|
dprintf("File not found. A directory was specified.\n");
|
|
exit_loop = 1;
|
|
continue;
|
|
}
|
|
|
|
//tfp++;
|
|
for(; tofind[tfp] == '\\' || tofind[tfp] == '/'; tfp++);
|
|
|
|
otfp = tfp;
|
|
|
|
for(y = otfp; y < l; y++)
|
|
{
|
|
if(tofind[y] == '\0' || tofind[y] == '\\' || tofind[y] == '/')
|
|
break;
|
|
}
|
|
|
|
tfp = y;
|
|
|
|
strcat(rootpath, orgname);
|
|
strcat(rootpath, "\\");
|
|
|
|
y = strlen(rootpath);
|
|
strcat(rootpath, "TRANS.TBL;1");
|
|
|
|
bzero(transtbl, 0x4000);
|
|
|
|
f = fopen(rootpath, "rb");
|
|
|
|
if(f == NULL)
|
|
{
|
|
dprintf("Couldn't find %s\n", rootpath);
|
|
exit_loop = 1;
|
|
continue;
|
|
}
|
|
|
|
rootpath[y] = 0;
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
s = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
fread(transtbl, 1, s, f);
|
|
fclose(f);
|
|
|
|
x = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return filename_found;
|
|
}
|
|
|
|
int isupper(int c)
|
|
{
|
|
return (c >= 'A' && c <= 'Z');
|
|
}
|
|
|
|
int islower(int c)
|
|
{
|
|
return (c >= 'a' && c <= 'z');
|
|
}
|
|
|
|
int isdigit(int c)
|
|
{
|
|
return (c >= '0' && c <= '9');
|
|
}
|
|
|
|
int isxdigit(int c)
|
|
{
|
|
return ((c >= '0' && c <= '9') || (c >= 'A' && c<='F') || (c >= 'a' && c<='f'));
|
|
}
|
|
|
|
int isalpha(int c)
|
|
{
|
|
return ((c>='a' && c<='z') || (c>='A' && c<='Z'));
|
|
}
|
|
|
|
int isalnum(int c)
|
|
{
|
|
return ((c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9'));
|
|
}
|
|
|
|
int isspace(int c)
|
|
{
|
|
return ((c == ' ') || (c == '\f') || (c == '\n') || (c == '\r') || (c == '\t') || (c == '\v'));
|
|
}
|
|
|
|
int isprint(int c)
|
|
{
|
|
return (c >= 0x20) && (c <= 0x7E);
|
|
}
|
|
|
|
int isgraph(int c)
|
|
{
|
|
return (c > 0x20) && (c <= 0x7E);
|
|
}
|
|
|
|
int iscntrl(int c)
|
|
{
|
|
return (c < 0x20);
|
|
}
|
|
|
|
int isblank(int c)
|
|
{
|
|
return ((c == ' ') || (c == '\t'));
|
|
}
|
|
|
|
void redirect_stdio_to_sio(void)
|
|
{
|
|
__stdio_direction = STDIO_DIRECTION_SIO;
|
|
}
|
|
|
|
void sio_stdio_mapcr(unsigned int setting)
|
|
{
|
|
__sio_cr_mapped = setting;
|
|
}
|
|
|
|
int sio_putchar(int c)
|
|
{
|
|
if(c == '\n' && __sio_cr_mapped)
|
|
sio_putchar('\r');
|
|
|
|
while(!SIOCheckOutBuffer());
|
|
|
|
SIOSendByte(c);
|
|
|
|
return c;
|
|
}
|
|
|
|
int sio_puts(const char *str)
|
|
{
|
|
while(*str)
|
|
sio_putchar(*(str++));
|
|
|
|
sio_putchar('\n');
|
|
|
|
return 1;
|
|
}
|
|
|
|
extern int bios_putchar(int c);
|
|
extern int bios_puts(const char *str);
|
|
|
|
int putchar(int c)
|
|
{
|
|
switch(__stdio_direction)
|
|
{
|
|
case STDIO_DIRECTION_BIOS:
|
|
return bios_putchar(c);
|
|
break;
|
|
case STDIO_DIRECTION_SIO:
|
|
return sio_putchar(c);
|
|
break;
|
|
}
|
|
|
|
return EOF;
|
|
}
|
|
|
|
int puts(const char *str)
|
|
{
|
|
switch(__stdio_direction)
|
|
{
|
|
case STDIO_DIRECTION_BIOS:
|
|
return bios_puts(str);
|
|
break;
|
|
case STDIO_DIRECTION_SIO:
|
|
return sio_puts(str);
|
|
break;
|
|
}
|
|
|
|
return EOF;
|
|
}
|
|
|
|
size_t fwrite(const void *restrict const ptr, const size_t size, const size_t nmemb,
|
|
FILE *restrict const f)
|
|
{
|
|
if(IS_CONS_OUT(f)) // stdout or stderr
|
|
{
|
|
const char *c = ptr;
|
|
int i;
|
|
|
|
for(i = 0; i < size; i++)
|
|
putchar(c[i]);
|
|
|
|
return size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fputs(const char *str, FILE *stream)
|
|
{
|
|
if(IS_CONS_OUT(stream))
|
|
return puts(str);
|
|
|
|
return EOF;
|
|
}
|
|
|
|
FILE *freopen(const char *path, const char *mode, FILE *stream)
|
|
{
|
|
if(stream == NULL)
|
|
return NULL;
|
|
|
|
if(stream->used)
|
|
fclose(stream);
|
|
|
|
return fopen_internal(path, mode, stream);
|
|
}
|
|
|
|
void clearerr(FILE *stream)
|
|
{
|
|
stream->eof = 0;
|
|
stream->error = 0;
|
|
}
|
|
|
|
int feof(FILE *stream)
|
|
{
|
|
return stream->eof;
|
|
}
|
|
|
|
int ferror(FILE *stream)
|
|
{
|
|
return stream->error;
|
|
}
|
|
|
|
int fileno(FILE *stream)
|
|
{
|
|
return stream->fildes;
|
|
}
|