/* * UPSE: the unix playstation sound emulator. * * Filename: upse_loader_psf2.c * Purpose: libupse: PSF2/MiniPSF2 loader. * * Copyright (c) 2008 William Pitcock * * UPSE is free software, released under the GNU General Public License, * version 2. * * A copy of the GNU General Public License, version 2, is included in * the UPSE source kit as COPYING. * * UPSE is offered without any warranty of any kind, explicit or implicit. */ #include "upse-internal.h" /* kang'd from binutils... *sigh* */ #define ELF32_R_SYM(val) ((val) >> 8) #define ELF32_R_TYPE(val) ((val) & 0xff) static u32 initialPC, initialSP; static u32 loadAddr; static char *_upse_resolve_path(const char *f, const char *newfile) { static char *ret; char *tp1; #if PSS_STYLE==1 tp1 = ((char *) strrchr(f, '/')); #else tp1 = ((char *) strrchr(f, '\\')); #if PSS_STYLE!=3 { char *tp3; tp3 = ((char *) strrchr(f, '/')); if (tp1 < tp3) tp1 = tp3; } #endif #endif if (!tp1) { ret = malloc(strlen(newfile) + 1); strcpy(ret, newfile); } else { ret = malloc(tp1 - f + 2 + strlen(newfile)); // 1(NULL), 1(/). memcpy(ret, f, tp1 - f); ret[tp1 - f] = '/'; ret[tp1 - f + 1] = 0; strcat(ret, newfile); } return (ret); } static u32 upse_psf2_parse_filesystem(upse_filesystem_t *ret, char *curdir, u8 *filesys, u8 *start, u32 len) { int numfiles, i, j; u8 *cptr; u32 offs, uncomp, bsize, cofs, uofs; u32 X; uLongf dlength; int uerr; u32 tlen; char tfn[4096]; u32 buflen = (16 * 1024 * 1024); u8 *buf = calloc(sizeof(u8), buflen); tlen = len; cptr = start + 4; numfiles = start[0] | start[1]<<8 | start[2]<<16 | start[3]<<24; _DEBUG("beginning parsing of filesystem @%p", filesys); for (i = 0; i < numfiles; i++) { offs = cptr[36] | cptr[37]<<8 | cptr[38]<<16 | cptr[39]<<24; uncomp = cptr[40] | cptr[41]<<8 | cptr[42]<<16 | cptr[43]<<24; bsize = cptr[44] | cptr[45]<<8 | cptr[46]<<16 | cptr[47]<<24; if (bsize > 0) { X = (uncomp + bsize - 1) / bsize; _DEBUG("file %s [filesystem @%p], %d block(s) (size %d, uncompressed %d)", cptr, filesys, X, bsize, uncomp); cofs = (offs + (X*4)); uofs = 0; for (j = 0; j < X; j++) { u32 usize = filesys[offs+(j*4)] | filesys[offs+1+(j*4)]<<8 | filesys[offs+2+(j*4)]<<16 | filesys[offs+3+(j*4)]<<24; dlength = buflen - uofs; _DEBUG("uncompressing block %d of %d (dlength %d, start %d, size %d)", j + 1, X, dlength, cofs, usize); uerr = uncompress(&buf[uofs], &dlength, &filesys[cofs], usize); if (uerr != Z_OK) { _WARN("uncompress failed, uerr:%d, buf:%s", uerr, &buf[uofs]); return 0; } cofs += usize; uofs += dlength; } #ifndef WIN32_MSC snprintf(tfn, 4096, "%s/%s", curdir, cptr); #else _snprintf_s(tfn, 4095, 4095, "%s/%s", curdir, cptr); #endif upse_filesystem_attach_path(ret, tfn, buf, uncomp); } else { char lcurdir[4096]; _DEBUG("new subdirectory [filesystem @%p]: %s %d %d %d", filesys, cptr, offs, uncomp, bsize); strncpy(lcurdir, curdir, 4096); strncat(lcurdir, "/", 4096); strncat(lcurdir, (char *) cptr, 4096); _DEBUG("parsing directory %s", lcurdir); upse_psf2_parse_filesystem(ret, lcurdir, filesys, &filesys[offs], len); } cptr += 48; } return (len - (cptr - filesys)); } /* this is sure going to be fun... * parse a PS2 elf image and return the initial PC addr. */ u32 upse_parse_psf2_elf(upse_module_instance_t *ins, u8 *start, u32 len) { u32 entry, phoff, shoff, phentsize, shentsize, phnum, shnum, shstrndx; u32 name, type, flags, addr, offset, size, shent; u32 totallen; int i, rec; /* * Make sure the load address is valid. The rest of this will be messed * up otherwise... and bad rips can cause bad load addresses... */ if (loadAddr & 3) { loadAddr &= ~3; loadAddr += 4; } if (memcmp("ELF", start + 1, 3)) { _DEBUG("Not an ELF file!"); return 0xffffffff; } /* * Ah, ELF. One of USL's most vile inventions, later enhanced by SCO and * Sun has been holding UNIX back for years. And then SONY goes and uses * it on the Playstation 2 and Playstation 3. Boy, what were they thinking? * * ELF is the inspircd of binary formats. It's everything COFF isn't. * Bloated, complex and poorly documented with lots of undocumented side * effects. Oh, and guess what! SONY added more nonsense ontop like the * .iopmod section which is some sort of codesigning verification that we * don't actually care about (and hey, neither does the console.) * * Leave it to SONY to move from something sensible like COFF to a full blown * executable format that causes 800% overhead on a SGI IRIX box. */ entry = start[24] | start[25] << 8 | start[26] << 16 | start[27] << 24; phoff = start[28] | start[29] << 8 | start[30] << 16 | start[31] << 24; shoff = start[32] | start[33] << 8 | start[34] << 16 | start[35] << 24; _DEBUG("entry: %08x phoff %08x shoff %08x", entry, phoff, shoff); phentsize = start[42] | start[43] << 8; phnum = start[44] | start[45] << 8; shentsize = start[46] | start[47] << 8; shnum = start[48] | start[49] << 8; shstrndx = start[50] | start[51] << 8; _DEBUG("phentsize %08x phnum %d shentsize %08x shnum %d shstrndx %d", phentsize, phnum, shentsize, shnum, shstrndx); shent = shoff; totallen = 0; for (i = 0; i < shnum; i++) { name = start[shent] | start[shent+1] << 8 | start[shent+2] << 16 | start[shent+3] << 24; type = start[shent+4] | start[shent+5] << 8 | start[shent+6] << 16 | start[shent+7] << 24; flags = start[shent+8] | start[shent+9] << 8 | start[shent+10] << 16 | start[shent+11] << 24; addr = start[shent+12] | start[shent+13] << 8 | start[shent+14] << 16 | start[shent+15] << 24; offset = start[shent+16] | start[shent+17] << 8 | start[shent+18] << 16 | start[shent+19] << 24; size = start[shent+20] | start[shent+21] << 8 | start[shent+22] << 16 | start[shent+23] << 24; switch (type) { case 0: break; case 1: upse_ps1_memory_load(ins, loadAddr + addr, size, start + offset); totallen += size; break; case 2: case 3: break; case 8: upse_ps1_memory_clear(ins, loadAddr + addr, size); totallen += size; break; case 9: for (rec = 0; rec < (size/8); rec++) { u32 offs, info, target, temp, val, vallo; static u32 hi16offs = 0, hi16target = 0; offs = start[offset+(rec*8)] | start[offset+1+(rec*8)]<<8 | start[offset+2+(rec*8)]<<16 | start[offset+3+(rec*8)]<<24; info = start[offset+4+(rec*8)] | start[offset+5+(rec*8)]<<8 | start[offset+6+(rec*8)]<<16 | start[offset+7+(rec*8)]<<24; target = loadAddr + offs; _DEBUG("[%04d] offs %08x type %02x info %08x => %08x", rec, offs, ELF32_R_TYPE(info), ELF32_R_SYM(info), target); /* * MIPS relocation types include some very exotic ones: * * 2 - standard 32-bit ELF relocation; just bump us to loadAddr * 2. * 4 - 26-bit ELF relocation (yeah... I know...) = fun stuff. * 5 - highest 16 bits only. * 6 - lowest 16 bits only. */ switch (ELF32_R_TYPE(info)) { case 2: target += loadAddr; break; case 4: temp = (target & 0x03ffffff); target &= 0xfc000000; temp += (loadAddr>>2); target |= temp; break; case 5: hi16offs = offs; hi16target = target; break; case 6: vallo = ((target & 0xffff) ^ 0x8000) - 0x8000; val = ((hi16target & 0xffff) << 16) + vallo; val += loadAddr; val = ((val >> 16) + ((val & 0x8000) != 0)) & 0xffff; hi16target = (hi16target & ~0xffff) | val; val = loadAddr + vallo; target = (target & ~0xffff) | (val & 0xffff); PSXMu32(ins, loadAddr + hi16offs) = BFLIP32(hi16target); break; default: _ERROR("unknown PS2-ELF relocation type: %d.", ELF32_R_TYPE(info)); return 0xffffffff; } } break; /* XXX: .iopmod section: we may want to validate the modules here. */ case 0x70000080: break; default: _DEBUG("unknown PS2-ELF section type: %d.", type); break; } shent += shentsize; } entry += loadAddr; entry |= 0x80000000; loadAddr += totallen; _DEBUG("entrypoint is %08x", entry); return entry; } upse_module_t * upse_load_psf2(void *fp, const char *path, const upse_iofuncs_t *iofuncs) { upse_psf_t *psfi; upse_xsf_t *xsf; u8 *in, *out, *buf = NULL; u32 inlen, buflen; u64 outlen; upse_filesystem_t *fs; upse_module_t *ret; char curdir[4096] = ""; upse_module_instance_t *ins; ret = calloc(sizeof(upse_module_t), 1); ins = &ret->instance; /* XXX: this is a magic value in HE. who knows where Neill got it from... */ loadAddr = 0x23f00; /* get and decode the data. */ in = upse_get_buffer(fp, iofuncs, &inlen); xsf = upse_xsf_decode(in, inlen, &out, &outlen); if (outlen > 0) return NULL; /* parse the reserved section into a upse_filesystem object. */ _DEBUG("filesystem length %d bytes", xsf->res_size); fs = upse_filesystem_new(); upse_psf2_parse_filesystem(fs, curdir, (u8 *) xsf->res_section, (u8 *) xsf->res_section, xsf->res_size); _DEBUG("lib: %s, libaux[0]: %s", xsf->lib, xsf->libaux[0]); if (*xsf->lib != '\0') { upse_xsf_t *lib; void *fplib; u8 *inlib, *outlib; u32 inliblen; u64 outliblen; char *tmpfn; char lcurdir[4096] = ""; tmpfn = _upse_resolve_path(path, xsf->lib); _DEBUG("opening %s", tmpfn); fplib = iofuncs->open_impl(tmpfn, "rb"); inlib = upse_get_buffer(fplib, iofuncs, &inliblen); lib = upse_xsf_decode(inlib, inliblen, &outlib, &outliblen); _DEBUG("subfilesystem length %d bytes", lib->res_size); upse_psf2_parse_filesystem(fs, lcurdir, (u8 *) lib->res_section, (u8 *) lib->res_section, lib->res_size); free(inlib); free(outlib); free(lib); } free(in); free(out); /* find and load psf2.irx. */ upse_filesystem_get_path(fs, "/psf2.irx", &buf, &buflen); if (buf == NULL) return NULL; upse_ps1_init(ins); upse_ps1_reset(ins, UPSE_PSX_REV_PS2_IOP); initialPC = upse_parse_psf2_elf(&ret->instance, buf, buflen); initialSP = 0x801ffff0; ins->cpustate.pc = initialPC; ins->cpustate.GPR.n.sp = initialSP; ins->cpustate.GPR.n.ra = 0x80000000; /* set up argc and argv */ ins->cpustate.GPR.n.a0 = 2; ins->cpustate.GPR.n.a1 = 0x80000004; /* we're running upse:/psf2.irx, so our device prefix is upse:/. */ strcpy((char *) PSXM(ins, ins->cpustate.GPR.n.a1), "upse:/psf2.irx"); /* fill out our metadata struct. */ psfi = calloc(sizeof(upse_psf_t), 1); psfi->xsf = xsf; psfi->volume = upse_strtof(xsf->inf_volume) * 32; psfi->fade = upse_time_to_ms(xsf->inf_fade); psfi->stop = upse_time_to_ms(xsf->inf_length); psfi->title = xsf->inf_title; psfi->artist = xsf->inf_artist; psfi->copyright = xsf->inf_copy; psfi->game = xsf->inf_game; psfi->year = xsf->inf_year; upse_ps1_spu_setvolume(ret->instance.spu, psfi->volume); upse_ps1_spu_setlength(ret->instance.spu, psfi->stop, psfi->fade); psfi->length = psfi->stop + psfi->fade; psfi->rate = 44100; ret->metadata = psfi; ret->opaque = fs; ret->evloop_run = upse_r3000_cpu_execute; ret->evloop_stop = upse_ps1_spu_stop; ret->evloop_render = upse_r3000_cpu_execute_render; ret->evloop_setcb = upse_ps1_spu_set_audio_callback; ret->evloop_seek = upse_ps1_spu_seek; return ret; }