s32 sys_spu_thread_bind_queue(u32 id, u32 spuq, u32 spuq_num) { sys_spu.warning("sys_spu_thread_bind_queue(id=0x%x, spuq=0x%x, spuq_num=0x%x)", id, spuq, spuq_num); LV2_LOCK; const auto thread = idm::get<SPUThread>(id); const auto queue = idm::get<lv2_event_queue>(spuq); if (!thread || !queue) { return CELL_ESRCH; } if (queue->type != SYS_SPU_QUEUE) { return CELL_EINVAL; } for (auto& v : thread->spuq) { if (auto q = v.second.lock()) { if (v.first == spuq_num || q == queue) { return CELL_EBUSY; } } } for (auto& v : thread->spuq) { if (v.second.expired()) { v.first = spuq_num; v.second = queue; return CELL_OK; } } return CELL_EAGAIN; }
s32 _sys_snprintf(ppu_thread& ppu, vm::ptr<char> dst, u32 count, vm::cptr<char> fmt, ppu_va_args_t va_args) { sysPrxForUser.warning("_sys_snprintf(dst=*0x%x, count=%d, fmt=%s, ...)", dst, count, fmt); std::string result = ps3_fmt(ppu, fmt, va_args.count); if (!count) { return 0; // ??? } else { count = (u32)std::min<size_t>(count - 1, result.size()); std::memcpy(dst.get_ptr(), result.c_str(), count); dst[count] = 0; return count; } }
s32 cellMouseInit(u32 max_connect) { sys_io.warning("cellMouseInit(max_connect=%d)", max_connect); if (max_connect > 7) { return CELL_MOUSE_ERROR_INVALID_PARAMETER; } const auto handler = fxm::import<MouseHandlerBase>(Emu.GetCallbacks().get_mouse_handler); if (!handler) { return CELL_MOUSE_ERROR_ALREADY_INITIALIZED; } handler->Init(max_connect); return CELL_OK; }
s32 sys_spu_thread_get_exit_status(u32 id, vm::ptr<u32> status) { sys_spu.warning("sys_spu_thread_get_exit_status(id=0x%x, status=*0x%x)", id, status); LV2_LOCK; const auto thread = idm::get<SPUThread>(id); if (!thread) { return CELL_ESRCH; } // TODO: check CELL_ESTAT condition *status = thread->ch_out_mbox.pop(*thread); return CELL_OK; }
s32 _sys_interrupt_thread_establish(vm::ptr<u32> ih, u32 intrtag, u32 intrthread, u64 arg1, u64 arg2) { sys_interrupt.warning("_sys_interrupt_thread_establish(ih=*0x%x, intrtag=0x%x, intrthread=0x%x, arg1=0x%llx, arg2=0x%llx)", ih, intrtag, intrthread, arg1, arg2); LV2_LOCK; // Get interrupt tag const auto tag = idm::get<lv2_int_tag_t>(intrtag); if (!tag) { return CELL_ESRCH; } // Get interrupt thread const auto it = idm::get<ppu_thread>(intrthread); if (!it) { return CELL_ESRCH; } // If interrupt thread is running, it's already established on another interrupt tag if (!test(it->state & cpu_flag::stop)) { return CELL_EAGAIN; } // It's unclear if multiple handlers can be established on single interrupt tag if (tag->handler) { sys_interrupt.error("_sys_interrupt_thread_establish(): handler service already exists (intrtag=0x%x) -> CELL_ESTAT", intrtag); return CELL_ESTAT; } tag->handler = idm::make_ptr<lv2_int_serv_t>(it, arg1, arg2); it->run(); *ih = tag->handler->id; return CELL_OK; }
s32 cellSailDescriptorCreateDatabase(vm::ptr<CellSailDescriptor> pSelf, vm::ptr<void> pDatabase, u32 size, u64 arg) { cellSail.warning("cellSailDescriptorCreateDatabase(pSelf=*0x%x, pDatabase=*0x%x, size=0x%x, arg=0x%llx)", pSelf, pDatabase, size, arg); switch ((s32)pSelf->streamType) { case CELL_SAIL_STREAM_PAMF: { u32 addr = pSelf->sp_; auto ptr = vm::ptr<CellPamfReader>::make(addr); memcpy(pDatabase.get_ptr(), ptr.get_ptr(), sizeof(CellPamfReader)); break; } default: cellSail.error("Unhandled stream type: %d", pSelf->streamType); } return CELL_OK; }
s32 sys_prx_unload_module(s32 id, u64 flags, vm::ptr<sys_prx_unload_module_option_t> pOpt) { sys_prx.warning("sys_prx_unload_module(id=0x%x, flags=0x%llx, pOpt=*0x%x)", id, flags, pOpt); // Get the PRX, free the used memory and delete the object and its ID const auto prx = idm::get<lv2_prx_t>(id); if (!prx) { return CELL_ESRCH; } //Memory.Free(prx->address); //s32 result = prx->exit ? prx->exit() : CELL_OK; idm::remove<lv2_prx_t>(id); return CELL_OK; }
error_code sys_fs_mkdir(vm::cptr<char> path, s32 mode) { sys_fs.warning("sys_fs_mkdir(path=%s, mode=%#o)", path, mode); const std::string& local_path = vfs::get(path.get_ptr()); if (fs::is_dir(local_path)) { return CELL_EEXIST; } if (!fs::create_path(local_path)) { return CELL_EIO; // ??? } sys_fs.notice("sys_fs_mkdir(): directory %s created", path); return CELL_OK; }
s32 sys_prx_start_module(s32 id, u64 flags, vm::ptr<sys_prx_start_module_option_t> pOpt) { sys_prx.warning("sys_prx_start_module(id=0x%x, flags=0x%llx, pOpt=*0x%x)", id, flags, pOpt); const auto prx = idm::get<lv2_prx_t>(id); if (!prx) { return CELL_ESRCH; } //if (prx->is_started) // return CELL_PRX_ERROR_ALREADY_STARTED; //prx->is_started = true; pOpt->entry_point.set(prx->start ? prx->start.addr() : ~0ull); return CELL_OK; }
error_code _sys_prx_stop_module(u32 id, u64 flags, vm::ptr<sys_prx_start_stop_module_option_t> pOpt) { sys_prx.warning("_sys_prx_stop_module(id=0x%x, flags=0x%x, pOpt=*0x%x)", id, flags, pOpt); const auto prx = idm::get<lv2_obj, lv2_prx>(id); if (!prx) { return CELL_ESRCH; } //if (!prx->is_started) // return CELL_PRX_ERROR_ALREADY_STOPPED; //prx->is_started = false; pOpt->entry.set(prx->stop ? prx->stop.addr() : ~0ull); return CELL_OK; }
s32 sys_get_random_number(vm::ptr<void> addr, u64 size) { sysPrxForUser.warning("sys_get_random_number(addr=*0x%x, size=%d)", addr, size); if (size > 0x1000) { return CELL_EINVAL; } switch (u32 rs = sys_ss_random_number_generator(2, addr, size)) { case 0x80010501: return CELL_ENOMEM; case 0x80010503: return CELL_EAGAIN; case 0x80010509: return CELL_EINVAL; default: if (rs) return CELL_EABORT; } return CELL_OK; }
s32 sys_spu_image_close(vm::ptr<sys_spu_image_t> img) { sysPrxForUser.warning("sys_spu_image_close(img=*0x%x)", img); if (img->type == SYS_SPU_IMAGE_TYPE_USER) { //_sys_free(img->segs.addr()); } else if (img->type == SYS_SPU_IMAGE_TYPE_KERNEL) { //return syscall_158(img); } else { return CELL_EINVAL; } VERIFY(vm::dealloc(img->segs.addr(), vm::main)); // Current rough implementation return CELL_OK; }
s32 sys_timer_get_information(u32 timer_id, vm::ptr<sys_timer_information_t> info) { sys_timer.warning("sys_timer_get_information(timer_id=0x%x, info=*0x%x)", timer_id, info); LV2_LOCK; const auto timer = idm::get<lv2_timer_t>(timer_id); if (!timer) { return CELL_ESRCH; } info->next_expiration_time = timer->expire; info->period = timer->period; info->timer_state = timer->state; return CELL_OK; }
error_code sys_memory_container_get_size(vm::ptr<sys_memory_info_t> mem_info, u32 cid) { sys_memory.warning("sys_memory_container_get_size(mem_info=*0x%x, cid=0x%x)", mem_info, cid); const auto ct = idm::get<lv2_memory_container>(cid); if (!ct) { return CELL_ESRCH; } mem_info->total_user_memory = ct->size; // Total container memory // Available container memory, minus a hidden 'buffer' // This buffer seems to be used by the PS3 OS for c style 'mallocs' // Todo: Research this more, even though we dont use this buffer, it helps out games when calculating // expected memory they can use allowing them to boot mem_info->available_user_memory = ct->size - ct->used - 0x1000000; return CELL_OK; }
s32 cellSysmoduleLoadModule(u16 id) { cellSysmodule.warning("cellSysmoduleLoadModule(id=%s)", get_module_id(id)); const auto name = get_module_name(id); if (!name) { cellSysmodule.error("cellSysmoduleLoadModule() failed: unknown module 0x%04X", id); return CELL_SYSMODULE_ERROR_UNKNOWN; } //if (Module<>* m = Emu.GetModuleManager().GetModuleById(id)) //{ // // CELL_SYSMODULE_ERROR_DUPLICATED shouldn't be returned // m->Load(); //} return CELL_OK; }
s32 sys_spu_thread_group_create(vm::ptr<u32> id, u32 num, s32 prio, vm::ptr<sys_spu_thread_group_attribute> attr) { sys_spu.warning("sys_spu_thread_group_create(id=*0x%x, num=%d, prio=%d, attr=*0x%x)", id, num, prio, attr); // TODO: max num value should be affected by sys_spu_initialize() settings if (!num || num > 6 || prio < 16 || prio > 255) { return CELL_EINVAL; } if (attr->type) { sys_spu.todo("Unsupported SPU Thread Group type (0x%x)", attr->type); } *id = idm::make<lv2_spu_group_t>(std::string{ attr->name.get_ptr(), attr->nsize - 1 }, num, prio, attr->type, attr->ct); return CELL_OK; }
s32 sceNpInit(u32 poolsize, vm::ptr<void> poolptr) { sceNp.warning("sceNpInit(poolsize=0x%x, poolptr=*0x%x)", poolsize, poolptr); if (poolsize == 0) { return SCE_NP_ERROR_INVALID_ARGUMENT; } else if (poolsize < 128 * 1024) { return SCE_NP_ERROR_INSUFFICIENT_BUFFER; } if (!poolptr) { return SCE_NP_ERROR_INVALID_ARGUMENT; } return CELL_OK; }
s32 cellFsAioWrite(vm::ptr<CellFsAio> aio, vm::ptr<s32> id, fs_aio_cb_t func) { cellFs.warning("cellFsAioWrite(aio=*0x%x, id=*0x%x, func=*0x%x)", aio, id, func); // TODO: detect mount point and send AIO request to the AIO thread of this mount point const s32 xid = (*id = ++g_fs_aio_id); const auto m = fxm::get_always<fs_aio_manager>(); m->thread->cmd_list ({ { 2, xid }, { aio, func }, }); m->thread->lock_notify(); return CELL_OK; }
s32 prx_load_module(std::string path, u64 flags, vm::ptr<sys_prx_load_module_option_t> pOpt) { sys_prx.warning("prx_load_module(path='%s', flags=0x%llx, pOpt=*0x%x)", path.c_str(), flags, pOpt); const ppu_prx_object obj = fs::file(vfs::get(path)); if (obj != elf_error::ok) { return CELL_PRX_ERROR_ILLEGAL_LIBRARY; } const auto prx = ppu_load_prx(obj); if (!prx) { return CELL_PRX_ERROR_ILLEGAL_LIBRARY; } return prx->id; }
s32 cellVideoOutGetScreenSize(u32 videoOut, vm::ptr<f32> screenSize) { cellAvconfExt.warning("cellVideoOutGetScreenSize(videoOut=%d, screenSize=*0x%x)", videoOut, screenSize); if (videoOut != CELL_VIDEO_OUT_PRIMARY) { return CELL_VIDEO_OUT_ERROR_UNSUPPORTED_VIDEO_OUT; } //TODO: Use virtual screen size #ifdef _WIN32 //HDC screen = GetDC(NULL); //float diagonal = roundf(sqrtf((powf(float(GetDeviceCaps(screen, HORZSIZE)), 2) + powf(float(GetDeviceCaps(screen, VERTSIZE)), 2))) * 0.0393f); #else // TODO: Linux implementation, without using wx // float diagonal = roundf(sqrtf((powf(wxGetDisplaySizeMM().GetWidth(), 2) + powf(wxGetDisplaySizeMM().GetHeight(), 2))) * 0.0393f); #endif return CELL_VIDEO_OUT_ERROR_VALUE_IS_NOT_SET; }
error_code sys_spu_elf_get_segments(u32 elf_img, vm::ptr<sys_spu_segment> segments, s32 nseg) { sysPrxForUser.warning("sys_spu_elf_get_segments(elf_img=0x%x, segments=*0x%x, nseg=0x%x)", elf_img, segments, nseg); // Initialize ELF loader vm::var<spu_elf_info> info({0}); if (auto res = info->init(vm::cast(elf_img))) { return res; } // Load ELF header vm::var<elf_ehdr<elf_be, u64>> ehdr({0}); if (info->ldr->get_ehdr(ehdr) || ehdr->e_machine != elf_machine::spu || !ehdr->e_phnum) { return CELL_ENOEXEC; } // Load program headers vm::var<elf_phdr<elf_be, u64>[]> phdr(ehdr->e_phnum); if (info->ldr->get_phdr(phdr, ehdr->e_phnum)) { return CELL_ENOEXEC; } const s32 num_segs = sys_spu_image::fill<false>(segments, nseg, phdr, elf_img); if (num_segs == -2) { return CELL_ENOMEM; } else if (num_segs < 0) { return CELL_ENOEXEC; } return CELL_OK; }
error_code sys_fs_opendir(vm::cptr<char> path, vm::ptr<u32> fd) { sys_fs.warning("sys_fs_opendir(path=%s, fd=*0x%x)", path, fd); const std::string& local_path = vfs::get(path.get_ptr()); if (local_path.empty()) { sys_fs.error("sys_fs_opendir(%s) failed: device not mounted", path); return CELL_ENOTMOUNTED; } // TODO: other checks for path if (fs::is_file(local_path)) { sys_fs.error("sys_fs_opendir(%s) failed: path is a file", path); return CELL_ENOTDIR; } fs::dir dir(local_path); if (!dir) { sys_fs.error("sys_fs_opendir(%s): failed to open directory", path); return CELL_ENOENT; } const auto _dir = idm::make_ptr<lv2_dir>(path.get_ptr(), std::move(dir)); if (!_dir) { // out of file descriptors return CELL_EMFILE; } *fd = _dir->id; sys_fs.notice("sys_fs_opendir(%s) -> lv2_fs_id %d", path, _dir->id); return CELL_OK; }
error_code sys_event_port_connect_local(u32 eport_id, u32 equeue_id) { sys_event.warning("sys_event_port_connect_local(eport_id=0x%x, equeue_id=0x%x)", eport_id, equeue_id); const auto queue = idm::get<lv2_event_queue>(equeue_id); if (!queue) { return CELL_ESRCH; } const auto port = idm::check<lv2_event_port>(eport_id, [&](lv2_event_port& port) -> CellError { if (port.type != SYS_EVENT_PORT_LOCAL) { return CELL_EINVAL; } lv2_lock lock{port}; if (!port.queue.expired()) { return CELL_EISCONN; } port.queue = queue; return {}; }); if (!port) { return CELL_ESRCH; } if (port.value) { return port.value; } return CELL_OK; }
s32 sys_raw_spu_create(vm::ptr<u32> id, vm::ptr<void> attr) { sys_spu.warning("sys_raw_spu_create(id=*0x%x, attr=*0x%x)", id, attr); LV2_LOCK; // TODO: check number set by sys_spu_initialize() const auto thread = idm::make_ptr<RawSPUThread>(""); if (!thread) { return CELL_EAGAIN; } thread->cpu_init(); *id = thread->index; return CELL_OK; }
s32 sys_raw_spu_load(s32 id, vm::cptr<char> path, vm::ptr<u32> entry) { sysPrxForUser.warning("sys_raw_spu_load(id=%d, path=%s, entry=*0x%x)", id, path, entry); const fs::file elf_file = fs::file(vfs::get(path.get_ptr())); if (!elf_file) { sysPrxForUser.error("sys_raw_spu_load() error: %s not found!", path); return CELL_ENOENT; } sys_spu_image img; img.load(elf_file); img.deploy(RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * id, img.segs.get_ptr(), img.nsegs); img.free(); *entry = img.entry_point; return CELL_OK; }
error_code sys_spu_image_close(vm::ptr<sys_spu_image> img) { sysPrxForUser.warning("sys_spu_image_close(img=*0x%x)", img); if (img->type == SYS_SPU_IMAGE_TYPE_USER) { //_sys_free(img->segs.addr()); vm::dealloc_verbose_nothrow(img->segs.addr(), vm::main); } else if (img->type == SYS_SPU_IMAGE_TYPE_KERNEL) { // Call the syscall return _sys_spu_image_close(img); } else { return CELL_EINVAL; } return CELL_OK; }
s32 _sys_interrupt_thread_disestablish(ppu_thread& ppu, u32 ih, vm::ptr<u64> r13) { sys_interrupt.warning("_sys_interrupt_thread_disestablish(ih=0x%x, r13=*0x%x)", ih, r13); LV2_LOCK; const auto handler = idm::get<lv2_int_serv_t>(ih); if (!handler) { return CELL_ESRCH; } // Wait for sys_interrupt_thread_eoi() and destroy interrupt thread handler->join(ppu, lv2_lock); // Save TLS base *r13 = handler->thread->gpr[13]; return CELL_OK; }
error_code cellVideoOutConfigure(u32 videoOut, vm::ptr<CellVideoOutConfiguration> config, vm::ptr<CellVideoOutOption> option, u32 waitForEvent) { cellSysutil.warning("cellVideoOutConfigure(videoOut=%d, config=*0x%x, option=*0x%x, waitForEvent=%d)", videoOut, config, option, waitForEvent); switch (videoOut) { case CELL_VIDEO_OUT_PRIMARY: if (config->resolutionId != g_cfg_video_out_resolution.get() || config->format != CELL_VIDEO_OUT_BUFFER_COLOR_FORMAT_X8R8G8B8 || (config->aspect != CELL_VIDEO_OUT_ASPECT_AUTO && config->aspect != g_cfg_video_out_aspect_ratio.get())) { return CELL_VIDEO_OUT_ERROR_ILLEGAL_CONFIGURATION; } return CELL_OK; case CELL_VIDEO_OUT_SECONDARY: return CELL_OK; } return CELL_VIDEO_OUT_ERROR_UNSUPPORTED_VIDEO_OUT; }
s32 sys_raw_spu_image_load(ppu_thread& ppu, s32 id, vm::ptr<sys_spu_image_t> img) { sysPrxForUser.warning("sys_raw_spu_image_load(id=%d, img=*0x%x)", id, img); // TODO: use segment info const auto stamp0 = get_system_time(); std::memcpy(vm::base(RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * id), img->segs.get_ptr(), 256 * 1024); const auto stamp1 = get_system_time(); vm::write32(RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * id + RAW_SPU_PROB_OFFSET + SPU_NPC_offs, img->entry_point | 1); const auto stamp2 = get_system_time(); sysPrxForUser.error("memcpy() latency: %lldus", (stamp1 - stamp0)); sysPrxForUser.error("MMIO latency: %lldus", (stamp2 - stamp1)); return CELL_OK; }
error_code sys_timer_connect_event_queue(u32 timer_id, u32 queue_id, u64 name, u64 data1, u64 data2) { sys_timer.warning("sys_timer_connect_event_queue(timer_id=0x%x, queue_id=0x%x, name=0x%llx, data1=0x%llx, data2=0x%llx)", timer_id, queue_id, name, data1, data2); const auto timer = idm::check<lv2_obj, lv2_timer>(timer_id, [&](lv2_timer& timer) -> CellError { const auto found = idm::find_unlocked<lv2_obj, lv2_event_queue>(queue_id); if (!found) { return CELL_ESRCH; } semaphore_lock lock(timer.mutex); if (!timer.port.expired()) { return CELL_EISCONN; } // Connect event queue timer.port = std::static_pointer_cast<lv2_event_queue>(found->second); timer.source = name ? name : ((u64)process_getpid() << 32) | timer_id; timer.data1 = data1; timer.data2 = data2; return {}; }); if (!timer) { return CELL_ESRCH; } if (timer.ret) { return timer.ret; } return CELL_OK; }